Releases: joewcarson/shattered-crowns-builds
Release list
v0.5.25-dev
Playtest build v0.5.25-dev.
A playtest-report pass. Almost every fix here is the same shape: a system
that worked correctly while the thing telling you about it did not.
A new battlefield: Sunscar Mesa
Traced from Final Fantasy Tactics' Zeklaus Desert (map #76) at its exact
10x18 footprint — the tallest board in the catalog, and the first traced
map without a compact sibling. Same provenance rules as the Highspire and
Gatestone pairs: the footprint is exact, the layout is traced from the
top-down and four isometric renders, and the per-tile heights are interpreted
from those renders rather than extracted. Not mirrored, for the same reason.
It's deliberately a third shape of height problem. Highspire is a town —
many short climbs, height as texture. Gatestone is an assault — one gated
climb. This is erosion: a single mesa stepping 0 to 5 in sandstone strata,
ringed by flats and cut clean through the middle by a wash at height 0 that
runs the full width of the board.
The wash is the whole idea, and it inverts the usual trade. It's the fastest
line across the map and the most exposed, because everything on both sides
looks down into it — and since it's sand it also costs double to walk. High
ground here is both safer and quicker. Getting out needs the ramps at
either end; the middle drops two steps at once, so a jumper leaves wherever it
likes and everyone else walks to a ramp.
It needed no new terrain vocabulary — sandstone is stone, so the existing
height ladder carries the strata and sand carries the floor. Supports 4v4,
5v5 and 8v8, making it only the third 8v8 battlefield we have. Sunscar
Wash (8x12, 2v2/3v3) is its compact sibling, keeping the mesa, the wash and
the long-axis approach at skirmish scale.
A second new battlefield: Gravewater Crypt
Traced from the Underground Cemetery of Limberry Castle (map #17) at 6 × 14
— by far the narrowest board in the catalog, and the first traced map that
isn't about climbing at all.
Highspire is a town of short climbs, Gatestone one gated ascent, Sunscar an
eroded massif. This is confinement. Six tiles wide, and two rows of flooded
graves pinch the middle down to a two-tile aisle. There is almost nowhere
to flank and nowhere to spread out, so the map keeps asking one question: hold
the aisle, or wade the graves at triple movement cost and spend your turn in
the open.
Height stays low — 0 to 3 — deliberately. A crypt is not a mountain, and the
pressure here comes from width instead. It exists to test what the height maps
can't ask: whether line-holding and area attacks are oppressive when nobody
can go around.
It's also the first traced map mirrored on both axes. The others are
asymmetric because real towns and forts are, but on a board this narrow an
asymmetry isn't character — it's a handicap, with no room to make up for a
worse side. That makes it the first of these fit for a ranked pool.
One caveat worth stating: the six-column width is read off the source map's
own axis labels, but the row count came from the image proportions rather than
legible labels. Fourteen is a measurement of a picture, not of the map.
Armies now face each other, whichever way they deploy
The Sunscar boards put the two sides on the north and south rims, marching
the length of the map at each other, rather than nine tiles apart across the
short axis with the board's whole length unused.
That surfaced a real bug. Both unit factories hardcoded Blue faces East, Red
faces West — correct only because every battlefield until now deployed along
the west and east columns. On a long-axis map both armies would have opened
the match showing each other their flank, and facing is not cosmetic: it
multiplies physical damage and scales evade.
Spawn facing is derived from the enemy's centre of mass now. It returns
exactly what the constant did on every pre-existing map — that's the point, a
no-op wherever it was already right and correct where it wasn't. Both
factories were changed together, offline and the authoritative online one,
because a fix applied to only one of them is the online/offline split this
project keeps paying for.
Dressed with a new desert theme, kept separate from the wasteland one (a
scorched caldera, tinted hot). Here the contrast is sand floor against rock
shelf rather than colour temperature, which matters because that's exactly the
distinction the map plays on. No cactus — neither of our asset packs has one.
Conviction decides it
Faith and Brave have scaled damage since the first commit, but gated exactly
three abilities — everything else in the blessing, affliction and talk catalogs
simply landed. An enemy's conviction had no say in whether they were slept,
charmed, stopped, blinded or struck undead. 49 abilities now roll for it.
It is the geometric mean of the two Faiths plus half the caster's MA, capped at
95 — nothing gated on belief is ever certain. Geometric rather than arithmetic
because Faith 0 and 100 are things the game can do to a unit (atheist and
faith pin them there at the cost of a whole ability), and an average lets a
Faith-90 caster still land on an Atheist'd target more than half the time,
refunding the turn that bought the immunity. MA is halved on a measurement, not
taste: gear is not optional, so the live range is 5–29 rather than the job
table's 5–18, and at full weight a geared caster hit the ceiling against a
devout target — the gate would have been inert for exactly the classes whose
kit it governs.
Item charges
The Apothecary's whole kit and the Ninja's thrown stock cost zero MP because
they are items — which left them costing nothing at all. Three each per
battle. Counted against the same tally the results board has kept since
v0.5.19, so charges inherit the whiff rule for free: a potion thrown at open
ground was never thrown, and a shuriken that misses is gone.
Durations that were only ever decoration
status_duration was threaded through five of the twenty-five status-applying
arms. The other twenty passed None — so a duration authored on Guard or
Protect compiled, registered, exported, rendered on a codex page and did
nothing. All twenty honour it now. Found by putting the registry in front of
Joe as a spreadsheet and watching which column he typed in.
Spells stopped caring where you stand
Facing has never multiplied magic damage, and v0.5.18 stopped the battle log
claiming it did — but the accuracy half survived. magic_hit_chance went on
shaving the target's evade by which way they faced, so flanking a caster made
every spell in the game measurably easier to land: 88% head-on against a ninja,
100% from behind. A spell is aimed at a tile, not thrown at somebody's back.
Facing keeps its full weight on the physical side, where it multiplies damage
and strips evade, and Backstab still reads it once more on top.
Found beside it: the damage breakdown pushed the height term for magic too,
so a mage casting down a slope read "height ×1.25" in the log and the hover box
for a bonus the spell cannot collect. Same lie, same list, one line apart. It
hid because neutral terms are filtered out, so it only ever appeared on maps
built to have slopes.
Odin rides in as a skeleton warrior
odin joins lich as a real model rather than a painted plate. What a summon
does and what it holds now live in a table keyed by summon id, deliberately
not derived: nothing on an ability row distinguishes a swordsman from a caster
— both rows read MagicDamage, Dark, rare — so Odin cleaves and the Lich casts
because of what they are. Verified before the animation was written: both
KayKit skeletons ship weaponless, so a warrior given a two-handed chop and no
sword swings its bare fists.
Six eidolons arrive as themselves
Summon art has been a built-and-empty seam since v0.5.22 — every one of the
sixteen summons turned up as the same generic bloom, so Ifrit and Carbuncle
were the same apparition in different colours. Ifrit, Shiva, Bahamut,
Leviathan, Odin and Carbuncle now have painted plates, in the house style of
the job portraits. The other ten still take the bloom, which is the table
working as designed rather than a job half finished: a missing entry costs one
illustration, never the spell.
Each plate flares mid-attack, not at rest — a creature posing is a creature
that turned up and did nothing. The attack is body language only: no breath
weapon, no beam, no glow off the hands, because the engine already draws the
magic and tints it by the ability's element. A plate drawing its own would
double it up and fix in paint a colour the palette is entitled to change.
The damage forecast was swinging an empty hand
Hovering a target quoted a swing thrown with no weapon at all — no weapon
power, no class stat, no Brave sharpening. It misleads worst where it matters
most, because a weapon class chooses which stat the swing reads: a summoner
with PA 4 holding a staff swings on its MA of 26, and the forecast said 4. In
one playtest a Rending Reel previewed at about 21, landed for 159, and
killed its caster's own Chronomancer through the gap.
The number on the hover is now the number the hit deals. The forecast also
never applied a target's innate elemental weakness, which the resolver has
always applied on the very next line.
Petrify is as good as dead until somebody breaks it
It used to be a two-turn stun wearing a dramatic name — a unit petrified on
turn 17 was swinging again on turn 21. Now it takes the unit out of the fight
entirely: no turns, and it doesn't count toward its team still standing, so
petrifying the last enemy wins. It also doesn't wear off. The way back is a
Cleanse — Esuna, Remedy, any of them — which is what makes the harshest status
in the game fair.
A charge costs the turns it takes
Committing a charge used to cost nothing but the action slot: the caster kept
taking full tur...
v0.5.24-dev
A playtest-report pass. Almost every fix here is the same shape: a system
that worked correctly while the thing telling you about it did not.
Conviction decides it
Faith and Brave have scaled damage since the first commit, but gated exactly
three abilities — everything else in the blessing, affliction and talk catalogs
simply landed. An enemy's conviction had no say in whether they were slept,
charmed, stopped, blinded or struck undead. 49 abilities now roll for it.
It is the geometric mean of the two Faiths plus half the caster's MA, capped at
95 — nothing gated on belief is ever certain. Geometric rather than arithmetic
because Faith 0 and 100 are things the game can do to a unit (atheist and
faith pin them there at the cost of a whole ability), and an average lets a
Faith-90 caster still land on an Atheist'd target more than half the time,
refunding the turn that bought the immunity. MA is halved on a measurement, not
taste: gear is not optional, so the live range is 5–29 rather than the job
table's 5–18, and at full weight a geared caster hit the ceiling against a
devout target — the gate would have been inert for exactly the classes whose
kit it governs.
Item charges
The Apothecary's whole kit and the Ninja's thrown stock cost zero MP because
they are items — which left them costing nothing at all. Three each per
battle. Counted against the same tally the results board has kept since
v0.5.19, so charges inherit the whiff rule for free: a potion thrown at open
ground was never thrown, and a shuriken that misses is gone.
Durations that were only ever decoration
status_duration was threaded through five of the twenty-five status-applying
arms. The other twenty passed None — so a duration authored on Guard or
Protect compiled, registered, exported, rendered on a codex page and did
nothing. All twenty honour it now. Found by putting the registry in front of
Joe as a spreadsheet and watching which column he typed in.
Odin rides in as a skeleton warrior
odin joins lich as a real model rather than a painted plate. What a summon
does and what it holds now live in a table keyed by summon id, deliberately
not derived: nothing on an ability row distinguishes a swordsman from a caster
— both rows read MagicDamage, Dark, rare — so Odin cleaves and the Lich casts
because of what they are. Verified before the animation was written: both
KayKit skeletons ship weaponless, so a warrior given a two-handed chop and no
sword swings its bare fists.
Six eidolons arrive as themselves
Summon art has been a built-and-empty seam since v0.5.22 — every one of the
sixteen summons turned up as the same generic bloom, so Ifrit and Carbuncle
were the same apparition in different colours. Ifrit, Shiva, Bahamut,
Leviathan, Odin and Carbuncle now have painted plates, in the house style of
the job portraits. The other ten still take the bloom, which is the table
working as designed rather than a job half finished: a missing entry costs one
illustration, never the spell.
Each plate flares mid-attack, not at rest — a creature posing is a creature
that turned up and did nothing. The attack is body language only: no breath
weapon, no beam, no glow off the hands, because the engine already draws the
magic and tints it by the ability's element. A plate drawing its own would
double it up and fix in paint a colour the palette is entitled to change.
The damage forecast was swinging an empty hand
Hovering a target quoted a swing thrown with no weapon at all — no weapon
power, no class stat, no Brave sharpening. It misleads worst where it matters
most, because a weapon class chooses which stat the swing reads: a summoner
with PA 4 holding a staff swings on its MA of 26, and the forecast said 4. In
one playtest a Rending Reel previewed at about 21, landed for 159, and
killed its caster's own Chronomancer through the gap.
The number on the hover is now the number the hit deals. The forecast also
never applied a target's innate elemental weakness, which the resolver has
always applied on the very next line.
Petrify is as good as dead until somebody breaks it
It used to be a two-turn stun wearing a dramatic name — a unit petrified on
turn 17 was swinging again on turn 21. Now it takes the unit out of the fight
entirely: no turns, and it doesn't count toward its team still standing, so
petrifying the last enemy wins. It also doesn't wear off. The way back is a
Cleanse — Esuna, Remedy, any of them — which is what makes the harshest status
in the game fair.
A charge costs the turns it takes
Committing a charge used to cost nothing but the action slot: the caster kept
taking full turns while the spell wound up, which made charging strictly better
than casting instantly — same effect, bigger numbers, and you still got to
play. A charging unit is now simply out of the turn order until its cast lands,
exactly as an airborne dragoon already was.
And a dragoon comes down where it went up. Jump spends the action slot and
left the move slot open, so a samurai could leap, stroll three tiles, and
"crash down" wherever it had walked to. You can't walk in mid-air any more.
Aiming at open ground is your mistake
A whiff no longer refunds its MP. The refund was written for a starvation bug
whose real defect was the silence around it, and that was fixed two releases
ago — what remained was the game paying for misclicks. Whiffs now announce
themselves with a popup and a battle-log line, on instant and charged casts
alike. Pointing at something an ability genuinely can't affect is still a
refusal, and still costs nothing.
The AI stops blowing up its own team
One guard was wrong in both directions. It only ever asked whether the caster
was standing too close, so allies were never considered — a Geomancer dropped a
Sinkhole two squares away, killed its own thief, wounded its own Orator and hit
itself for 51, for zero damage to the other side. That one cast decided the
match. The same guard also vetoed any enemy standing near the caster, and the
AI only ever weighed two targets to begin with, so casters routinely found
nothing to do and shuffled a tile instead of casting.
Bots now read the true blast footprint — the same one the resolver uses — and
weigh every enemy they can see.
Better gear, more often
Ultimate gear now drops at 5% instead of 1%, and rare at 10%, taken out of
common. A one-in-a-hundred ultimate meant whole evenings without ever seeing
the top of the ladder; gear is rolled fresh every battle and never owned, so a
generous top end costs no permanent power — only a better story.
A living unit lying on the ground
The client took a hit's full damage off HP, including the part a barrier had
already eaten. A Bard on 63 behind a 25-point barrier took a 70-damage hit,
dropped to zero client-side, played its death animation and hung a skull over
its head — while the engine correctly had it alive on 18. The next sync
restored the HP, so it went on taking turns while lying on the floor.
The battle log now reads "hits Bard for 70 (25 absorbed, 45 to HP)", the client
no longer decides who is dead, and a unit that is alive is stood back up
whatever else goes wrong.
The results board
Now takes three quarters of the screen instead of a fixed panel a third of its
height, with each army on its own full-width row. A 3v3 with every unit's kit,
gear and ability breakdown no longer scrolls at all.
Hover tooltips on that board were also silently broken: an apostrophe anywhere
in an item or ability description — "restore an ally's HP", "a bard's relic" —
broke the tag it lived in, printing raw markup across the row and, worse,
swallowing whole skills off the line so a unit's kit simply looked shorter than
it was. Around two dozen descriptions carry one.
The live scoreboard (Tab) gained the HEAL column the results board has had
since v0.5.15.
v0.5.23-dev
Playtest build v0.5.23-dev.
Every battlefield but one now stands on real ground instead of a tinted box.
Real ground tiles
Added KayKit's Medieval Builder pack (CC0, free, 98 models) — the first set
we own that ships ground tiles on a square grid. Until now every theme
either borrowed dungeon flagstone or had no ground art at all.
The four traced maps get it first: stone, grass, sand and water each become
real art, so Highspire's canals are actual water and its streets actual ground,
in place of the borrowed dungeon look v0.5.22 shipped.
Every scale was measured rather than guessed, the same way the existing packs
were. Each square tile is exactly 2.000 across, so it lands on our tile size at
0.5, and wall_straight is exactly one tile wide so a wall run meets flush.
One genuinely new mechanism came out of it. A surface is not always a flat
mat. The dungeon's flagstone is 0.15 thick and sits on its own centre, so it
lands correctly at the tile top; the Builder tiles are blocks that extend a
full unit upward from their origin, and laid at the same point would float a
whole height step above the board. Themes can now declare a surface_drop,
which sinks the block's body into the tinted column underneath — which is what
that column was always for. It defaults to zero, so no existing theme moved.
The woodland maps finally have a floor
The forest theme went its whole life with no ground art: the Forest Nature
free tier is props-only (modular ground is an EXTRA-tier item), so the tinted
box literally was the forest floor and scatter carried the entire look. All
six woodland battlefields — Sacred Grove, Forest Glade, Open Plains, Highland
Pass, Twin Peaks and River Crossing — now stand on real earth, rock, sand and
water.
The Forest props are untouched. They're what makes the theme a forest rather
than a field, and they're finer than anything the Builder set has.
Scorched Crater is dressed; Frozen Expanse deliberately isn't
Scorched Crater had no theme at all. A new wasteland theme covers the four
fifths of that map which is neither magma nor snow — caldera rim, ash flats,
stone. Lava deliberately gets no surface: no molten tile exists in any pack we
own, the tinted box already reads as a glowing pit, and dressing it with rock
would put a crust over the one hazard the map is built around.
That leaves Frozen Expanse as the only undressed battlefield, on purpose.
Nothing we own has a snow tile, and half-dressing it — rock footholds over
tinted ice — would read worse than the consistent bare board it is today.
KayKit's snow biome is a Patreon-tier extra for this same pack, which is the
fix whenever someone wants it.
Skeletons are drop-in compatible (not yet wired)
KayKit's free Skeletons pack — Warrior, Rogue, Mage, Minion — uses the same
Rig_Medium, 23 bones as our Adventurers, with handslot.l and
handslot.r present. Merging our existing animation libraries onto one
resolves 25 of 25 tracks, so they inherit the whole 65-clip library and the
weapon-mounting system with no new pipeline at all.
Downloaded and verified but deliberately not wired to anything: which units
should be skeletons is a design decision, not an art one.
Rejected after inspection
The Medieval Hexagon pack. Its tiles are hex-only, and its town buildings —
church, tavern, blacksmith — ship only in four team colours, which would read
as team-coloured buildings in a neutral town; its "neutral" folder is bridges,
fences and scaffolding. Its buildings also duplicate the Builder pack's. It was
downloaded, examined and removed rather than left sitting unused in the tree.
Also worth recording: the Forest Nature free tier is props only — 105
models, every one a rock, bush, tree or grass tuft, with no floors, walls or
buildings. Verified against the distribution zip. That's why it could never
have dressed a town by itself.
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.
v0.5.22-dev
Four new battlefields traced from FFT maps, summons that arrive instead of being thrown, and every skill and item now states what it does.
Four new battlefields, built to be a testing ground for the height rules —
two recreations of real Final Fantasy Tactics maps, each with a compact
sibling.
The Highspire pair
highspire_canals (10x15, 4v4 + 5v5) recreates Final Fantasy Tactics' Magic
City Gariland — ffhacktics map #22 — and highspire_quarter (8x10, 2v2 + 3v3)
is its compact sibling, the same relationship Potter's Field has to Cursed
Graveyard.
What is exact and what is not, because it decides how much to trust the
numbers. The 10x15 footprint is exact, read off the source map's own axis
labels. The feature layout — canal system, house blocks, cobbled streets,
grass fringe, the town stepping up toward the north-west — is traced from the
top-down render and the four isometric views. The per-tile heights are
interpreted from those renders, not extracted from the game's terrain table,
which is not published anywhere reachable. Every elevation is faithful to the
silhouette rather than byte-exact, and is expected to be tuned.
They are deliberately not mirrored. Every other showpiece battlefield in
the catalog is symmetric so neither side inherits an advantage; a real town is
not. These are a height testbed first and a fair fight second — keep them out
of ranked pools until they have been played.
Why they exist
Roofs are authored as walkable staircases, so the same rooftop is four
steps for a Jump-1 unit and two for a Jump-2 unit. Roof edges then drop three
or four at once. Getting down is a different question from getting up, and that
asymmetry is the thing the height rules most need exercising against.
The stone glyph ladder grew to cover it: s/S/t/T/k are heights 1
through 5. Stone at new heights rather than a new terrain type on purpose — a
tiled roof is masonry, and the client mirrors terrain names but takes height
as a plain integer, so new heights cost zero client edits where a new terrain
would add another hand-maintained mirror to drift.
To read a board without booting the game:
cargo test traced_map_height_table -- --nocapture
It prints both maps as numeric elevation grids, north-up in the same
orientation as the source renders. A glyph is compact to author and useless to
check a movement calculation against.
A test that could be satisfied by flattening the map
traced_maps_are_a_walkable_height_ladder asserts every level 0–5 is
present and every passable tile is reachable by a Jump-1 unit — no roof is an
island. Verified by stranding the tower peak, which fails it by name and
coordinate.
But a completely flat map passes that test perfectly while destroying the only
reason these maps exist. So it ships with a counterpart,
traced_maps_have_ledges_that_reward_a_bigger_jump, pinning the steep ledges the
first test would happily let you delete (25 live, floor of 8).
The reachability flood is a local implementation, deliberately not
movement::get_move_range: it asserts the map is authored correctly, not that
the mover works, and sharing one implementation would let either bug hide the
other.
The Gatestone pair
gatestone_bastion (10x13, 4v4 + 5v5) traces Zaland Fort City — ffhacktics
map #35 — with gatestone_ward (8x10, 2v2 + 3v3) as its compact sibling. Same
provenance rules as above: exact footprint, traced features, interpreted
heights, not mirrored.
It is a different shape of height problem from Highspire, which is the
reason to have both. Highspire is a town: staircases everywhere, many short
climbs, height as texture. Gatestone is an assault — one tiered keep climbing
0 to 5 straight up the middle, a curtain wall across the waist with a single
gate, and a low open approach to the south. The gate is the bottom of the
staircase, so the chokepoint and the climb are the same decision.
Both armies start in the low south; the spawn columns run only the bottom six
rows. The high ground is north of both of them — contested rather than
owned, which keeps it a fight instead of a siege.
All four maps are now walked by one list, tests::TRACED_MAPS, so a fifth is
covered by adding it there and nowhere else. traced_maps_keep_their_traced_footprints
additionally pins each pair's signature feature — the canal for Highspire, the
curtain wall for Gatestone — because a map authored out of its own defining
feature is a different map.
A town theme, built out of two packs
All four traced maps are dressed with a new town theme. It spans two asset
packs on purpose, because neither can dress a settlement alone: Dungeon
supplies the only masonry we own — paved tops for streets and roof terraces,
and real walls — while Forest Nature supplies the only vegetation, for the
grassy banks that ring both source maps in their own renders.
Mixing packs is safe because scatter and structure entries take a per-entry
scale and only fall back to the theme's. The theme scale is the Dungeon one,
which is what the floors and walls need, and every Forest prop carries its own.
The two packs are authored at wildly different unit sizes — a 4-unit grid
versus roughly 1 unit to the metre — so one shared number could never have
served both.
Grass deliberately gets no surface, the same call the forest theme makes:
paving the banks would erase the one part of these maps that is supposed to
read as outdoors. Stone scatter runs about 13% against the dungeon's 37% and
drops the torches and banners — a street should read as a street, and a wall
sconce outdoors reads as a mistake. Water is daylight blue rather than the
dungeon's near-black, which read as a pit rather than a canal you are meant to
wade.
This is not yet a real town set: there are no tiled roofs, no
half-timbering, no cobbles, no market clutter. The roofs are flagstone terraces
because flagstone is what we own. Worth revisiting when a village pack lands —
the swap stays a per-map edit in MAP_THEMES plus the one theme entry.
(For the record, since it came up: the Forest Nature FREE tier is props
only — 105 models, every one a rock, bush, tree or grass tuft, with no
floors, walls or buildings. Verified against the distribution zip. That is why
it can't dress a town by itself, and why the forest theme has no surfaces
either.)
The game now says what your skills and gear actually do
The roll ceremony named the gear it dealt you. The results board named both the
kit and the loadout. Neither said what any of it was for — you watched an
ultimate weapon turn over with no way to know why it beat the one it replaced.
On the roll screen the stats sit beside the name. The card went from 240 to
320px, which was a measurement rather than a guess: across all 241 items a stat
line runs 11 characters at the median and 41 at the worst ("4 WP, +22 MP, +6
MA, +3 MDEF, +9 EV, Holy"), so 240 fit roughly the shorter half. At 320 the
great majority land on one line and the handful of outliers wrap instead of
truncating — no per-item decision, and no item quietly losing its last stat.
On the results board every chip hovers. Skills, gear and the ability-usage
line each carry a tooltip with the mechanics, the base hit chance and the
prose. The board is 440px with up to eight units a side and three dense lines
each, so printing mechanics inline for six skills and five items would bury the
stats the board exists to show. Hover costs no layout at all.
Weapons now state their base hit % — 80% for an axe, 100% for a dagger.
Accuracy has been live since v0.5.10 and had never been shown anywhere, so a
fifth of an axe's swings went missing with nothing to explain why. It appears
on the roll card, in the hover, and on the Codex's weapon-class pane.
One sentence, one author
EquipmentDef::stat_mods_label() has always existed in the engine — the
Discord roll bot renders it verbatim — and the Codex derived the same sentence
a second time in GDScript. Two more screens wanted it, which would have made
four authors for one summary. It now ships on the item itself and every screen
reads it.
A guard for the numbers being printed
smoke_phase2 compared ids, names, rarity and JP cost. It never compared
mp_cost, ct_cost, power, range, aoe or accuracy — precisely the numbers these
screens now print, out of a mirror that is maintained by hand and has drifted
in six documented ways.
It found a real bug on its first run. Chakra has cost 10 MP since v0.5.10 and
the mirror still said 0, so for eleven releases the client showed it as free
and let a monk with no MP select it, whereupon the engine refused. That is the
"Chakra is broken" playtest report v0.5.15 built CommandRejected to explain —
with the half that was actually lying still sitting there.
The engine serialises AbilityType as its Rust variant name while the client
keeps its own vocabulary, so comparing them field-for-field would need a
twenty-row hand-kept mapping — the disease rather than the cure. What is
guarded instead is the single fact the displayed accuracy depends on: the set
of abilities the engine calls MagicDamage must equal the set the mirror calls
"magic". Verified by diverging one (48 vs 47).
Two bugs the tests caught by being reverted
weapon_type is an Option engine-side, so String(null) did not return ""
— it aborted the call and yielded a coerced 0, which every helm would have
rendered as "0% acc".
And the accuracy test was vacuous: it searched every tooltip for "95%" and
passed with the ability-accuracy path stubbed out, because the Kingsworn gear
hover states 95% by its own separate route. It now asserts against ability
hovers only. Same lesson an_archer_fires_from_bow_range recorded — to test an
inherited value, strip out every source that isn't the inheritance.
Summons arrive, and an ability's numbers reach its effect
The plan was to hand-author the nineteen ultimates and...
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.
v0.5.20-dev
Playtest build v0.5.20-dev.
Playtest drop — covers v0.5.18, v0.5.19 and v0.5.20. The last build testers
saw was v0.5.17, so this is three releases' worth. Full per-release detail is in
the three sections below this one; here is what you will actually notice.
The game has a typeface
Every screen was on Godot's built-in sans until now. Three tiers: Cinzel
Decorative for the wordmark and the VICTORY/DEFEAT banner (two places only),
Marcellus SC for page headings and the main menu, Fira Sans for
everything you read and every number. Body text also went 12px → 14px.
Combat explains itself now
- Hovering a target shows the maths.
zodiac ×1.25 · brave ×0.65 · their DEF ×0.67, green above 1.0 and red below. Every zodiac roll used to fire
silently — you could play a whole match without learning the wheel exists. - Refused commands say why. Out of range now reports the distance and your
reach instead of the turn quietly evaporating. - Brave and Faith are visible. A
+5 BRVpopup, a live unit card, and a
log line that prints where the number landed. - The ⌛ charge, ☠ death clock and ✦ crystal markers are real art rather than
text placeholders.
Things that were quietly wrong
- Brave, Faith and zodiac were never rolled in offline practice — all three
were derived from the unit's ID, so every skirmish dealt Brave 60–65 in ID
order, on every map, forever. They roll across the designed 45–90 band now. - The battle log claimed a ×1.5 backstab bonus on spells. Magic ignores
facing (FFT's rule) and always has; only the narration lied. Facing still
makes a spell more likely to land. - +5 Brave was applied but never shown — the unit card froze at its roster
number all match, and a blank red badge appeared that nothing could remove. - A unit killed by its own poison at turn start could be handed a turn on its
corpse.
Rules changes worth knowing before you play
- Ability range is enforced by the engine. It never was — the only thing
stopping a cast at the far corner of the board was the client's tile
highlight. Online this was exploitable. - Talk skills have a per-match budget of ±15 (three casts). Cheer used to
stack without limit to Brave 100, which multiplies every physical hit for the
rest of the battle. The budget is net, so an enemy Intimidate re-opens it. - Fast-forward (
For ▶▶) in offline matches auto-waits turns with nothing
in them and disengages the moment one of your units has something to do. - The results board gained a per-ability breakdown and a HEAL column, and
lobby chat gained a mute (click a name).
Typography in full
The client has never had a font. Every screen was on Godot's built-in sans —
the Cinzel decision from the JSX prototype had simply never reached the Godot
build. It has one now, in three tiers.
| Tier | Face | Where |
|---|---|---|
| Title | Cinzel Decorative | The main-menu wordmark and the VICTORY/DEFEAT banner. Two places, deliberately. |
| Display | Marcellus SC | Page headings, the main-menu column, anything titular. |
| Body | Fira Sans | Everything you actually read, and every number. |
The tiers are sized, not decorative. A histogram of the client's ~200 text
sites puts 173 of them between 9 and 14px — battle log, stat bars, badges,
tooltips, chat. That is the real typography of this game, which is why the
base face is the one that holds up at 10px rather than the one that looks best
on a title card. Marcellus is single-weight inscriptional and Cinzel Decorative
is frankly ornamental; both are lovely at 22px and neither can carry a battle
log.
Cinzel Decorative is rationed to two sites because a decorative face used a
third time stops being an event. smoke_fonts asserts the count is exactly
two — that is the design decision here most likely to erode quietly.
Body text went 12px → 14px across all 46 sites that specified it, once the
real face was in and Joe could see it: the battle log, the lobby chat, the
Codex rows and most of the profile and store copy. The 9/10/11px sites are
untouched. Seven files mix 12 and 13, so this could have inverted a hierarchy —
those pairs turned out to be unrelated elements (a price against a loading
message, a placeholder against a status line) rather than heading/subheading
relationships.
How it's wired
One line in project.godot registers a default Theme, and that covers all 173
small-text sites without touching one of them. The titular tier is two
theme type variations (ScTitle, ScDisplay) that carry a font and nothing
else, so applying one is a single line per site and can never resize a screen
that was already tuned.
The theme is generated by tools/build_theme.gd rather than hand-written.
A Theme has per-type font keys that are easy to get subtly wrong, and a wrong
key fails silently — the control just keeps the default face. Generating it
means Godot validates the resource on save. RichTextLabel needs four of its
own font keys and does not fall back to default_font; miss those and the
battle log, chat, Codex and results-board chips stay on the default while
everything around them changes.
Label3D doesn't read a Theme at all, so the six floating labels on the board
— damage numbers, the death-clock count, badge counts, the +N overflow — get
their face applied by hand. The smoke counts Label3D.new() against
apply_to_label_3d in both files so a new one can't be added without a face.
Guard net
tools/smoke_fonts.tscn (25 checks). A font that fails to load does not error
in Godot — the control silently keeps the built-in default and everything still
"works", the same failure shape as the v0.5.17 SVG glyphs that imported cleanly
and rendered nothing. So it checks that the three roles resolve to three
different families by name, that the theme carries each one, that the project
registers the theme, and that a real Label instantiated at runtime actually
inherits it.
Ten families were evaluated on a rendered specimen against the game's own
palette at real HUD sizes; the seven that lost are deleted. The three that
remain are SIL Open Font License, with a licence file each in
client/assets/fonts/ — 1.5 MB total.
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.
v0.5.17-dev
Nine new statuses, and art for all thirty-eight.
Two releases in one build. v0.5.16 finished Final Fantasy Tactics' status list
in the engine; v0.5.17 gave all 38 statuses art. Neither shipped on its own, so
they arrive together.
Nine new statuses
Each is applied by a real ability — a registry entry nothing can produce is
just a tooltip.
| Status | What it does | Cast by |
|---|---|---|
| Oil | Fire hits for double. Burns off the moment fire connects. | Oil (Arcanist) |
| Undead | Healing harms — including Regen and a friendly Elixir. | Corruption (Cursebrand) |
| Doom | A three-turn countdown to a guaranteed KO. | Death Knell (Cursebrand) |
| Invisible | Your attacks ignore evade; enemy AI cannot see you. | Vanish (Chronomancer) |
| Faith | Faith pinned to 100 — magic cast and received at full force. | Conviction (Orator) |
| Atheist | Faith pinned to 0 — casts nothing, and nothing touches them. | Doubt (Orator) |
| Toad | Reduced to the basic attack. Sitting duck while it lasts. | Toad (Arcanist) |
| Defending | Evasion doubled until your next turn. | Defend (universal) |
| Charging | Winding up a spell: no evade, +50% physical taken. | the engine, on any queued cast |
Two more are derived, never stored: Critical (HP under 20%) and
Chicken (Brave under 10, which makes you a free hit — FFT's rule).
Defend, and why the AI won't spam it
Every unit now fields Defend alongside Attack: free, instant, self-only,
evasion doubled until your next turn.
The AI treats it as a last resort rather than an option. A range-0 self-buff is
always in range and always succeeds, so ranking it normally made both armies
stand on their spawn tiles guarding at each other forever.
Interrupting a caster finally means something
A unit mid-charge takes half again as much physical damage and cannot dodge.
Sleep does the same — without it, putting an enemy to sleep for two turns and
waking them with your first hit was close to a wasted turn.
Fixed — Blind was a placeholder for five releases
Blind shipped as a −50% Physical Attack cut, standing in for a hit/miss
system that didn't exist yet. The accuracy layer landed in v0.5.10 and nobody
came back for this.
It was wrong in both directions: it weakened ability damage that FFT's
Blindness never touches, and left the thing it is named for — swinging at
where you think someone is — untouched. A blinded ninja landed every
strike, just softly. It now halves accuracy and leaves strength alone.
Also fixed: two damage sites silently discarded any status multiplier above
1.0, which would have thrown away all three of the new "helpless target"
effects; and the client's status table had drifted from the engine for six
releases — it was the one registry the drift guard couldn't see.
All 38 statuses now have icons
They used to render as one- and two-character text labels under the HP bar,
which at board zoom was an unreadable smear that couldn't tell good news from
bad.
Now a framed pictogram each, tinted by category — green buffs, red debuffs,
slate action states. The row caps at three badges plus a +N counter,
debuffs first, because a Mystic can put four afflictions on one knight.
Six motion behaviours, applied per status rather than animated frame by frame.
Poison beats on the engine's own tick, so the pulse lands with the damage
number. Petrify and Stop deliberately don't move — being frozen out of the
clock is the information. Only Doom prints its remaining turns.
Eight statuses paint the character itself
Petrify and Undead flatten the body to one colour and Petrify stops the idle
outright; Poison, Oil, Berserk and Toad glow; Invisible fades. You should know
a unit is poisoned from across the board without reading anything.
Toad squashes rather than turning into a toad — there is no toad model yet.
Battlefield effects
Buffs bloom outward, debuffs contract inward, damage-over-time ticks puff, and
cures are deliberately silent. Oil going up is brighter than the fire that
caused it: it's a two-turn setup that pays off in one moment, and if the payoff
looks like an ordinary Fire then nobody learns the combination.
Worth knowing
- Badge size and glow strength are best guesses. They were tuned without
ever seeing the game run, so say if a four-badge row crowds the HP bar or a
poison glow buries a job's colours. - Statuses keep our 2–3 turn durations rather than FFT's "until end of
battle", and everything here is curable — a status with no counter-play is
a dice roll, not a tactic. - Healing an Undead unit never actually kills it; HP floors at 1.
- Not built, deliberately: Vampire and true Berserk (both need a command
override the engine doesn't have), Performing, Traitor, and Treasure.
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.
v0.5.14-dev
Playtest build v0.5.14-dev.
The dragoon's Jump stops being a fixed 4-tile leap and becomes a rolled
one — the same "you're dealt a fresh hand each battle, and JP buys the ODDS,
not a guarantee" mechanic the skill and gear rolls already run on.
Added — the Jump range is rolled each battle (jump_roll)
Was: every dragoon's Jump reached a flat 4 tiles, full range unlocked from the
start. Now the basic Jump's range is dealt at match start from the tiers the
account owns:
| Tier | Range | Native weight |
|---|---|---|
| Jump-3 | 3 tiles | 45% — the always-unlocked floor |
| Jump-4 | 4 tiles | 35% |
| Jump-5 | 5 tiles | 19% |
| Jump-6 | 6 tiles | 1% |
Buying a tier with JP adds it to the pool at its native weight; every tier
you have NOT bought folds its weight down into Jump-3. Own only Jump-5 and
your odds are Jump-5 19% / Jump-3 81%. Own everything and you get the full
45/35/19/1. So spending JP shifts you toward a longer leap — a Jump-6 is a 1%
gold-card thrill, never a purchase you can count on. It's the exact same
pool-widening logic as the equipment roll and the Fist-odds fix.
The tiers are learnable movement-registry abilities (Jump 4 / Jump 5 / Jump 6,
200 / 400 / 800 JP), so they're permanent account unlocks like any other JP
purchase and never clutter the action menu or the ability roll. The rolled
result rides on each unit and shows on the pre-battle reveal card, rarity-
tinted — a Jump-6 reads gold, like an ultimate skill or weapon.
Only the basic Jump rolls. Sky Render (Rare, AoE) keeps its 4-tile range
and Dragon Dive (Ultimate) its 5 — they're distinct abilities, not range
variants.
This replaces the linear "+1/+2/+3 permanent upgrade" version that was in
progress; the rolled model is more on-brand and keeps Jump one ability.
Fixed — a brittle two-client turn-order test (pre-existing, v0.5.13)
two_clients_auth_and_receive_turn_start asserted that after a unit Waits, the
next turn belongs to a different unit. Since v0.5.13's dt-jump scheduler a
faster unit legitimately re-crosses the CT threshold and takes another turn —
in the knight (sp 7) vs mage (sp 6) demo the knight often acts twice — so the
test would stall waiting for a turn it wrongly assumed. It now proves the Wait
advanced the match by watching for the unit's TurnEnd event (the direct
signal), not by assuming who acts next. The engine was always correct; the
test's assumption wasn't.
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.
v0.5.12-dev
Playtest build v0.5.12-dev.
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_hpcorrectly 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.
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.
v0.5.11-dev
Playtest build v0.5.11-dev.
A two-player playtest pass. One of these is the worst combat bug the project
has shipped, and it had been live the entire time.
Fixed — Front and Back were swapped on every attack in the game (1e11909)
Reported as "Iron Fist walked right up to White Seraph and it said from
BEHIND ×1.5 — but it was FRONT."
get_facing computed its direction as where the blow travels in the top
half and read that same value as where the attacker stands in the bottom
half. The two halves disagreed, so Front and Back were transposed on every
single hit. Both armies spawn facing each other and walk forward, so the
common case — a head-on approach — collected the ×1.5 backstab bonus, while
an actual flank round the back got ×1.0. Every damage number in every match
to date was computed against an inverted facing.
It survived because every facing test in the tree hands FacingResult in
ready-made; the geometry that produces one had no test at all. It does now,
covering all four headings, range independence and the diagonal tie-break.
Added — attacks can miss (d95835d)
Reported as "all attacks, magic AND physical, seem to hit 100% of the time.
in FFT there is a miss&evade calculation."
Correct, and worse than it looked: UnitStats.evade existed, shields granted
it, the Codex rendered it, and AbilityForecast even carried a hit_pct
field — and nothing read any of it. Class evade was 0 on every unit
because JobDef had no evade at all. Third instance of this project's
signature failure: registry data shipped ahead of the code that reads it,
with the UI rendering it as though it worked.
hit% = base accuracy − (target evade × facing factor)
- All 18 jobs get class evade, physical and magic separately. Light fast
classes dodge steel (ninja 30 / thief 25 / dancer 22); robes dodge spells
(mystic and chronomancer 20). Plate is the reverse — the knight is the
hardest class in the game to miss. - Weapon classes get base accuracy. Guns 100: a weapon that ignores its
wielder's stats ignores their aim too. Axe/flail/bag 80 — the same "wild"
group whose damage already swings 0.35–1.65×. - Facing decides how much evade applies: full from the front, HALF from
the side, ZERO from behind. Flanking beats dodging. This is also why the
facing inversion above had to be fixed first, or the whole accuracy system
would have shipped backwards on day one. - Concentrate now makes its wielder unmissable — finally a reason to
spend the Support slot on it. - Misses say so, with the number: "Life Leech MISSES Bard (55% to hit)."
- The hover forecast fills in its real hit% from the same functions the
resolver rolls against, so the box and the outcome cannot disagree.
Measured face-on hit rates now run 55% (thief) to 84% (squire). Two things
measurement caught that guessing would not have: gear evade was doubling the
class spread it was meant to decorate (a default-kit thief sat at 57 total,
missed two swings in three), and those item numbers had been authored while
the stat was inert — balance-tested against a tooltip and nothing else.
Changed — Life Leech is a gamble now, not a certainty
It was the highest power in the instant tier and a full self-heal and
zero charge time, where every comparable nuke pays 2–3 CT. A mystic finished a
logged match at 95/95 having deleted a bard with it. Rather than shave the
numbers, it keeps the whole payoff and lands ~60% of the time.
Fixed — Confuse breaks on damage from any source
Joe's rule. The flag covers every ability, counter and drain hit for free —
but the poison DOT writes HP by hand and never ran the break check, so it was
also the one hit in the game that could never wake a sleeper.
Fixed — Chakra, and the reason it looked broken
Chakra restored HP only, so at full HP it visibly did nothing. It now restores
MP as well, scaled off max MP rather than MA (the monk's dump stat).
But the actual cause of "chakra has no effect" was worse: abilities that
hit nothing still charged full MP, silently. An earth_slash into empty
ground and a Revive aimed at an enemy corpse ate 22 of the monk's 35 MP —
after which Chakra was refused for insufficient MP and emitted no events at
all, which is indistinguishable from a broken ability. Whiffs now refund.
Charged casts still pay up front: committing a charge is the commitment.
Fixed — tiles that lit up and then refused the click
The move-range highlight never stripped body-blocked tiles the way the engine
does, so ally-occupied and corpse-occupied squares were painted as reachable
and then silently rejected. Lava also cost 2 in the engine and 1 in the
client.
Added — the Dragoon actually jumps (ff7048d)
UnitTookFlight / UnitLanded had only ever been narrated. The dragoon now
leaves the screen on takeoff and comes down on the landing tile with weight
behind it — driven off the engine's landing coordinate, not the aimed one,
since the engine diverts to a free neighbour when the target square is taken.
Added — gear on the post-battle board
Each unit's rolled equipment on its own line under the rolled skills, same
rarity tinting, through both the game-over screen and Match History.
Fixed — online matches would have had no evade at all
match_server.rs holds a fourth inline unit-stat builder — the
authoritative online factory — hardcoding evade: 0. Making the new field
required turned that into a compile error. Had it defaulted, online and
offline would have been two different games, silently.
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.