2026-02-02
15:12:52, atom feed.
I missed the update two week's ago, and last week's update is late, so this post is a double feature! Recently I am spending a lot more time editing Twitch VODS to create a YouTube dev log of sorts. This amounts to me listening to me build my own game instead of spending time building the game, and these blog posts are also me talking about working instead of "working," so I will probably start to make these updates more concise.
I will still post small videos of gameplay directly in some of these updates which will serve as quick visual checkpoints in the journey, though. I will also still write the occassional long post when I have a topic I want to discuss in more detail.
Here is the curated commit log from two weeks ago.
2026-01-20
feat: add sinusoidal entity pos. displacement during attack animation
2026-01-20
feat: added transient health bars above entities
This addes health bars as entities in the component system, which is not
what we ultimately want to do. Instead, we need to add them to the UI
system and somehow associate timers with the graphics.
2026-01-21
feat: move health bars to just components, not entities of their own
2026-01-21
feat: add diagonal facing direction for entities, render system
2026-01-21
feat: move health bar/tile border rendering to Ui; fix draw order
2026-01-23
experiment: introduce explicit turn-order tracking
This is difficult to reason about and cumbersome to maintain and work
with. This is likely not the right approach.
I started the week off by adding position displacement to entities when they were performing attack animations. This is done with the simple formula
max_displacement = N;
ratio = time_left_on_animation_timer / total_animation_time;
current_displacement = N * sin(pi * ratio);
This way an attacking enemy moves, albeit with no special "moving" sprite animation, in the direction it attacks and then retreats, with movement displacement proportional to the ratio of time left on an animation timer vs. the total amount of time the animation takes. This easy change adds a little more life to the game.
Next I added transient health bars. The idea here was to temporarily
display a health bar over an entity after it took damage. Because the health
bar was only temporarily displayed, I decided to make the health bar an entity
in the ECS with a dedicated timer on it. This was a bad idea for various
reasons; in short, I then made the timer a component of an entity, and the
component is just a timer. When the timer expires, the component is removed
from the entity (in my implementation, the has flag is set to false). One
nice thing about this implementation is that I could add another property to
the component which is the health the entity had before being attacked, so I
could highlight precisely how much health the entity lost instead of just
showing the entity's instantaneous health.
One issue I had up to this point was that while enemies logically attacked the player one at a time, they could all do so starting on the same frame, meaning that all their attack animations would play simultaneously. In the game I'm making, I want them to play serially. Until now, I was tracking turn order by just looping through the view over my collection of entities. The player came first and so got to act first, and if the player acted, then a turn-blocking attack animation was played, but when entities attacked, there was no way to persist a half-emptied turn "queue" across frames, and so some entities' turns could be skipped entirely. So, I needed to handle turn ordering across frames.
I implemented this very suboptimally initially, cleaning it up later (see next week's update). I created something similar to my entity storage, which is a fixed-size array in the game state to hold indicies of entity lined up to take their turn. I used a view over the array to track which entities were actually in the "queue." The indices in the view were actually indices into the collection of entities, and because I packed entities contiguously, if one entity's health dropped to 0 and that entity was in the turns view, I needed to remove the entity from the turns view and then look for any other entity in the view that had an index greater than that of the entity I just deleted and decrement those indices by 1. This is because those entities would have been shifted up by 1 in the backing entity storage and thus their indicies have changed.
This was the source of many logical errors.
Here is the curated commit log from last week.
2026-01-27
fix: make all entities' action cooldown values the same
This fixes an issue where entities can move and attack before an
entity that just attacked can take another turn.
2026-01-27
feat: introduce prune_turn_queue_via_entity_cooldown
2026-01-29
feat: add peek_at, delete_at, set_at procedures to ring buffer API
2026-01-29
experiment: use ring buffer as queue to store entity turn orders
2026-01-30
feat: valid entities are no longer contiguous in the ECS
2026-01-30
feat: add graphical debug view, toggle with F1
Last week, I fixed an issue where certain cooldown values would cause entities' turns to be skipped. Currently I have quite a few cooldown values in the implementation: an action cooldown, a player attack cooldown, an enemy attack cooldown, and idle animation timer value, etc. Because I detect whether a turn-blocking animation is playing by looking at certain components' timer values relative to the state an entity is in, it's becoming difficult to reason about timing of events relative to other events. The general solution here, constrained by the particular game design I have in mind, is to reduce the number of cooldowns and have them apply mostly to the player, or to corral the usage of most cooldowns to a single timer component. This way it's easier to reason about what is gating the progression of turns.
At one point, the action cooldown for attacking was longer than the action cooldown for moving, so, for example, enemies could move twice if the player attacked once. If everyone either attacked or moved, the turn view would became empty, and I repopulated it by just adding all valid entities into it. Most if not all of the entities had an active cooldown timer ticking down, though, so then the question was, when it was that entity's "turn," do I just skip the enemy but leave them in the view or do I remove them from the view? Leaving the entity in the view means the entity gets to go immediately once its cooldown expires, which may not follow logical turn order depending on this or that game mechanic. Removing the entity was the right thing to do, but then when does it get re-added? Well, only when the view next becomes empty. And there are more states to describe which increase the complexity of reasoning about turn order and all that. It all boils down to me needing to carefully define what the game's allowed turn sequencing is and then implement that instead of it being emergent.
I wondered, would using a different data structure for the turn view help, for example an actual queue? So I used my ring buffer implementation as a queue. From a conceptual perspective, this helped more, but it came with its own challenges. By nature of being a ring buffer, the logical end of the buffer can come before the logical beginning of the buffer (as far as indices are concerned), so therefore one cannot simply take a Jai view over the backing storage. This means I needed to add support procedures to introspect the contents of the ring buffer. That was all fine, but then we hit the same issue the view had, which was that when we need to delete an entity from the middle of the queue, we have to shift everyone down. This could be solved by using something like a linked list, but I don't have such a data structure and, if I did, I would want a version where all backing storage can be allocated up front. This is esentially an array where elements are wrapped in something that provides the index of the next element. Fun idea but not immediately necessary.
After various bumpy rides through my naive understanding of an ECS and thorny implementations of turn ordering, I realized, as noted earlier, that the biggest pain was coming from keeping valid entities contiguous in backing entity storage. Once stopped doing this and just searched the entire collection of entities for valid entities when populating the turn queue, the index juggling work I was doing earlier became much simpler. Now, my ECS implementation still uses a lot of space unnecessarily, but there are such view entities and components in my game that the actual overhead is not tiny, on the order of mebibytes (currently less than about 3). So further optimiation of the ECS is not a priority at this time, as I am driving toward being able to play with game design more than engine fundamentals.
Lastly, after juggling entity indices into backing storage and "turns" which were indicies of entities in the entity view and therefore entity backing storage and debugging timing and all of this, I realized it's time to add a little graphical debugging support, so I added a simple turn queue visualization and also a visualization of entity index and turn queue index. The entity index is drawn on the top half of an entity, and the turn queue index is drawn on the bottom half, both toggled when I press F1.
The visualization reflects the turn queue every frame, so there are times where it is entirely empty, while all entities are cooling down from some action. There are other times when only the player is in the queue. This is caused by the player performing some action, getting a cooldown set, then all other entities performing some action, getting cooldowns set, the player's cooldown expiring such that the player gets added back into the turn queue, and then the player stalling (neither moving nor attacking when it's the player's turn), which also prevents the turn queue from being repopulated. (I currently only add entities to the turn queue that don't have an active action cooldown.)