← Back to devlog

2025-11-29

Card-play loop, week one

The combat scene where the first cards were played
The combat scene where Strike got played for the first time.

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.