v0.5.34-dev · 2026-08-04
Sidestep
Sword arts ride the weapon, and guns hit like guns
Three FFT-parity corrections, each measured over a fresh 10,000-battle simulator run. Sword arts are WP-carried, not Brave-carried: FFT computes Holy Sword skills from PA × WP with no Brave in the row, so Stasis Sword and Blade Storm now skip both Brave terms — and the forecast, the damage breakdown and the battle log all say so together, because a hover box printing "brave ×0.61" on an ability that ignores Brave would be the old facing lie with a new name. Guns re-raised (8/9/11/12/14/15/15 WP): the old numbers left a mediator's ultimate gun rated below its common book; chemist came along for free (45.3% → 50.9% in duels). Dual Wield pairs the same weapon class (FFT's rule) — both hands strike one tile, so a gun+knife pair either wasted the gun's reach or fired the knife across the map. And the Orator keeps all four of its weapon classes, weighted 60/15/15/10, because a two-outcome weapon roll stops being worth a reveal card.
The dancer dances, and a mutual kill has a winner
A new headless balance simulator (balance/sim/, driving the REAL
match pipeline — units built by the production factory, turns by the
server's own AI driver) found that thirteen abilities were unreachable
by the AI: every range-0 self-centred offensive kit — all 7 dances,
6 of 10 draw-outs — was never once proposed, because the AI asked
"is an enemy within reach 0?" and vetoed the caster standing in its own
blast besides. Both AI tiers now aim self-centred kits at their own
feet and score the ring; the dancer went from literally never dancing
to 4,644 casts in 10k battles (51.4% win rate). A mutual kill now has
a winner — both sides wiping on the same blow used to fall through to
"no winner ever" and hang the server loop; the last killing blow takes
it. Mobility reworked from a clamp into its inputs: base MOV ≤ 4,
Movement slot ≤ +1, accessory +1 MOV items all rare or better — MOV 6
still exists but is a gamble you assemble, not the default state of a
fast class (a flat cap had made Move +2 a 400-JP tooltip, this repo's
oldest failure reintroduced by a fix).
The cursor found its voice
Three UI sounds, and the wiring is the story: AudioManager now
auto-connects every BaseButton that enters the scene tree — hover and
focus play a 50 ms cursor tick (one shared cooldown, so sweeping the
mouse down a menu column is a ripple, not a buzz), pressing plays the
confirm (a bright fifth over a click). No scene wires anything; a
button clicks by being a button, current screens and future ones alike,
and the smoke proves it by dropping a fresh Button into the tree and
emitting pressed — silence there would mean every future menu ships
mute. The third sound is the YOUR-TURN chime (a small rising bell,
D5 to A5), played in battle when one of your units comes up —
suppressed under fast-forward, where auto-waited turns would chime
about their own emptiness. All three deliberately tiny: the correct
volume for a UI sound is "barely there, until it's missing."
smoke_music 194 → 205. A denied-command buzz waits on a real
CommandRejected signal reaching GameState — rejections currently flow
only through the log narration, and a UI pass is not the place to
invent engine plumbing.
Steel follows the hand
Physical arts now sound like the weapon that delivers them — the same
rule WEAPON_STYLE uses for the animation, applied to the ear: an
Equip-Guns archer's Hawkeye fires like the gun it actually is. Seven
new sounds (arrow twang-and-thock, gunshot with PSX grit, the rhythmic
whistle of something thrown plus its shink, and the dragoon's falling
whoosh into a spearhead slam), all selected by FxPlan.action_sounds —
one authority returning delivery + impact + the per-hit rule (physical
thunks on every connect, a dual wield twice; magic parks once per
ability). Bows and guns fix the basic attack too, which had been
borrowing the sword's swing. The smoke walks the registry × seven
weapon classes and demands every derivable name exist; smoke_music
166 → 194. Two of its own checks needed honest fixes on the way: the
SFX size floor was calibrated to cries and rejected a 0.20s shink, and
the spell-parking pin referenced literals the refactor had correctly
turned into data.
Every spell found its voice
Thirty-one spell sounds, all original synthesis, and NO per-ability
table: FxPlan.impact_sound derives the sound from the same fields the
visuals derive from — element picks the FAMILY (fire whoomph-and-
crackle, ice shard-rain, lightning crack-and-thunder, wind gale, earth
rockfall, water splash, holy chime bloom, dark minor-second drone,
arcane for the element-less), magnitude picks the TIER, and tier scales
count, weight and length together. Tier 1 ice is two quick shards;
tier 3 is the cacophony — glass raining over a rumble into one final
crash (the FFT Ice-4 memory, expressed as parameters). Heals get a
rising arpeggio, buffs a two-note lift, debuffs a detuned fall, and
every spell opens with a soft cast release. The element sound parks on
the impact frame the FX director holds — scheduled once per ability,
so an AoE is one cacophony, not three copies.
The third synth file triggered the promised extraction: shared DSP
lives in tools/music/sc_dsp.py now, verified lossless by all seven
shipped WAV hashes staying identical. The smoke walks the LIVE registry
through the derivation: every ability must map to a sound that exists,
every family must be reachable, and tier 3 must audibly outlast tier 1.
The units found their verbs
Five unit-action sounds, all synthesized through the KO-cry template
(tools/music/synth_unit_sfx.py — parametric DSP, deterministic seeds,
loudness authored in the synth rather than patched at play time):
move (two soft footfalls — quiet on purpose, it is the game's most
common verb), attack swing (band-swept whoosh, played at delivery),
attack hit (bright crack + inharmonic metal partials + body thud,
played at the impact frame the FX director already holds), attack
miss (the same air with nothing in it, on the same beat as the MISS
popup), and defend (click, CHNK, damped ring — set and hold).
The swing and hit key on the plan's TOUCH archetype — the same rule
that picks the melee animation, so the ear and the arm cannot disagree;
spell deliveries stay silent until the spell-SFX pass rather than
borrowing a sword's thunk. Defend keys on its id (its nova archetype is
shared with real spells). KO cries v3 landed alongside: the yelp
contour (rise THEN dive), higher peaks, PS1 decimation crunch —
approved. smoke_music 39 → 59.
The match ends in music, and the cries got sharp
Victory fanfare (~16s, D major): a trumpet flourish into the Crown
leitmotif in the major — the same tune the title screen states in D
minor, resolved; the bittersweet minor-sixth peak becomes a bright
major sixth. Defeat sting (~16s, D minor): a lament bass under an
oboe line that falls to the leading tone and never resolves, the bell
tolling D over the final dominant. Both are one-shots in a new
AudioManager.STINGS table — deliberately not MUSIC, where the smoke
enforces looping — fired from the same winner comparison the
VICTORY/DEFEAT banner makes, so they can never disagree. Sting renders
trim their reverb tail (a loop needs its seam; a one-shot ending in
seven silent seconds just overstays).
KO cries v2 after Joe's verdict on v1 ("bad"): quick and sharp and
higher — duration halved to ~0.3s, fundamentals roughly doubled
(male 330→120 Hz, female 560→230), 6 ms attack, steeper clean decay,
brighter harmonic tilt, breath noise cut to an onset consonant, thud
earlier and tighter. v1 chased "real human cry"; the reference's
grammar is a synthy chirp, and chasing realism made it slow and low.
smoke_music 30 → 39 checks (stings load, sized, never loop, and the
match-end call is source-pinned).
Three battle themes, and the fallen cry out
Battle now has music: Steel and Sky (A minor, 116 — march snare and
a horn call over a staccato string engine), The Broken Line (D minor,
132 — an Andalusian bass under a trombone theme, frantic wind runs,
offbeat brass stabs), and Crown of Ash (C minor, 92 — brass chorale,
organ, choir, a neapolitan Db). Each quotes the title theme's Crown
leitmotif at its climax in its own key — three battles, one crown — and
each ends its loop on the dominant so the seam resolves like a phrase.
Composed in-repo through the same engine as the title theme (now shared:
tools/music/sc_midi.py, extracted with the .mid hash proving it
lossless). The track is picked per match, seeded on match + map, so a
replay opens with the same music.
Units that fall now CRY OUT — a male or female death cry (stable
per-unit coin flip until units carry a real gender) plus a body-thud,
riding the new SFX bus, so the Settings dial that said "no effects in
this build yet" is no longer telling the truth and its caption comes
off next pass. The cries are synthesized from scratch
(tools/music/synth_ko_cries.py — a falling glottal pulse train through
gliding vowel formants, breath noise, lo-fi lowpass), deterministic,
shipped as WAV because vorbis smears a half-second transient.
smoke_music grew to 30 checks: loop flags on all four tracks, cries
load and do NOT loop, the rotation reaches all three tracks, and the KO
handler actually calls the cry.
A Settings page, and every dial on it is real
Main menu gains ⚙ SETTINGS: Master / Music / Sound Effects volume at
0–100%, fullscreen (the F11 toggle's twin), vsync, and the server host —
which moved here from the old SERVER… dialog so there is one door to one
setting. The volume dials drive actual AudioServer buses that
AudioManager creates at boot (music already routes through the Music
bus, so that slider is audible the moment it moves; the SFX bus carries
nothing yet and the page says so — effects inherit the dial when they
land). 0% is a true mute, not a very quiet number. Everything persists
to user://settings.cfg. Guarded by tools/smoke_settings (20 checks),
whose load-bearing one reads the AudioServer after poking the page's own
slider — verified by disconnecting the slider, which fails it by name —
because a slider that only moves its own label is this repo's oldest
failure mode wearing a new control.
A hit breaks Charm
Charm now breaks the moment the charmed unit takes damage from any
source — FFT's own rule, and the same shake Confuse has had since
v0.5.10. It was never wired: the tooltip said "until it breaks" while
nothing could break it, so in Joe's v0.5.33 playtest a charmed Dancer's
own teammate hit her for 22 and the charm calmly ran out its full clock.
In practice the breaker is nearly always the original team, since the
charming side can't target its temporary recruit at all. One flag in the
status registry (breaks_on_damage), which every damage site already
consults; guarded end-to-end by
status_integration::charm_breaks_when_the_charmed_unit_takes_damage
(verified by reverting the flag — it fails by name).
Magic seeks; steel lands where it was swung
The v0.5.31 cast lock-on now applies to MAGIC only (Joe's rule from the first ultimates playtest). A charged spell, heal, buff or curse still follows the unit it was committed on — that fix stays, and your self-buffs still land after you move. But a charged PHYSICAL delivery — the dragoon's Jump, Sky Render and Dragon Dive, the knight's Blade Storm and Stasis Sword, the monk's Hundred Fists and Doom Fist, the ninja's Sudden Death, the archer's Hawkeye, Snipe and Rain of Arrows — commits to the TILE. If the target moves during the wind-up, they dodged it: the blow lands on empty ground, and against an ultimate that dodge burns the attacker's one charge. A lancer leaping to safety is a great defensive move, and now it is rewarded.
Decided by the ability's type family, not a hand list, so the next charged physical anybody authors anchors automatically. The battle log already tells you which kind you are facing: "begins charging Dragon Dive at (4,4)" is a tile you can leave; "…at Shadowblade" is a spell that will find you.
Ultimates land in slow motion
An ultimate resolving is now a cinematic: the whole world drops to 30% speed for the wind-up — the caster's animation, the delivery sprites, the sigil, everything, because it all runs on the one engine clock — the camera leans in deeper than it does for any other ability, the impact hits at the usual near-freeze, and then the world dwells slow for a beat before easing back up to full speed. If the blow KOs somebody, the moment holds a little longer still: the fatality read, watching the body fall in slow motion. Applies to every ultimate and to the long-charge eidolons (Bahamut arriving at quarter speed is exactly as intended); an AoE ultimate that downs three units lingers once, not three times.
The ability's own numbers never change — the same amount of ability time passes, it just takes longer to watch, which is why the damage number still pops exactly when the blow connects instead of drifting off the sword. Skipped entirely under fast-forward, same rule as the hit-stop: the point of fast-forward is that you are not watching. All told an ultimate now owns about two and a half seconds of the room, three on a kill — and since ultimates are one-shots as of v0.5.32, you get that moment at most once per unit per match.