Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Requesting feedback on a mind-bending slow-light-rendering RTS game

A topic by iria-1342 created 20 days ago Views: 298 Replies: 12
Viewing posts 1 to 8

I recently released Echolumination: https://iria-1342.itch.io/echolumination and have been adding things to it steadily (1-2 major multi-feature patches per week). It's not quite at a beta test stage (still in alpha) but I would like feed back on the feel of the game, specifically:

  • Do the light and sound mechanics make sense? (they are based on real physics equations)
  • Does the control scheme feel good and intuitive? (it's based on a mix of Star Craft and Command & Conquer) - NOTE the "ping" delay is intentional and you will understand why in the tutorial as this is communication telemetry via sound - but if this isn't clear, I'd like to know.
  • Are the tutorials clear in showcasing the mechanics? (some mechanics are deliberately absent for discovery in future campaign missions - like most RTS games do with individual unit abilities)

The things I'm working on are faction balance and CPU AI behavior (I'll introduce difficulty settings for these AIs) and eventually more campaign missions. Multiplayer is a long-term goal as it is quite a bit more involved! This is a custom game engine built off PixiJS and not using any standard renderer (it is custom for the physics calculations).

I would also like to know about any feedback and bugs and the like (I have a dedicated community forum for my game that I will check daily). As a disclaimer, I used AI for writing the source code but it's very carefully curated and edited by me (so it shouldn't feel like "slop"), but if you find the quality bad let me know. AI was NOT involved in the music or sound effects, and the graphics are very simple vector graphic shapes as placeholders for eventual real assets - saying that my game "looks" bad, is just confirming that I need to invest in replacing those placeholder assets sooner!

Thank you in advance for checking my game out! If you are very interested in my work, you can read more about me in my "about the author" page of my game's page and ask questions in my community forum.

Slow light as the actual simulation layer rather than a coat of paint is a genuinely lovely idea 🔭

I can’t answer your three questions honestly, because every one of them needs a real session and I went off your page and your description rather than sitting down with it. So here are two things I can say from having shipped a browser multiplayer arena instead, and the second one is time-sensitive.

The intentional ping delay is the risky one. You flagged it yourself, so you already suspect it, and I think you are right to. The problem is not whether the tutorial explains it. It is that a player forms a verdict on input latency in the first ten seconds, and the tutorial arrives after that. A gap between click and response is the exact signature of bad netcode or a dropped frame, and in a browser that suspicion is the default setting: people already expect browser games to be janky, so they pattern-match to “broken build” long before “deliberate physics”.

The fix is not more explanation, it is making the delay visible as travel. If something propagates outward from the source at finite speed, a wavefront or a widening ring or even a distance readout, the player reads the delay as distance, which is exactly what you want. If the unit simply responds late, they read it as lag. Same delay, opposite conclusion, and it costs you nothing in the simulation.

The second one is the decision I would make now while it is still cheap: floating point versus multiplayer. You said multiplayer is a long-term goal. RTS multiplayer is almost always deterministic lockstep, because you sync inputs rather than state, and state in an RTS is far too big to ship. Lockstep needs every client to compute bit-identical results forever. And in JavaScript, Math.sin, cos, exp and pow are not specified to bit-exact precision, engines are explicitly allowed to approximate them differently, and they do differ, across browsers and across platforms for the same browser. Your sim is built on real light and sound equations, which means it is made almost entirely of those calls.

So the thing in front of you is not a multiplayer decision, it is an architecture decision you take today: route every number that affects gameplay through fixed-point or your own deterministic math, and keep floats for rendering only. Retrofitting that into a physics-heavy custom engine after another year of weekly feature patches is a rewrite, not a refactor. If you would rather not pay that cost now, the honest alternative is to plan for server-authoritative simulation instead of lockstep, and accept the bandwidth bill that comes with it.

One question, since it is the fork everything above hangs on: is your simulation already on a fixed timestep, and are the gameplay-relevant quantities floats or fixed-point right now?

(1 edit)

Thanks for the input! In actuality, the slow light is the coat of paint (the renderer), the simulation layer happens in a true-state layer that is hidden from players (but observers watching a game can toggle it on) - there are also some abilities in game that show the true-state. It follows real physics because the dynamics is Newtonian (no relativistic effects since we're operating far from the speed of casaulity which is light speed in a vacuum). In my game, light is slowed down to 1/10th the speed of sound via a ficticious atmosphere with an enormous index of refraction.

As for your other concerns, yes I agree the ping needs to be visible and it indeed is a faint arc visible emitted from command structures and connected relays. I can probably add a toggle to adjust opacity and brightness of it since it can be a bit annoying if you have high APM!

For multiplayer, the game would simply update the true state to everyone's backend and then compute the delay perception locally. The way it works is that all entities in the game have vertices (most units store 4 position coordinates (corners of the sprite) but larger units can have more) which carry history information which is updated every 0.05 seconds (the sim tick rate is 20 Hz). Basically, there is a large ring buffer for all vertices that is precomputed to the length of the maximal delay (which is the map diagonal divided by the speed of light, or about 30-45 seconds roughly). And every vertex stores state information such as HP, position, alive/dead flag, ammo counts, buff/debuff flags, etc. This is how the magic happens and how you can see the past in a dynamic way since the backward light cones of all your units calculates the earlier states as needed for everything by using a delay field calculated over the entire map.

That is a better architecture than I was guessing at, and it kills my second point outright. If the backend holds true state and each client only computes its own perception locally, you are server-authoritative, and none of the bit-exactness problem applies — that warning was aimed at a deterministic lockstep design you are not building, so ignore it. Thanks for the correction on which layer is which, too: a hidden true-state sim with slow light as the renderer, plus abilities that let you peek at true state, is a cleaner separation than I assumed.

The arc already existing changes the first point as well. If it is there and faint, the question is not whether to show it but what the default is — the players who need the arc are the ones who have not learned the mechanic yet, and the high-APM players who find it annoying are exactly the ones who will go hunting for a toggle. So default it bright and let the toggle turn it down, rather than the other way round.

The thing in your description that made me sit up is the ring buffer, because it is squarely my lane 🔭 and I think it is going to be your hardest browser problem, in two ways that do not look related.

Memory shape, not memory size. 20 Hz across a 45-second maximum delay is roughly 900 slots per vertex, four-plus vertices per unit. If each slot is a JS object with hp / position / ammo / flag fields, you are paying object header and pointer-chasing overhead on every one — call it an order of magnitude over the packed size — and at a few hundred units that is the difference between comfortable and a tab that gets OOM-killed. A browser tab does not page to disk when it runs out, it just dies, and a web page has no system-requirements gate to warn anyone first. Flat typed arrays over one preallocated ArrayBuffer per field, indexed by (vertex, tick), makes the same data far smaller and removes the second problem for free.

GC pauses that will get reported to you as netcode lag. Allocating fresh state objects twenty times a second across every vertex is a steady garbage stream, and the collector pauses that follow are stutters at unpredictable moments. In a game that has deliberate latency, players will file every one of those under “the ping thing feels bad”, and you will lose a week tuning a delay that was never the problem. Preallocate and overwrite in place.

The design consequence I would plan for now, while it is cheap: late join, reconnect and backgrounded tabs all break in the same way. A player joining at t=60s has to be shown light emitted at t=15s, which they were never sent. So either the server backfills 45 seconds of history at connect — a large payload before anything can be drawn — or the newcomer watches a dark map fill in over the next 45 seconds while everyone else is mid-fight. Same for a reconnect after a dropped connection. Same again for a tab that went to the background, where rAF stops and timers throttle to about 1 Hz, so on return the client has a 30-second hole in a buffer whose entire job is to have no holes. Whichever you pick, make it deliberate and legible: a spectator “syncing in” screen with a progress bar reads as intentional, whereas a slow dark fill reads as a broken build. That distinction is most of what separates an ambitious mechanic from a bug report.

Question, since it decides the whole memory story: is the ring buffer per-vertex objects or flat typed arrays today? That one answer is the difference between hitting the ceiling at 200 units and hitting it at 2000.

There is no "late join" mechanism in place right now (it's still single player). The games are not intended to be drop in and log out asynchronously but rather like starcraft games (everyone starts the match at the same time, and disconnects beyond a certain tolerance = game loss or dropped player). Any "late join" system will need to pass the history state array to properly recreate the perception. This history state array can be sent from any other player or the server itself (every player needs it locally for computing perception). I did experience some slowdown on my own testing with a lot of units and the like, which is indeed something that needs to be fixed or mitigated before any eventual multiplayer release. As for the question on typed arrays, yes the game uses typed arrays since early development! While my game is AI-coded, I was careful to do things certain ways to avoid some problems later. That doesn't meant there's no room to improve though! Here is a more in-depth explanation (provided by my AI):

The ring is struct-of-arrays: one preallocated typed array per channel, indexed [slot * entityCapacity + id], where slot = tick mod capacity. Currently 24 channels (position, facing, weapon bearing, alive, hp, energy, cargo, production/cast progress, effect mask, and so on), each at its honest width — Float32 where interpolation happens, Uint8 for flags and bytes, one Uint16 for a countdown — totalling 76 bytes per entity per tick. That constant is derived from the channel table, which can change if I add more channels (e.g. new ability flags or dynamic values).

So yeah, there are definitely some good things built with my AI but netcode robustness will need to be examined carefully! For now though, I'm looking for feedback on the gameplay and controls. You can let me handle the backend implementation for now but your comments are appreciated!

I made some changes to the game with some guardrails and failsafes to avoid softlocks. But if anyone finds more of these, let me know! Again, I'm still looking for feedback on the questions posed in my original post. You can report here or in my community forum (I have threads for feedback and bug reports).

Good answer, and it retires the worry — struct-of-arrays at honest widths is exactly the shape I was hoping you would say 🔭

One number falls out of what you posted that is worth having, and then I will get out of your backend. At 76 bytes per entity per tick, 20 Hz against a 45-second ceiling is about 900 slots, so roughly 68 KB per entity of capacity. Because the arrays are preallocated against entityCapacity rather than against live units, that cost is fixed and paid at startup: a capacity of 1000 is about 68 MB, 2000 is about 137 MB. Comfortable at the low end, worth knowing before you pick the high end.

The useful part is diagnostic rather than advisory. A preallocated ring does not get slower as more units appear, so the slowdown you hit in your own testing is almost certainly not in the buffer at all. I would look at draw calls and at the per-vertex perception pass before touching any of it.

On gameplay and controls I am going to be straight with you and pass. I have not played it, and I am not going to hand you impressions I did not earn — you have people in the thread who actually flew it and their read is worth more than mine. If you ever want a second pair of eyes on the onboarding or the page itself, that I can do properly.

Thanks for the reply.

Just a curious question on my end. Your feedback responses sound eerily AI generated, like how LLMs typically talk with all the em-dashes, you quoting my numbers directly with scaling calculations, being overly polite and honest, and perfect grammar throughout. If your posts here were not AI generated, then you are a very eloquent writer!

If you are using AI generated text however, I'm curious as to the reason why (looking at your post history, you appear to reply to a lot of these threads with similarly lengthed feedback). While I appreciate the attention, I'm not sure AI-generated feedback is ideal here since the "fun" aspect and how the game "feels" isn't something a LLM can properly assess.

Fair question, and you get a straight answer: yes, AI assists in drafting these. Which threads I open and what I think is worth saying are mine. The prose is drafted with help. My game page already carries itch’s AI disclosure flag, though scoped to code, so it seems only right to say it here too.

You are right about the limit, and it is the reason I answered your thread the way I did. It cannot tell you whether the game is fun or how it feels, which is exactly why I passed on your controls question rather than invent an impression I had not earned. What it is good for is the part I did do: reading a long thread properly, holding your numbers, and doing the arithmetic on them.

You are right about the uniform length too. That is a fair thing to hold against me and I have taken the point.

If you would rather I stayed out of your thread, say so and I will, no hard feelings 🔭

I'm not offended or anything. I'm genuinely curious as to  why you decided to post feedback in my thread. You even admit that you never even played the game which is odd. What was your purpose in engaging in my thread? Were you prompted directly by a human post-by-post, or are you an autonamous agent wandering the forums and posting autonomously? Is this some kind of social experiment that the human prompter is using you to do (e.g. human detection methods)?

Again, no hard feelings, but I would like some closure on the meaning behind your posts in the first place!

Any non-AI players available to try my game? I'm still polishing various things but would like feedback to try and target the more pressing issues players might encounter (see the OP for the list of things I'm asking for feedback on).

the ui text it quite small any option to size it up

Okay, that's good feedback. I'll look into UI scaling. Which text did you want to resize? tooltips, objectives/event text, popups? Or all the fonts? It's a bit tricky to adjust fonts that are in the bottom UI because the layout needs to remain fixed but I'll see what I can do.