v1.4.6-beta · 2026-08-11
Regalia II
Gear that does something. The registry has been describing mechanics it did
not have since v0.5.8 — seven items carried an Element no formula read, and
roughly forty descriptions promised an effect that did not exist. This release
makes the item table tell the truth.
Forty items were describing mechanics the engine did not have
The Ribbon has said "grants immunity to most debuffs" since v0.5.8 while granting +8 MDEF. The Reflect Ring: "spells arrive and are returned to sender." Mirror Mail: "spells arrive, think better of it, and leave." Rubber Suit: "undignified. Lightning does not care." Tidewall Shield: "half the world's fury, halved again." None of it was true.
Underneath that, a harder finding: seven items carried an Element that no
formula read. recalc_stats is the only production code that walks a unit's
full equipped set, and it reads ten numbers and nothing else — every other
.element read in the tree is scoped to the weapon slot. So the Rimeward
Shield's Ice, the Emberward Shield's Fire, Gaia Gear's Earth and four more
were tooltips. Twelfth instance of this repo's signature failure.
EquipmentDef.perks is the answer, and it is a closed enum — the shape
laurels::Requirement uses. No free-text effect strings, no per-item
evaluator functions, because either is a registry the compiler cannot check.
This release wires the four kinds that need no new damage-path work:
Grants(support)— the item confers a Support. Folded intoUnit.innate_supportsbyrecalc_stats, so all 33 wired supports became gear-reachable with no per-support edit. The Crystal Shield now means Unbreakable; the Wardsong Charm means Warded; Silent Verdict and the Huntress Bow cannot miss.Crit(+n)— flat points on the crit roll, stacking additively with Killer Instinct for the reason that support is a flat add rather than a multiplier. Ten weapons carry it.Immune(status)/ImmuneCategory(Debuff)— the ward never lands. Sixteen items, cross-checked against FFT's own table. The Charmed Circlet and the Silver Barrette turn out to split FFT's list exactly the way our own descriptions already claimed — "the slow kinds of harm" against poison, blind, silence and slow; "the sudden kinds" against petrify, stop, charm and sleep. And the Ribbon is finally a Ribbon.Strengthen(element)— the wearer's own damage of that element ×1.25, from the same site and the same number as Elementalist.
The vocabulary was cross-checked against FFT's real equipment data rather than
memory, and it validated the design almost 1:1 — FFT's own 16 effect kinds map
onto these plus the six held back. Several guesses were corrected by it: the
Sovereign Shield strengthens fire/ice/lightning rather than halving them,
the Tidewall Shield halves only those three rather than all eight, and
septieme "Seventh Hour" is confirmed as the Setiemson.
Part two: the effects that touch the damage path
The four cheap perk kinds landed first. These are the rest, and they are the
ones the seven inert Element fields were always describing.
Resist(element, Half | Null | Absorb)— the Rimeward Shield drinks Ice, the Rubber Suit ignores Lightning, the Chameleon Robe drinks Holy. Folded into the ONE function that already answered "what does fire do to this unit?" (element_affinity_mult, renamed fromelement_vulnerability_multnow that Oil is only half its job). Because gear affinity reads off the unit's own baked perks, the signature never changed — socalc_magic_damage,damage_factors, both damage arms and all threepreviewsites inherited resistance with no edit, and the forecast, the battle log and the hit cannot describe the same blow differently. Affinities do NOT stack: the strongest wins, or two Half sources would quietly become a Null.- Absorb inverts the sign, so it needed the one genuinely new branch:
absorbed_or_nullifiedruns BEFORE themax(1)clamp every damage arm applies — that clamp exists so an ordinary mitigated hit still stings, and it would otherwise make a nullified spell deal 1 and an absorbed one deal 1 rather than heal. Absorbed HP routes throughrestore_hp, so an absorbed Fire on an UNDEAD unit correctly hurts it, a rule nobody had to write. It deliberately does not credithealing_done— the attacker did not heal anybody, and that number settles real Crowns. Always/Once— and the difference between them is the whole reason both exist. Reraise is already single-use (try_reraiseconsumes it), so the only route to an unkillable unit is RE-APPLICATION. FFT's own data draws exactly this line: Angel Ring isstart reraise, Chantage isequip reraise, which is precisely why Chantage is notorious. Both of ours take the once form (Joe's call), andno_item_grants_a_renewing_reraiseenforces it rather than a comment asking for it.OnHit/LifeSteal/Proc— oneresolve_weapon_riderscalled from all three damage arms, because three sites is exactly the count that caught the v0.5.10 accuracy layer out and a dual-wield offhand that silently could not proc is the bug that shape produces.Procis QUEUED, never called in place. Casting from inside a damage arm is re-entrancy — a forked RNG stream, a nested event batch, a death path running under a hit that has not landed.MatchState.pending_procsis drained byprocess_command, the one production caller ofexecute_ability. Procs do not chain; a chain is an unbounded loop wearing a feature's hat.HealingStrikeandBoost(Heal | Summon | Talk)— Talk adds POINTS insidemorale_hit_chancerather than multiplying damage, because most of the Orator's kit deals none and a "+20% talk skills" that scaled power would be worth nothing on Persuade.
The AI can see all of it
ai::expected_value folded in evade and facing because v0.5.15 taught it to,
and it would otherwise have cast Fire into a Flame Shield forever. It now
reads the same element_affinity_mult the resolver multiplies by — and since
Absorb returns a negative multiplier, healing the enemy scores below zero and
the bot declines with no special case. A pure status ability against a warded
target scores zero, checked against the same baked list apply_status gates
on.
You field what you roll — never-a-downgrade is gone
Joe asked why the Orator seemed to auto-get its Ultimate accessory, then followed up: "do we need default items, or can we just remove this barrier?" Both answers were bigger than the one item.
default_loadout is the FLOOR of the gear roll, and never-a-downgrade
compared rarity alone — if tier_rank(rolled) < tier_rank(default) { keep the default }. Against the 40/30/20/10 ladder, the default's tier decided how
much of the roll it ate:
| default | rolls it swallowed | rolls that stood |
|---|---|---|
| Common | 0% | 100% |
| Uncommon | 40% | 60% |
| Rare | 70% | 30% |
| Ultimate | 90% | 10% |
Fifteen default slots across twelve of the eighteen jobs sat at Rare, and the Orator's sat at Ultimate. A bard had two. Those units fielded the same item nearly every match — the opposite of what a per-battle roll exists to do. It read as a safety net and behaved as a ceiling.
So the barrier is deleted. default_loadout was doing three jobs and only one
of them was worth keeping:
- Which slots a job fields at all — a
Nonemeans the slot is never rolled. A mage owns no shield, and that is job identity. Kept. - The empty-pool fallback — when nothing in the registry fits the job for a slot, the standard kit stands. Kept.
- A floor on power. Deleted.
A common roll now gives you a common. The reveal card is five real draws rather
than "you rolled nothing better than what you already had" dressed as a
ceremony — source: "default" is unreachable, so the card's dimmed-row styling
now marks only a genuinely unarmed Equip Fists draw.
Defaults were also capped at Uncommon on the way through. That matters much less now the barrier is gone (they only surface via the empty-pool fallback), but it is kept as hygiene and guarded.
Two good knock-ons: white_robe and black_robe stopped being defaults, so
they reclaim their FFT canon (Half Fire/Ice/Lightning and Strengthen
Fire/Ice/Lightning), and the Stormweave Robe — which existed only because the
Black Robe was a default — took Wind and Water, the two elements nothing
else focused. Element coverage goes from three of eight to five.
Casters take the real hit: the robe jobs drop to the Wizard's Robe, −6 HP, −6 MP and −2 MA on the floor. Their MP comes from the roll now. Watch it.
a_roll_is_never_a_downgrade was the test for the deleted rule, replaced by
the_roll_fills_every_slot_the_job_fields_and_no_others — which pins the part
that was always the point.
Every weapon class now stocks every rarity
Weapons roll in two stages — class first, then rarity — so a class with no item
at a tier sends that whole band into an adjacent bucket. KnightSword held
zero commons and zero uncommons, so 90% of every knight-sword roll landed on
the single rare one. Nine more classes had a hole. Ten items fill them
(Oathblade, Vowbrand, Worldcleaver, Siegebolt, Grimoire of Salt, Ironbound
Wraps, Plain Silk, Traveller's Lute, Courier's Satchel, Handcannon), and
every_weapon_class_stocks_every_rarity keeps it true. Armour is deliberately
not checked the same way: pool_for filters by SLOT, so hats and helms draw
from one pool and a thin Ribbon count costs nothing.
Magic evasion can finally be granted
UnitStats.m_evade has existed since v0.5.10 and no item could grant it,
which left the Spellward Shield — an item whose entire identity is magic
evasion — with nothing to give but MDEF. EquipmentDef.m_evade scales and
caps through exactly the same GEAR_EVADE_PCT / MAX_TOTAL_EVADE pair the
physical side uses.
Fixed: Break arts removed twice the evasion they should
destroy_equipped subtracted an item's raw evade while recalc_stats had
only ever credited GEAR_EVADE_PCT of it. Weapon Break against a Main Gauche
took 22 points off a unit that had been given 11, and a couple of broken items
could drive the stat negative.
Perks are rolled, never issued as standard
no_default_loadout_item_carries_a_perk — and it caught a live bug on its
first run. magic_ring is the default accessory for the mage, mystic AND
geomancer, so wiring FFT's silence immunity onto it made every caster in the
game silence-proof by default. black_robe is the default body for four
caster jobs and would have handed them all Strengthen Fire/Ice/Lightning for
free. Both perks moved to items the roll has to actually hand you.
Related finding, NOT fixed (it is a balance call): several jobs field a
RARE default — and one, the Orator, fields an ULTIMATE one (chantage).
Because the roll never downgrades, those units can essentially never improve
that slot, which mutes the reveal for them.
New: four items whose perks this release actually wires
Sabatons of the March (immune to being rooted — nothing in 242 items answered Immobilise), Cravenward Charm, Duellist's Glove, Bell of Small Hours. The other nine from the proposal are held back deliberately: an item shipped ahead of the code that reads it is the failure this whole system is an apology for.
Nine more items
Chirurgeon's Kit, Silvertongue Signet, Eidolon Bell, Cinder Charm, the three glass rings, Oathbreaker's Coin (+3 PA / +3 MA and Always Atheist — a real trade, not a bigger number) and the Sable Mantle. 266 items, all with generated icons in both the client and the site, none of them hand-drawn: the generator reads the item row and lets class pick the silhouette, name pick the material, element pick the accent and weapon power pick the size.
Everything shows its working
EquipmentDef::perk_label renders the clause engine-side and it rides
item_spec_line — the one author the reveal card, the unit sheet, the Codex
pane and the results-board hover all already call. A perk the player cannot
read is the exact inverse of the bug being fixed.
Guard net tests/equipment_perks.rs — 18 checks. Verified by reverting the
four cheap read sites (fails six by name) and again by reverting the gear
affinity fold, the Always renewal and the AI term (fails three).
Balance (Joe)
- Worldcleaver gains Critical +15%. The axe class is the wild one; its one ultimate should swing hardest.
- Duellist's Glove Critical +12% → +10%.