Ignoring the side effects of io, a main loop can be replaced by a recursive call that passes the new world state resulting from the time step back to the beginning. That world state can be expressed as an ecs, and the operations performed on a world state to produce a new one can be the systems, and still be pure functions. There is nothing in an ecs that requires imperative programming. Some of the benefits of an ECS are related to allowing granular copies to be as shallow as possible for such a world state, to allow for immutability. I'm not sure where a singleton would get used even if you were wanting to go imperative with an ecs.
You don’t even have to ignore io: input is a coeffect which is passed to your otherwise pure function as an argument and output is directly derived from your returned works state. That is, yes there’s some side effects happening before and after each iteration but it can be relatively cleanly separated. In my toy engine, I do this: input is fed in and rendering is driven by the output. My game logic isn’t a pure function (it’s a task graph that reads and writes from event queues and internally maintained task state, but input is fed into the start of the task graph and rendering is driven by tasks at the end), but it conceptually could be as all the statefulness is encapsulated and internal.