Skip to content
Shattered Crowns

v0.5.20-dev · 2026-07-28

Set in Type

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 BRV popup, 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 (F or ▶▶) 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.