v0.5.21-dev
Playtest build v0.5.21-dev.
A bug-and-calculations pass over a v0.5.20 playtest log. Six findings, and
the two biggest were both the same shape: a second implementation of
something the engine already knew how to do.
The offline AI was a second AI
network_manager._run_ai_turn was ~135 lines of GDScript under a header
claiming it "mirrors the engine's Silver AI so offline and online behave the
same". It did not, and had not for a long time:
- It moved one tile per turn. It only ever considered the four adjacent
squares, so a unit's Move stat was irrelevant to it. In the log: eight AI
moves, every one exactly one tile, while the human walked four in a turn. - It never used a charged ability —
ct_cost > 0was skipped outright.
A summoner holding 163 MP took two turns in the whole match and spent both
stepping one tile sideways. No summons, no Jump, no real magic, offline. - It ignored roughly 18 of the ~48 ability types, matching
ab.type
against a hardcoded string list. Among the missing:apply_buff,
apply_debuffanddamage_and_debuff— the three generic forms that exist
precisely so new content needs no code edit. Everything added since v0.5.6
was invisible to it.
It also targeted the closest enemy where the engine targets the weakest, never
learned the accuracy ranking added in v0.5.15, and had never heard of Invisible.
The engine AI is now the only AI. ai::decide_with_budget takes a TurnBudget
saying which slots remain, so one call answers both halves of a turn, and
ScEngine.ai_decide exposes it to the client. AI commands travel the same path
player commands do, so slot tracking, the match log and undo all behave
identically for both.
The server had the mirror-image bug: it ran exactly one decide per turn
and then ended it, so online bots moved or attacked and never both — on the
authoritative path, in every match with an AI seat or a disconnected player.
And the fast-forward never worked. ai::has_nothing_to_do matched only
Wait, while its own doc comment promised "Wait or a self-Defend". Since
defend is a universal action every unit holds, the AI's "nothing worth doing"
answer is a self-Defend — so the check returned false for every unit with an
action left, and v0.5.19's fast-forward auto-waited nobody and disengaged on
the first turn with "…has something to do".
Red was winning the coin toss every match
Both unit factories seeded the clock with ct: (id * 7) % 30. Units are
created blue-first, so that expression is a fixed per-team handicap:
| mode | blue | red |
|---|---|---|
| 1v1 | 0 | 7 |
| 2v2 | 7 | 35 |
| 3v3 | 21 | 54 |
| 4v4 | 42 | 64 |
Red opened better in every match up to 4v4, forever, and opening turn order was
identical every time. 3v3 is the launch quickmatch queue. Opening CT is rolled
with Brave/Faith/zodiac now — same band, so the pace of the opening is
unchanged, and explicitly not affected by the account level bonus, because
going first is not a stat you level into.
Damage shows its working
DamageDealt carried three multipliers out of roughly twelve, which made a
battle log impossible to check against itself. Two cases from the log:
- The same dragoon hit the same chemist for 72 and then 118, with
identical zodiac and Brave printed. Facing explains 1.25 of the 1.64× — the
rest was height, which appeared nowhere. - An Oil'd monk took a doubled Flamebrand, narrated as "zodiac ×0.75, faith
×0.53". The ×2 that a whole setup turn had been spent to buy went unmentioned
— and was missing from the hover forecast too.
combat::damage_factors now names every term the resolver applied, and the
event and the forecast call the same function so they cannot disagree. Fixed on
the way: the Drain arm reported zodiac_mult: 1.0 while its damage genuinely
collects the zodiac wheel.
Smaller things
- Aiming a single-target ability at your own side now says so. Casting
Insult on your own mage was legal, in range, hit nobody, and produced
nothing — no event, no line, no popup, action slot gone. It emits
CommandRejected{NoSuchTarget}. An AoE centred on an ally is still a whiff,
because dropping a blast on your own front-liner to catch the enemies behind
them is legitimate aiming. - The match log was stamped
v0.5.15-devfor five releases. Two version
constants sat side by side under a comment saying "bump both on each
release"; only one got bumped. The label is derived from the version now. - The match log records MP. It had none anywhere, so regen, ability costs,
the whiff refund and Chakra were all unverifiable after the fact. - Charging no longer narrates "(99 turns)" — that is a sentinel, not a
countdown. - Corrected a stale
check_match_endnote in CLAUDE.md: it does not hold the
match open for revivable bodies, deliberately.
Known, not fixed
A Fire-elemental physical ability's element is inert: physical hits take
their element from the equipped weapon, so dragon_fist's authored
Element::Fire never reaches the Oil check or the weakness check. Surfaced by
writing the damage-breakdown tests. Left alone because it is a balance
decision, not a defect.
Install: download the zip, extract the whole folder, run shattered-crowns-windows.exe.
Keep the sc_engine folder next to the exe - the game will not start without it.