Skip to content
Shattered Crowns

v0.5.12-dev · 2026-07-19

Nobody Was Asking

The second two-player playtest pass — twelve numbered reports, three battle logs. The title is the through-line: four of these were systems that already worked correctly and were simply never consulted. The aura had been drawing since Phase 3 and was buried under a disc added three releases later; the weapon-reach helper and its call site both shipped in v0.5.8 and read a field that never arrived; Armor Break lowered max HP in the engine while the HUD printed the old number. Not one of them was a broken calculation.

Fixed — the match didn't end when it was over (900a283)

Reported as "game doesnt really end when over. its like 3 more rounds of 'waiting' before the final screen."

check_match_end treated any KO'd-but-not-crystallized body as revivable and held the match open until its death clock ran out. But a Raise has to be cast by somebody, and a team with zero living units has nobody left to cast it — so the winning side was handed three rounds of units opening their turn and immediately waiting, against a result already decided. The revive window is real and still works; it just lives where it can be used now, on a team that still has a caster standing.

Fixed — Jump could spear its own jumper (900a283)

Reported as "dancer jumped on herself in battle log 1? not sure."

Confirmed, and she killed herself doing it. resolve_queued_cast lands the dragoon on the aimed tile before resolve_ability runs — deliberately, so the landing blow comes from a unit that is back on the board and counterable. When the target had vacated that tile mid-flight, the jumper was the one standing on it, and the damage arm's single unit_at lookup found them: 32 damage, self-KO, and a log line reading "Dancer uses Jump on themself."

Not covered by the existing landing test, which keeps the target in place — so the jumper lands beside them and the lookup finds the victim. The self-hit needs an empty aimed tile.

Changed — Speed no longer buys a second game (900a283)

Reported as "speed is imba in this game - see battle log 2 to see a normal speed (not hasted) ninja going every other turn."

Measured before touching anything: CT gain was exactly sp, so turn share was exactly proportional to a stat spanning 5 (summoner) to 12 (ninja) — 15 after gear. The fastest unit took 2.77x the turns of the slowest. Doubling a unit's actions is worth more than any other stat in the game can buy, so Speed was not one stat among six; it was the stat.

CT gain is now CT_BASE_GAIN + sp, which compresses the ratio to ~1.53x without touching a single job row — every class keeps its exact ordering, and jobs.rs stays the balance document.

Worth recording: the first attempt over-corrected to 1.03x, i.e. it made Speed inert, which is as wrong as letting it double-turn. The guard test asserts a floor as well as a ceiling because of it.

Joe's rule, now pinned as a test derived from the live job table: no unit may take two turns before another has taken one, at base speed. Measured ungeared, ninja (sp 12) against summoner (sp 5): 2.26x before, 1.50x now. The old curve was a literal double-turn.

That test is a duel on purpose. With a full roster on the board the ratio reads 1.04x, which looks like a pass and is really just the tick loop running out of supply -- so the test asserts its own board is unsaturated before it measures anything.

Known and not fixed: at 5v5 and 8v8 the roster's total CT gain already exceeds the threshold, so those modes have always equalised turns and Speed is close to inert there regardless. Fixing that means raising CT_THRESHOLD and the client tick period together — a pacing change, not a balance one.

Changed — status durations, 21 of them (900a283)

Joe's call: "confuse lasts 4 turns, change it to 2. too strong — actually, pretty much no status should last longer than 3 turns."

A match is decided in 20-40 turns across six units, so a unit sees perhaps six turns in a whole battle. A 4-turn Confuse was therefore not a debuff — it was two thirds of that unit's participation, from one ability. Reraise at 6 outlived most matches outright.

The rule the retune followed: how long a status lasts scales inversely with how much agency it removes. Numbers-only mods (Protect, Attack Up, the DOT/HOTs) keep the full 3; anything that costs you the turn itself or points your unit at its own team gets 2. Registry-wide test enforces both ceilings.

Fixed — the match clock ran during the roll ceremony (900a283)

Reported as "in battle log #3, the game started with Bard using Valor song on Seraph before anyone even had a turn."

The server started its tick loop the instant the match was created, with no notion that every client was still animating the pre-battle reveal. Battle 3 opens with the enemy Bard casting at t=0.04s — and Joe's own unit was handed, and timed out of, two 30-second turns against a board he could not see yet.

The clock now waits. Released by a client ack when the ceremony closes, and bounded by a 25-second deadline so an old client, a dropped frame or a wedged machine costs a short delay and never a frozen match.

Fixed — basic-attack reach ignored the weapon (900a283)

Reported as "i equipped a gun, got a gun weapon in the roll, but my attack range is still only 1."

GameDatabase.attack_range_for and its call site in battle_scene both shipped in v0.5.8. The equipment key they read was never put on the client's unit dict, so the helper hit its return 1 guard for every weapon in the game — bows, crossbows and guns all swung like daggers. Now carried, and re-read on every sync, so a shattered bow stops reaching five tiles. Wired into the offline AI too, which had been walking archers into melee.

Fixed — status counters, the death clock, and max HP after Armor Break

Three reports, one cause: the client re-read the engine's derived state only after a command, never on the clock. Everything the engine changed between turns was invisible.

  • Status durations never appeared to count down (reported as "the status indicators number doesnt go down each turn").
  • Death tickers sat at 3 forever ("death tickers are not dropping # at all").
  • Armor Break lowered max_hp correctly in the engine — and had a passing test proving it — while the HUD kept printing the old ceiling, so the knight still read 117/147 after losing his plate ("not sure if it actually lowered samurai's MAX HP").

Fixed — equipped auras have been invisible since v0.5.6 (900a283)

Reported as "I bought the Royal Aura and its equipped but didnt display anywhere."

The ring has been drawing correctly since Phase 3. What changed is that v0.5.6 gave every unit a solid emissive team-colour base plate of radius 0.42 at y -0.29 — and the aura is a torus of outer radius 0.42 at y -0.30. Same width, one centimetre lower, completely buried. Resized to sit outside the plate, with the plate's radius named as a constant so the next person to move either one has something to collide with.

(The title half already worked — "GAYLORD Stormcaller" is on the victory screenshot from the same session.)

Fixed — Fist advertised 91% odds (900a283)

Reported as "iron fists new weapons do not = 100% chance."

The 9% was never lost: Fist stocks no rare item, and a rare roll falls down into uncommon, which really pays 39%. But the Codex printed the raw 60/30/9/1 bands from a hardcoded client-side copy, which cannot know that. Now computed engine-side by walking the same fallback the roll walks, so every weapon class advertises a full 100% and a retune moves the tier and its displayed percentage together.

Changed — AI-filled units bring their own kits (900a283)

Reported as "for AI filled slots, randomize those units with random sub class. right now its all the same."

Bots were indexed straight out of a fixed five-job array and given no secondary at all, which is why every AI-filled 3v3 opened knight + archer against healer

  • mage, match after match. The primaries are shuffled per match now, and each bot draws a random legal sub-job — which is where a bot's kit actually varies, since the secondary rolls its own three picks.

Added — the reveal shows Brave, Faith and zodiac (900a283)

These are per-match rolls too — create_unit_from_loadout draws each one and picks a fresh sign — so the ceremony that shows you what you rolled was hiding a third of it. They matter before the first turn, not after: Brave scales every physical hit, Faith scales magic both ways, and the zodiac pairing is worth knowing while you are still choosing who to walk at.

Note on the tests

The Jump, opening-hold, weapon-reach and aura fixes were each verified by reverting them and watching the new test fail.

That mattered once: the weapon-reach smoke check passed against the broken build on its first draft. With the fix reverted every unit had an empty equipment dict, every unit hit the loop's continue, and the check asserted nothing while reporting PASS. It counts its comparisons now. A test that passes while doing nothing is precisely how this class of bug survives here.