2025-11-29
Card-play loop, week one

The combat engine is up. You can draw a hand, play Strike five times, and watch a slime die. That's most of a game.
The whole engine is a single function: playCard(state, cardUid, targetUid) → state. Pure, deterministic, no side effects, RNG threaded through as a state object. The same is true for endTurn, drawCards, and the enemy AI. A run is a list of these calls. If I save the calls, I can replay them.
Why this matters: the leaderboard problem. If the engine is deterministic, I can later run the same reducer on the server against the submitted action list and recompute the score. That's anti-cheat with no extra network round-trips during play. I don't have it yet — for now scores run on honour — but the architecture is paid for.
What's playable right now: 1 class (Warrior), 8 cards (Strike, Defend, Bash, Iron Wave, Pommel Strike, Cleave, Heavy Blade, Anger), and a single enemy — the Acid Slime, who hits for 5. The fights are five seconds long. It feels right.
What hurts: the test loop. I'm clicking through the UI to verify a 3-line reducer change. Going to write proper engine unit tests over the weekend so I can iterate on numbers without loading a browser.
Next: a procedurally-generated 15-floor map with branching paths.