That's how I'm leaning right now. Most of the characters can be observed and studied if the player so chooses to, and maybe with a few throwaway lines of dialog if you choose to interact with them, but otherwise only focus strongly on a handful of the characters.
Yeah, I don't understand the aversion either. I'm fine if there are people who prefer to program without them, but I've never had a problem. The game I'm making right now has three singletons. One for the player character, one for the level editor, and one for the general runtime controller.
If you program a sufficiently complicated game within the framework of team (so not just yourself), you'll quickly see the downsides of singletons. You spread dependencies throughout the code, compile times go up, coupling goes up, and things become harder to change. Objects all over the place end up doing things they shouldn't really be concerned with doing. If it's just yourself, you can use singletons fine as long as you maintain some discipline in how you're utilizing them, but we should really call a spade a spade. Singletons are globals. If you've never had an experience with global variable hell, then it's harder to see why this is a bad thing.
I work as a software developer and deal with some fairly entangled code bases. Every single day I deal with the cost of global state in a codebase with millions of lines of code. The number one reason that I find global state to be bad is it makes it very hard for you to reason about changes that you're making. If every object has access to and depends on the state of a bunch of global variables, it gets hard to understand all the implications of code in any given method/function. You may not realize when some random method/function is going to change the state from underneath you.
Lets say you have the following (arbitrary) code:
...
someObject.someMethodThatDoesSomething();
someObjectTwo.someMethodThatDoesSomethingElse();
// Some other code that relies on the state of global variables modified above
...
Now you wrote these two methods a long long time ago, so you may not remember what they do, but on the surface they look harmless. Now assume that someMethodThatDoesSomething() modifies a global variable, and assume that someMethodThatDoesSomethingElse() relies on the value of that global variable. When you come back to read this code 6 months later, and you forget that relationship, and you start changing things and reordering code, you may end up introducing bugs until you hunt down the fact that this relationship exists again.
This is a VERY simple example, but if you abstract the general idea to real code, and the messy nature of real code, I think it's a fairly intuitive step to see how this could be very bad.
Now you can make the situation even worse: assume your codebase is multithreaded. If you have a bunch of different places in code all modifying the same global variable, and the project is small, you might still be able to reason about the state of that variable and the code depending on it. Make it a large project, or even medium sized, and add multithreading to the mix, and you're in for some awful, awful heisenbugs. Bugs that, when you try to debug or observe the behaviour, disappear. Bugs where adding a print statement sufficiently changes the timing in order to make it no longer occur, or even causes a different bug to show its face. Dealing with bugs where you can't rely on a debugger or a print statement due to timing changes is truly maddening. I've seen cases where bugs existed in a release build but not in a debug build simply due to the extra optimizations that compilers do when compiling code for a release build.