Equipment can be changed at any time, including in the middle of a fight. What it cannot do is change a fight already in progress: every combat bonus is snapshotted when the battle starts and served from that snapshot until the battle ends.
items.csv row: a base number × its tier (I–X) × its grade factor (Worn ×1.00 … Void ×2.08, Grade). Weapons and armour pay the stat of their slot; accessories pay by family (a Signet is Attack, a Relic is HP).gearBonus().gearBonus() returns that frozen copy and ignores the live loadout entirely.initUnits() at the same moment, so they behave identically.The lock binds the FIGHT, not the SHEET (D ruling 2026-08-02). Every display surface reads the live loadout: the character stat panel, POWER, and the creature cards all update the instant an item is swapped, mid-battle or not. Only the combat path sees the frozen copy. Before this the freeze applied to everything, so a player who swapped a weapon during a fight saw no number move anywhere on the equipment screen and had no way to tell the swap had taken. Display producers are wrapped in a helper that lifts the freeze for the duration of the read and restores it on the way out — including on a throw, so a render can never leak live gear into the fight in progress.
Why it is taken deliberately and not lazily. The freeze used to happen on whichever combat read called gearBonus() first. That was only safe while the equipment window and the optimizer both refused to run during a battle — once the battle gate came off, a player could change their loadout before any combat read and the NEW gear would have been frozen instead of the old, silently altering the fight in progress. The snapshot is now taken at battle entry, before anything can act.
An item's stat comes from its own row in items.csv, never from the slot it sits in. itemStat() reads the Effect column and matches the authored grammar (+flat X for gear, tags and gems; increased % X for runes). The slot's default stat is only a fallback for an item with no effect row at all.
This matters most for accessories, which are the one category where the family decides the stat:
| Family | Effect | Grants |
|---|---|---|
| Signet | +Attack (implicit, 6×T×GF, Local) | Attack |
| Relic | +HP (implicit, 9×T×GF, Local) | HP |
Both go in the same two Accessory slots, whose slot-level default is Attack — so any surface that labels off the slot instead of the item will call a Relic an Attack item while showing its correct HP number. Weapons and armour hide this class of bug, because there the slot default happens to match the item.
Scoring must bypass the lock. The optimizer measures a candidate by swapping it into state and asking the game for its own Power number. During a battle gearBonus() would answer with the frozen snapshot for every candidate alike, so every option scored identically and the optimizer reported “already optimal” whatever was in the bag. The measurement therefore lifts the freeze for the duration of the swap and restores it afterwards. This was found by probe the moment the battle gate was removed — the gate had been hiding it.
Client: gearBonus(), freezeGear(), optBuildPlan(), optScore(), optApply() in play/app.js
Ordered level > grade > line. The list built its cards straight off BAG.concat(bagAdd) — insertion order — so a Divine sat wherever it happened to have been hatched and the player had to hunt for it. It now sorts by level descending, then GRADE_ORDER descending, then line, then name (D 2026-08-19, revising the first grade-first order the same day the soul bag was revised).
soulSort: rank, then level, then grade, then line) rather than keeping a second answer. The breeding partner picker deliberately still leads with grade: pairP() floors the average of the two parents’ grades, so grade is the lever on that screen (its own D ruling, 2026-08-05).BAG is copied before sorting. It is not React state, and the bag's own order is its identity — sorting it in place would reorder the inventory as a side effect of opening a picker.eqHMv; eqHoverOn carried !s.eqPick, so the summary was
suppressed precisely while a picker was open — the one screen where a player most needs it
was the one screen that refused it. Fixed 2026-08-05.top:82% so it does not sit on the list being
hovered, and it is pointer-events:none so it cannot steal the mouse off a row.
Without the second, hovering would drop the hover the instant the panel appeared, and flicker.Probe: tools/probe_soulhover.py (BEFORE 4/4, AFTER 5/5). It dispatches a
real mouseover on the row, because the renderer strips
sc-camel-on-mouse-enter from the DOM and there is no attribute to select on.
LIVE on release since 2026-08-05 (0.32).
souls.csv authors the S3 text, and gearBonus then applies p.<pct> × stMult × grMult. The screen quoted the CSV constant while the game paid something else — measured on a live save, the equipment UI read +4% Attack while gearBonus was accumulating 6.22. The percentages in the text are now the effective ones.
soulEffLine still builds the derivation for the details window, where showing the working is the point.parseSoul decides which those are. Scaling every percentage in the sentence was the first attempt and it corrupted the condition: +5% Defense while above 50% HP became above 77.8% HP, silently changing when the soul fires. The magnitudes are taken from parseSoul's own output rather than from a rule about plus signs, because parseSoul is what gearBonus pays out of — a number it does not read as an effect is a number the game does not scale either.×N STRENGTH (Sstage × grade) along with the raw → effective working for every slot in the line.top:32% to avoid the multiplier band. It is back to dead centre at full size.Client: soulTextEff(), soulEffLine(), invTipInfo() in play/app.js. Probe: tools/probe_soulpct.py — BEFORE 3/3 (asserting the text quotes the raw constant), AFTER 9/9, with the displayed number compared against what gearBonus accumulates rather than against a formula the probe restates.
Each of the eight gear slots shows a readout of what its soul grants — the move for an ability slot, the passive text for an armour slot. Since 2026-08-05 that panel also reports the slotted creature, not only its effect:
battleRewards banks XP into soulOv[gi].src — so the bar is read
through monXpOf/xpLevel, the same pair the creature sheet uses. The bar
and the sheet cannot disagree, and neither is a snapshot taken when the creature was consumed.monGenderKnown, never monGender: the latter defaults to
M so breeding always has a side to compare, and printing that default as fact is
exactly what the UI must not do. A creature with no stamped gender shows no glyph.Probe: tools/probe_eqsoulxp.py (BEFORE 4/4, AFTER 6/6). The party card
carries its own level label at 15px stage — 5.39 CSS px, still sub-legible and
not yet raised; see The Stage. LIVE on release since 2026-08-05 (0.32).
Clicking an item in the inventory opens its details. What that window offers is decided by the item's category.
| Action | Shown for |
|---|---|
| DISMANTLE | Weapon, Armour, Accessory, Gem, Rune, Scroll, Tag,
Key — and only when dismYield actually prices the item. Tag and Key
were added 2026-08-05: both were already priced (Key on the tier ladder, Tag by grade and tier)
and the button was hidden by a category whitelist alone. Bulk sweep still never touches a Key
— a key is the gate on a boss fight, so it goes one at a time, as a deliberate act. |
| ENTER THE FLOOR n BOSS | Keys only, and only when the floor is unlocked
(maxFloor ≥ keyFloor − 1). When the floor is locked the button is
absent, not present and refusing. |
| EQUIP / UPGRADE | Unchanged — gear, tags and slottables route to the Equipment window; creatures never do. |
A tag's SLOT row reads “Tag”. The layout names the two creature tag
sockets Tag A and Tag B so it can tell them apart, and
equipItem writes slot:EQUIP[i].slot onto a tag it hands back to the bag. An
unequipped tag therefore carried Tag B into its details and read as though it only fitted
that socket. It fits either. Fresh drops never showed this, which is why it survived.
The boss route does not chain across a transit.
enterBossRoom() derives its target from bossFloor(), which reads the
current floor, so it can only be called once the player is already in that boss's decade.
If they are, the boss room opens directly; otherwise travelToBossRoom() parks them on the
boss's approach floor and opens the den in the same move, so the button always lands in the boss room
(see Bosses). Travel is asynchronous and this area produced the orphaned-veil softlock on
2026-08-04, so nothing is sequenced across it. Probe: tools/probe_itemdet.py.
LIVE on release since 2026-08-05 (0.32).
A gear soul’s output (passive magnitude, and granted move power on the four ability slots) is scaled by two multipliers that multiply together. They are set as one curve, because neither is meaningful alone.
| Lever | Range | What it costs |
|---|---|---|
| Source creature’s stage | S1 ×1.00 · S2 ×1.12 · S3 ×1.25 | Raised by advancing the creature — levels, not resources. |
| Source creature’s quality grade | Worn ×1.00 → Void ×2.00, nine equal steps (1 + (1/9) × (grade# − 1)) | Inherited from the egg. Creatures are not upgraded with resources — they are bred and hatched, so this lever is raised by breeding for a better egg, and above Legendary it is a breeding pursuit outright. See Breeding. |
The combined surface therefore runs ×1.00 at S1/Worn to ×2.50 at S3/Void. Nothing is ever below ×1.00 — a soul is never a penalty, only a bonus that grows.
| Creature quality grade | Stage 1 | Stage 2 | Stage 3 |
|---|---|---|---|
| Worn ×1.00 | ×1.00 | ×1.12 | ×1.25 |
| Rare ×1.33 | ×1.33 | ×1.49 | ×1.67 |
| Legendary ×1.56 | ×1.56 | ×1.74 | ×1.94 |
| Divine ×1.78 | ×1.78 | ×1.99 | ×2.22 |
| Void ×2.00 | ×2.00 | ×2.24 | ×2.50 |
Stage × grade: play/app.js SOUL_STAGE_MULT × src/power.js soulMultOf (1 + (1/9) × grade index). Generated by tools/codex_tables.py.
Rebased 2026-08-05 (D). It previously ran ×0.60 (S1/Worn) to ×1.45 (S3/Void), so most souls sat below 1 and read as a penalty. Span is close to unchanged (2.42× → 2.50×) — a rebase, not an inflation; modelled avatar power at the ceiling +7.7%. The levers also swapped weight: stage used to carry 1.67× and grade only 1.45×, which put the wider range on the lever that costs least. Grade now carries 2.00× — a Void creature is a breeding outcome, and consuming one for its soul should read as a real decision. Recalculated with tools/statmodel.py against the three anchor creatures, never hand-modelled.
Not this lever, but recorded while looking: the CLASS-soul upgrade cost does not reconcile. Class souls (CLS-*) do have a resource ladder and it keeps its own grade field; creature souls never did. Three sources disagree on what that ladder costs and none can be quoted until D rules: Upgrading states mult × 5 units per attempt, but soul_grading.csv’s last row is 1200 Scrap = 80 units at the stated 1 unit = 15 Scrap and no SoulMult × 5 reaches 80; and the CSV contradicts that rate internally, every row’s Scrap column implying twice the units its Dust/Essence/Ink columns do.
Known, and not a soul problem. Solving for the combined multiplier that
would put a maxed L100 avatar on the median maxed-SSR-creature power does not converge — at
×3.00 the avatar is still 32.8% short, and atk never moves at all, because LOCAL souls
only multiply Defence on three armour slots. The avatar/creature parity target cannot be reached through
the soul ladder and needs its own lever.
LIVE on release since 2026-08-08 (0.42). An item’s tier is the single largest term in its stat
base (coefficient × T × GF), and until now it was readable only by opening the
item or reading a meta string. Every surface that draws an item now carries a
tier plaque: a Roman numeral in the top-right corner of the item’s own box,
on a near-black plate bordered in the item’s grade colour.
Five prop sites feed one plaque, so there is exactly one definition of what it says:
| Surface | Prop site | Position |
|---|---|---|
| Inventory grid tile | invGridProps | top-right, z-index:4 — above the frame overlay, below the dismantle × mark (6) |
| Equipment slot row | invSlots | top-right of the 72 px icon box, which gains position:relative to host it |
| Equip picker card | eqChoices (slot branch) | top-right of the card |
| Socket picker card | eqChoices (sock branch) | top-right of the card — gems, runes and scrolls |
| Victory reward row | rwItems | far right of the row |
Mechanics. hasTier is !!it.tier, so an item
with no tier — creatures, eggs, currency, an empty equipment slot — renders nothing at all
rather than a zero or a blank plate. tierRoman is romanN(tier), the same
converter that names the chronicle chapters; there is no second numeral table. The plaque is
pointer-events:none on every surface, so it can never eat a click meant for the tile
beneath it. Reward rows are the one place the tier is not carried on the row object: the drop code
writes it into the sub string (Rare Weapon T7), so the mapper reads
x.tier where it exists and otherwise parses /\bT(\d+)\b/ out of
sub — no reward push site had to be touched.
Why this took three attempts. invGridProps had exposed a
tierBadge prop since the inventory was written and no markup ever bound it.
Earlier sessions read the prop, saw it populated, and went hunting for an occlusion bug that did not
exist. The lesson is the standing one: a prop is not a pixel. tools/probe_tierplaque.py
measures geometry, computed style and viewport containment on the real client — BEFORE 4/7 with
zero plaques on any of the three surfaces, AFTER 10/10. Two of its own assertions were wrong first and
were fixed rather than relaxed: the corner test measured the leaf span against itself (dc-runtime wraps
bound text in a static inner span, so the positioned box is the parent) and answered 0/0, which reads
as a pass; and the plaque failed the legibility floor at 7.3 CSS px, which is why the type is 15–16
stage px and not the 11 px it started at.
Build 0.47. The optimizer could lose an enchant scroll, could hand back a loadout weaker than the one it replaced, scored the incumbent on terms it denied every challenger, and could never report a socket row as unchanged. Each is separate; they are listed in the order they cost the player most.
1. DATA LOSS — optApply() destroyed enchant scrolls.
equipItem() unfolds an arriving item's it.socks onto its new instance socket key. That is the
arriver unfolds half of D ruling other #4, whose other half is the leaver: gems and runes are refunded as
loose bag items, and only the scroll (ki >= 2) folds onto the bag record — see
Socket Ownership. optApply() performed the leaver half and
never performed the arriver half. So a scroll that folded onto a bag record when the optimizer
unequipped a piece was destroyed the moment the optimizer equipped it again: a round trip through the
one button lost it, silently, with no refusal and no message. This is the same class of defect as the class-switch bug
that destroyed 41 of a player's stones — a third code path emptying a slot without obeying the contract.
2. The plan could end WORSE than it started. Measured on a real save under the ATK objective:
35,887 → 35,694, with every gear row reading “X → X” — not one
item moved, only stones. The cause is that fillFor always rebuilt a host's sockets from a clean
slate: the incumbent's existing arrangement was reproduced only by luck, and a slot served earlier could take
stones a later slot was already wearing. optOpen then refuses a regressing plan with
“ALREADY OPTIMAL!” while the next click builds a different plan — which is why the
reporting player had to click several times before anything happened.
Fixed three ways, and the three are meant to be read together:
| Change | Mechanic |
|---|---|
| KEEP-WHAT-YOU-WEAR is a candidate | The incumbent arrangement is entered as a candidate and evaluated first. With the existing strict-greater-than acceptance test, every slot then either holds or strictly improves, so the pass is monotone BY CONSTRUCTION rather than by luck. A regression is no longer possible to build, so it is no longer something the refusal has to catch. |
| Slots are served biggest-host-base-first | A rune is a percentage OF ITS HOST, so the host with the biggest base must pick first — this is the rearrangement inequality, not a heuristic. statFits keeps each stat's hosts in their own relative order, so one pass serves every stat without a per-stat sweep. |
| A global re-deal with hosts final | Once the hosts are settled, the whole loadout is re-dealt under several slot orders and the best kept, with the sweep's own result installed as the baseline so the re-deal cannot lose to it. |
For a future session: a SINGLE-HOST release-and-refill is a no-op.
fillFor is deterministic given the free pool, so releasing one host's stones and refilling that host
returns exactly what was there. Only a global re-deal can move a stone between hosts. Anyone
attempting to improve this by iterating one slot at a time will measure no change and conclude wrongly that the pool
is already optimal.
Results on the same real save: ATK 35,887 → 36,160 where it had been losing 193; Overall Power +440; DEF +124; HP +2,742. Plan build time ~142 ms.
KNOWN REMAINING LIMIT, recorded honestly. Repeated clicks still find a little more:
36,160 → 36,452 over four runs. The residual is a genuine host-by-stone assignment
problem across duplicate item instances — not a bug in the three changes above. Closing it requires
restructuring the plan's bag accounting, which is what consume rides on, so it is a deliberate
non-goal for 0.47 rather than an oversight.
3. The incumbent was scored WITH its enchant scroll and every challenger WITHOUT one. The socket
strip removes only ki 0 and 1, so an incumbent kept its ki >= 2 scroll through scoring
while a challenger — arriving with its scroll folded onto its bag record — was measured bare. A challenger's
own ki >= 2 scroll is now folded in when it is scored. It cannot collide with the fill,
which assigns ki 0 and 1 only, so the two never address the same socket key.
4. sameFill could never be true, so every socket row reported “changed” forever.
It compared the incumbent's chips using an identity prefix with no |@gi instance suffix,
although socket keys have been instance-keyed since bug #8. With duplicated items equipped — two Signets and
four Fangs on the reporting save — the prefix matched 80 keys where 24 belonged to the slot, so
the comparison could not come out equal on any run. The rows were not lying about a change they had made; they were
answering a question that had been asked wrongly.
SLOTG — Void is
3/3/3, Rare is 1/1/1 — so a T4 Void piece carries six stones against a T10
Rare's two, and genuinely wins. Tier is not an input to the optimizer's decision, by
design. It scores by real computed stat contribution through the buildItem /
itemBase / itemStat chain and the game's own charMetric; the tier is already
inside those numbers where it belongs, and adding it as a separate preference would double-count it.Build 0.47. The creature picker had no sort at all and arrived in bag insertion order. The comparator's keys, in order:
| Key | Direction | Note |
|---|---|---|
| Level | best first | |
| Grade | best first | |
| Rarity | best first | Ranked off a rarity order using the creatures.csv spelling — Common, Uncommon, Rare, Super Rare, Super Super Rare. |
| Codex number | ascending | The only ascending key. |
| Gender | tie-break only | Reads monGenderKnown, which can answer unknown — never monGender, which defaults to M and would invent one. |
| Name | ascending | Present so the order is TOTAL and cannot shuffle between re-renders. |
SSR, so using it would silently drop the top two rarities into the unranked bucket — the two that
matter most on this screen.Build 0.47. The passive list led with the raw Effect_S3 sentence out of
souls.csv and then arrowed to the effective value, so the first number on screen was the
unmultiplied base and it never moved when the soul staged up. The slot's hover tooltip quoted the raw sentence
too, so one soul showed two different numbers on one screen.
parseSoul identified are scaled. A condition threshold is never
scaled, so “+5% Defense while above 50% HP” cannot become “above 77.8% HP”
— the same trap this page already records from 2026-08-07, held closed here.Build 0.47. The tier roman-numeral badge moved from the top-right corner of the item tile to the bottom-right.
Correction to earlier text on this page. Every item surface states its tier above describes the plaque as sitting “in the top-right corner of the item's own box”, and its table lists top-right for the inventory grid tile. That is stale for the item tile as of 0.47; the other surfaces in that table are unchanged. The section is left as written — this page is append-only.
Build 0.48 cycle, dev. Report #129 (D, 2026-08-12): “The numeral needs to be centered in its container both vertically and horizontally. Tags are not showing their tier on icons in the equipment menu. The tier numeral needs to be on the bottom right of the item icon image, not top right.” Three complaints, and they are three symptoms of the same thing: 0.47 moved ONE of the plaque sites and left the rest where they were, so the screens stopped agreeing with each other.
All of them now sit in the bottom-right corner of their own box, and the plate is an
inline-flex box that centres whatever numeral it is handed. Centring had never been solved on either
axis: line-height:1.45 on a 15 px font is 21.75 px inside a 22 px content box, so the
glyph sat low, and text-align:center on a min-width plate does not settle a numeral as
narrow as I against one as wide as VIII.
And the plaque family gained a sixth site it should always have had. The creature cards’
tag rows draw the same 72 px icon box as the equipment slot rows directly above them, and they
carried no plaque — because neither half of it existed: invMons[].tags[] emitted no
hasTier and no tierRoman, and the tile had no badge element and no
position:relative to host one. A tag’s tier appeared nowhere on that screen; its
title tooltip is grade only (it read “grade · +0” until refinement was purged from all text, 2026-08-25).
| Surface | Prop site | Position (current) |
|---|---|---|
| Inventory grid tile | invGridProps | bottom-right (moved in 0.47) |
| Equipment slot row | invSlots | bottom-right of the 72 px icon box |
| Creature tag row | invMons[].tags[] | bottom-right of the 72 px icon box — new |
| Equip picker card | eqChoices (slot branch) | bottom-right of the card |
| Socket picker card | eqChoices (sock branch) | bottom-right of the card |
| Victory reward row | rwItems | far right of the row (in-line, not a corner) |
Correction, superseding the correction above. The tier badge moved to the BOTTOM-right of the item tile ends by saying “the other surfaces in that table are unchanged”. That sentence is now stale in its turn: as of this change every corner site is bottom-right, and the table above is the current one. The two earlier sections stay as written — this page is append-only.
What is still not on this tile. The tag rows show a stat value and their sockets and
no item name: nameTier is computed for every tag and bound in no markup. That is a separate
question for D, not part of #129.
Status: LIVE on release since 2026-09-02 (0.67). “Set auto-optimize to HP. Gear list pop
up described the changes and showed that changes would be made. Changes are cancelled every time.”
Six [auto] rows carried the same failure from four different accounts (#191, #206, #209,
#243, #244, #259).
The plan was right and the commit was thrown away. optApply takes an item
census before and after and refuses to commit if the totals differ by one — the guard written after
the 0.47 scroll-destruction bug, and it is a good guard. But a stone has TWO homes, and the census read
them differently:
sockOv with its category
derived from the socket index in the key (0 Gem, 1 Rune, 2 Scroll).socks when that piece goes back to the bag, it was
counted straight off the record — and a folded record carries no cat
field.So one scroll was Scroll|Fated Scroll|10|Rare in a socket and
|Fated Scroll|10|Rare in a fold. Any plan that moved a scroll between the two read as one item
destroyed and a different one created, and the whole apply was abandoned.
All six live refusals are scrolls, and all six balance exactly once the empty prefix is ignored
— nothing was ever lost or created. Scrolls alone, because gems and runes are refunded to
the bag as their own rows and those rows carry a category; a scroll is replace-only and travels inside the
item it is socketed in.
The fix is one derivation shared by both homes — the fold now reads its category
from the socks entry’s own ki, the same 0/1/2 the socket branch reads. The guard keeps
its teeth: a commit that really destroys a scroll, and a commit that really duplicates one, are both still
refused.
This also explains report #187 (“it did not replace … it did not add the socketables that were in my inventory”). Both fixes it could have been waiting on were already live when it was filed — the target/overall-power tie-break landed 2026-08-07 and the one-stone-twice fix landed 2026-08-13 — and an abandoned apply looks exactly like an optimiser that decided to do nothing. The reporter’s own save carries 20 scrolls socketed on worn gear and none in folds, so any plan that swaps one of those pieces out trips the mismatch. Confirmation on his save is owed before #187 is called closed.
Client: invCensus() in play/app.js. Gate:
tools/probe_optcensus.mjs — BEFORE 4/4 on the build it replaces, AFTER
9/9, methods lifted verbatim, and the six live refusals replayed as fixtures.
The rule. Every equippable row in items.csv carries Level_Req = (tier−1)×10: T1 = 1, T2 = 10, … T9 = 80, T10 = 90 — gear, tags, gems, runes and scrolls alike. Until 2026-08-25 the only reader of that column coloured a label on the craft bench; a level-1 account could wear full T10. D's ruling the same day: the ACCOUNT level gates gear (the number on the HUD, playerLv, uncapped past 100) — not the class level, which keeps its own job at the 33/66 stage gates.
| Slot | Level tested |
|---|---|
| Character gear, slots 0–7 (both hands, both accessories, four armour) | the account level |
| Tag A / Tag B of Monster I (slots 10–11) | Monster I's level; empty creature slot ⇒ not gated |
| Tag A / Tag B of Monster II (slots 12–13) | Monster II's level; empty creature slot ⇒ not gated |
| A gem, rune or scroll in any socket | whatever its host slot tests — a stone in a tag is judged against the creature |
One test, five doors. lvlBlock(slot, item) answers the required level when it is not met and 0 otherwise, and everything reads it:
equipItem refuses the same way as a backstop. Legal pieces (a T9 at level 88) equip exactly as before.buildItem returns 0 for it — and its slot's red block reads ILLEGAL / LV n (the second line used to be the fixed word UNEQUIP; it now names the level, and the creature-panel tag rows got the block for the first time — a tag never showed any illegal state before). An over-level stone or scroll shows the red ILLEGAL cell with a tip naming the level.src/power.js, the leaderboard) applies the identical rule — parity gate green on real saves — so an inert piece is worth nothing on the board too.Already worn above level follows the standing ruling on anything illegal (D, 2026-07-28): nothing is unequipped, unsocketed or moved on the player's behalf. The piece stays, contributes nothing, and comes alive by itself the moment the wearer reaches the level. Measured before the rule shipped (2026-08-25, all 18 alpha saves): 4 accounts wore character gear above their account level and 9 party creatures wore tags above their own level. D ordered all of them promoted to level 90 (accounts and creatures), which the ops record on Admin & backend covers; a wearer at 90 clears every requirement in the game, since nothing equippable asks for more.
Never judge on an empty codex. A row with no Level_Req (or a codex that has not landed yet) reads as no requirement, exactly like the stat-fit rule — an unreadable requirement never condemns a piece.
POWER is atk + def + hp/5 summed over the avatar and the two companions, with every soul valued on the unit it affects, as the stat it adds there, times its uptime (Tower rulings form card T7, D 2026-10-18: “Build it, with the simulation”). Since 0.88 every soul counts (D, 2026-09-28: “every soul counts to power level, modified by their grade”). The grade lever is the soul’s own stage × grade multiplier — the one battle uses — so a Void soul always out-scores the same soul at Worn. The ranked number is the server’s: it computes POWER itself from your stored save in its daily sweep (src/power.js), and your own POWER sheet previews it by running that same code (shipped to the client in battle_module.js) — the client never claims a power number.
tools/soul_uptime.mjs → tools/soul_uptime.csv). First turn / first hit stays at D’s 0.5 (the 0.87 ruling). Measured (re-measured 2026-10-22 on the C1 + T10 engine): above 50% HP 0.65, above 70% 0.45, below 50% 0.35; vs a debuffed target 0.56, vs a target that acted first 0.53, vs a target with reduced Attack 0.47, vs a target below 50% HP 0.35; the rare shapes (untouched, first hit against you, below 25% HP, with a Defense buff, vs a target with a DoT or a buff) sit on the 0.25 floor, the common ones (ally damaged last turn, vs a target with no buff) on the 0.75 ceiling. “While any ally is X” is 1.0 when an X is seated, 0 when none is; vs a named enemy type sums the types’ share of the creature codex and clamps like any condition.moves.csv); damage taken is Defense + HP/5 × the percentage (a source-limited soul × that source’s share of the damage taken); thorns is Attack × the percentage × the damage taken per damage dealt; DoT potency is Attack × the DoT share of damage dealt (0.19); a +1 turn is the effect it extends over the base duration the engine applies (3 turns for a DoT, 2.6 for a debuff, 2.5 for a buff); debuff potency and duration ride the enemy’s debuffed share and the debuff magnitude, buff duration your own buffed share and the buff magnitude; healing received is Defense + HP/5 × the healing per damage taken; the buff-removal nuke is Attack × the removal share over the mean hit. “Fungal allies deal +N%” multiplies only the seated unit of that type.Gates: tools/probe_t7_soulpower.py pins the two example souls on the two builds, the measured weight on a conditional soul and the table to the CSV; tools/probe_soulpower.py seats every passive soul in souls.csv and asserts each moves POWER, more at Void than at Worn, identically on client and leaderboard; tools/probe_power_share.py pins that the sheet is the server’s function.
A saved build is the server’s record of a loadout: which of your own pieces sits in each gear slot, each soul slot and each socket. Builds are kept per class line, in that line’s Build_Slots slots (classes.csv; 5 for every line today). The BUILDS button opens them; since 2026-10-26 (T65) it sits in the player panel directly under CLASSES, beside AUTO-OPTIMIZE, no longer in the header.
Status key: every entry dated on or before 2026-11-02 is LIVE on release (0.92, 2026-11-02); an “ON DEV”, “On dev” or “held for release” note inside one of those entries is the status on the day it was written.
2026-11-03 — codex review (dfmc-client lane codexreview-a; D 2026-11-03 “review the codex, clean it up, update whatever needs updating”). WIKI ONLY. The page opened with the gear lock and ran through forty fix records before saying what the equipment screen is. New section The equipment screen at a glance ahead of the record: the slots, what a piece pays (base × tier × grade factor), sockets, the soul seated in every gear slot (moves in the hand and accessory slots, passives in the armour slots) and the T40 reading of Team (character + both monsters), the level rule, POWER, where AUTO-OPTIMIZE / BUILDS / DISMANTLE / FILTERS live. Saved builds still placed the BUILDS button in the header; T65 moved it under CLASSES on 2026-10-26. Status key added above the changelog. No mechanic changed.
2026-10-26 — T65 follow-up: AUTO-OPTIMIZE leaves the equipment header too (dfmc-client lane curfix2; rulings card T65, its recommended option built while D’s answer is pending — the answer corrects it). With BUILDS gone the AUTO-OPTIMIZE button (about 188 css px) still shared the header with the currency strip: on a phone (390×844) Scrap and Ink were cut off, and at 1600×900 the strip kept only 15 css px of slack instead of 24. It now sits beside BUILDS under the CLASSES switch, with the same preview-first behaviour and the same padlock line while it is locked; the currency strip keeps the header’s full width. Proof: tools/probe_curfix.py 25/27 → 33/33 (two AUTO-OPTIMIZE checks per viewport added).
2026-10-26 — T65: the BUILDS button leaves the equipment header (dfmc-client lane t65builds; rulings card T65, its recommended option built while D’s answer is pending — the answer corrects it). In the header the saved-builds button took about 136 css px of the currency strip and cut Scrap and Ink off. It now sits in the player panel directly under the CLASSES switch (builds are kept per class line) and opens the same saved-builds window; the currency strip gets that width back. Proof: tools/probe_curfix.py T65 checks at 1423×687, 390×844 and 844×390.
2026-10-23 — hardcode-3: the measured soul uptimes are the codex table soul_uptime.csv (dfmc-client lane hardcode-3; D: “The csvs need to be the source of all truth. Hardcoded NEVER.”). Held for release 0.90. The same file tools/soul_uptime.mjs writes (its used column); src/power.js buildTables reads it and the client hands it to the shared power formula. The literal in power.js stays only as the fallback. No number changed. A pin that predates the row keeps the old value and names it (never a refusal); it reaches players when DATA_PIN advances and the bake runs. Gate tools/probe_hardcode3.mjs 33/72 → 72/72.
2026-10-23 — a soul passive’s text no longer runs under the Lv badge (dfmc-client lane probe-triage-5. Dev build now; release with the next ritual.) #891 (D 2026-09-14) says nothing hides behind the level number: the move rows already end 80px before the edge, but the soul passive row kept only its 14px padding, so a long passive line (“+7.8% Attack the turn…”) ran 44px under the badge. The passive line now reserves the same 80px. Gate: tools/probe_mvlv.py 5/6 → 6/6.
2026-10-23 — a uid-less worn piece of a legacy save is named by the server, not taken off (dfmc-client lane loadout-bugs, server + client). On dev. A restored legacy save wears gear with no uid. Post-wipe the save took every such piece off the party, and the client stamped its own uids on them, so an equip that displaced one named a uid the server never held and was refused whole — the equip did not stick and both pieces came off. The server now mints a uid (and an items row) once for a uid-less piece its own stored save wears in that slot (flag party_worn_minted); a uid-less piece the stored save does not wear is still taken off. On the server-authoritative save the client no longer stamps uids on slot pieces. Gate tools/probe_loadout_srv.py.
2026-10-23 — the auto-optimise gate follows D’s #50 (dfmc-client lane loadout-bugs). Gate only, no mechanic changed. With a focus set, the target stat leads and overall power is the fallback: the target never drops, overall breaks ties, and overall may fall on a plan that raises the target. The 2026-08-29 entry below (“neither number may go backwards”) is the older rule. tools/probe_optfocus.py check 3.
2026-10-23 — the soul strength stage × grade table is generated (dfmc-client lane a8-s5-c17-tail, C17 part 4). WIKI ONLY. tools/codex_tables.py block soul-strength: play/app.js SOUL_STAGE_MULT × src/power.js soulMultOf, read from the engine. No number changed.
2026-10-23 — the socket columns are headed GEM / RUNE / SCROLL (screen review finding 5, dfmc-client lane screen-fixes-2). Dev build now; release with the next ritual. The hero gear rows in the equipment window end in a grid of socket cells whose numbers carry no stat word on a gem (D #904/#905, kept). The grid now has one small header per socket kind in the strip above the first row, and every cell sits in its kind's fixed band: gems in the first three columns, runes in the next three, scrolls in the last three (a Void item's 3/3/3 is the most a grade gives). Before, the cells packed to the left, so a rune could stand where the row above had a gem. Display only; socket counts and values are unchanged. The creature tag rows are not changed. Gate tools/probe_b9_sockhead.py.
2026-10-23 — the bag tile corner badge says what it is (screen review finding 6, dfmc-client lane screen-fixes). Dev build now; release with the next ritual. The small badge at the bottom-right of every items-bag tile is the item’s tier. It had no title and let no hover through; hovering it now reads “Tier N (roman) – the item tier; a higher tier has a higher base stat”.
2026-10-23 — saved builds per class line (backlog B11, D T45 “saved builds are a server record, applied by the server”, dfmc-client lane b11-builds). On dev; release with the next release and the data pin that carries classes.csv Build_Slots; server + client. New: up to Build_Slots (5) named builds per class line, kept on the server (/api/builds: list, save, rename, delete). Save snapshots the server’s stored loadout; apply is a server action (the buildApply save intent) that moves the account’s own pieces into place, skips and names any piece no longer held, and answers the resulting state, which the game adopts. Until the pin advances, the release reads a classes.csv with no Build_Slots column and saving answers “not open for this class yet”. Gates: tools/probe_b11_builds.py (save, apply restores, a dismantled piece skipped and reported, another account’s build refused, apply idempotent) and tools/probe_b11_builds_ui.py (the window, apply by click, phone portrait and landscape).
2026-10-23 — keep rolling scrolls: the scroll theatre offers the next scroll (backlog B2, report #921, dfmc-client lane b2-scrolls). On dev; release with the next release; client only; no roll, odds, band or cost moves. When a scroll roll stamps and the player still holds a scroll that socket accepts, the theatre no longer closes by itself: it offers ROLL AGAIN on the next scroll - the first one the socket picker would list (highest tier, then highest grade, among the scrolls the host’s level allows) - and KEEP THIS ROLL. The ROLL AGAIN button names the roll it destroys, so that click is the replace confirm (scrolls are only ever overwritten, never removed). Each roll is the same server door as a pick from the list: a fresh quote with its own server-minted attempt key, the server’s draw, the scroll consumed and the forced save that settles it; the next roll waits for that save. A draw the server refuses consumes nothing and the theatre stays open with the reason. KEEP or a click on the scene closes it; with no scroll left it says NO SCROLLS LEFT TO ROLL and closes by itself as before. Gate: tools/probe_b2_keeproll.py (desktop, phone landscape and portrait).
2026-10-23 — the creature pickers show each creature’s moves, and a type badge names its type (backlog B7, ruling 9.10, dfmc-client lane b7-picker). On dev; release with the next release; client only, display only. Every candidate card in the party-slot creature picker, the slotted creature above it and every parent card in the breeding picker now list the moves that creature has at its own level — the same moves.csv rows (Level_Learned, and the empowered name past Level_Upgrade_V2/V3) the party card and the fight use — one line per move: its name and its target and effect icons, coloured on the left by move kind. Hovering a move shows its effect text. Hovering a type badge shows the type’s name (creatures.csv Type_Primary / Type_Secondary); tapping it on a phone shows the name as a toast and does not pick or unequip the creature. Gate: tools/probe_b7_picker.py, desktop and phone landscape.
2026-10-23 — the slot picker builds its cards as you scroll (backlog B12, dfmc-client lane t35-b12). On dev; release with the next release; client only. Measured with a full bag (600 creatures, 400 items): opening the equipment window took 36 ms, the main-hand picker 45 ms, but the creature picker built a card for all 600 candidates, 21,322 page elements and 0.6 s from click to picture. A slot picker now builds the first 60 cards of its sorted list (best first, the order is unchanged) and the next 60 each time its list is scrolled near the bottom. Every candidate is still there; none is filtered. The creature picker now opens in about 0.1 s.
2026-10-23 — the loadout’s effect line is computed, never designer notation or “undefined” (dfmc-client 2d038719, lane ui-polish-1, D playthrough 2026-10-23). Dev build now; release with the next ritual. The slot picker row and the equipped-item detail panel printed the items.csv Effect cell raw (“+flat Attack (implicit, 16.5×T×GF, Local); ability slot 1 (soul move grant)”), and a server-minted piece with no stored effect read “Common · T1 · undefined”. The line is now the stored effect, else the items.csv row, with the notation stripped clause by clause and the implicit stat clause given the piece’s real base value (“+18 ATK · Ability slot 1”); with no line the separator goes too. Creature rows keep their own effect. Gate tools/probe_gearfx.py 2/6 → 6/6.
2026-10-23 — POWER is seen moving: a bump on advancement and on upgrade (backlog B13, ruling §4.3). SHIPPED IN 0.90. An advancement or an upgrade changed POWER without a sign. The client now takes POWER (the same computePower the server’s power fold runs, on the save the server adopted) just before the commit and watches for the number to move: the advancement reveal gains a line POWER before → after (+delta) under the name line, shown only once the new form stands (evoStage 3, after the cry, T15); the equipment window’s POWER readout ticks from the old number to the new over about 0.6 s, flashes, and shows a signed chip (+N green, −N red) for about 3 s. An upgrade that fails, or one on an item that is not equipped, moves nothing and shows nothing. Display only: nothing here decides or writes. Gate tools/probe_b13_pwbump.py (client).
2026-10-23 — Team souls are everyone on the team: the hero takes the Team HP share, and 40 Team soul magnitudes rebalanced on that premise (Tower rulings form card T40, D 2026-10-22, backlog C5). SHIPPED IN 0.90. Every Team bucket already reached the hero and both monsters (Attack, Defense, damage dealt and taken, thorns, healing received, debuff and buff duration and potency, damage over time, the conditional Team souls) except HP: the hero’s max HP read the Personal HP share alone, so a Team HP soul skipped him. Now hero max HP × (1 + (Personal HP % + Team HP %) / 100), on the server and the client alike; no soul carries a Team HP passive today, so no number moves on that half. Then every Team soul was valued with the soul-power model on all three units, against the median soul of its slot and rarity (Head: Personal + Team; Feet: Team): inside 0.67×–1.5× it stays, outside it the magnitude moves toward the tier, at most ×2 or ÷2, whole percent, and a passive never drops as rarity rises. Too strong, cut: the flat Allies +N% Defense Feet (Common/Uncommon/Rare +4/+5% → +2%, Super Rare +5 → +3, Super Super Rare +6/+7 → +3/+4), BULWARK Feet −4% → −2% damage taken, the first-turn +Defense / −damage Commons, ZARATAN and CENOTAPH Feet, the Common and Uncommon healing-received Heads (+4/+5/+6 → +2/+3/+3) and DAGON Head (+6 → +3). Too weak, doubled or near: first-hit and reduced-Speed / buffed-target damage (+4/+5 → +8/+9), damage-over-time, bleed and burn damage (+5/+6 → +10/+12), the debuff-potency Feet (MARID, TYPHON, DEMIURGE, INKMANTLE +5/+6 → +10/+12), SPOREWHALE debuffs expire 20% sooner, SANGLEECH +9% heal. Held: the +1-turn souls, the source-limited damage-taken souls (T10), the type-scoped souls. The full table is dfmc-client docs/team-souls-2026-10.md; codex/souls.csv is the truth. tools/soul_uptime.mjs re-run and SOUL_UPTIME re-pasted (seed save POWER 8,204 → 8,257); the T4 balance sim 7/7 before and after. Gate tools/probe_c5_team_souls.py 4/9 old tree → 9/9.
2026-10-22 — the soul uptimes re-measured on the C1 + T10 engine (follow-up to Tower rulings form cards T7 and T4, D 2026-10-18). LIVE on release since 2026-10-23 (0.89); POWER moves by a few points for a save carrying conditional or utility souls (the seed save 9,318 → 9,322). The T7 table was measured on the pre-C1 engine; the sqrt(atk/def) damage, the Auto PVP HP ×1.5 and the T10 tick rule make fights run longer, so every uptime and share moved by a few hundredths: above 50% HP 0.66 → 0.65, below 50% 0.34 → 0.35, vs a target that acted first 0.52 → 0.53, vs a target with reduced Attack 0.46 → 0.47, the DoT share of damage dealt 0.18 → 0.19 (of damage taken 0.16 → 0.18), the debuffed shares 0.56 → 0.59 and 0.57 → 0.59, the base buff duration 2.4 → 2.5 turns; first turn stays at D’s 0.5 (measured 0.08). Same tool, same 504 fights, same 0.25–0.75 clamp: tools/soul_uptime.csv and the SOUL_UPTIME literal rewritten, nothing else in the model touched. Gate tools/probe_t7_soulpower.py 7/9 (the old literal against the new table) → 9/9.
2026-10-22 — every “take less damage” soul reduces the lingering tick too, and the four damage-over-time souls are 3× their tier (Tower rulings form card T10, D 2026-10-18: “All damage souls reduce ticks too … Are the damage over time specific souls stronger than the generalistic ones? Review and rebalance”). LIVE on release since 2026-10-23 (0.89); battle numbers move for every damage-taken soul, POWER moves for the four. Since 2026-10-01 (SOULBAT) a plain “Allies take -4% damage” (BULWARK Feet), a first-turn one, an above/below-N%-HP one and a full-HP one cut hits only: only a “from damage-over-time” soul reached the end-of-round tick. The rule is now: every damage-taken soul reduces a lingering tick as it reduces a hit — the conditional ones are judged on the ally at the end of the round (the first lingering effect’s applier stands as the attacker for “while the enemy has no active buffs”), and the Team package’s flat share, its self-typed share (“Avian allies take …”) and its damage-over-time shares all pay, with the floors a hit has (conditional souls to 0.1, the package to 0.5). The multi-hit and enemy-type shares never reach a tick: a tick is not a move and has no attacker type. A 60-point Scorchrot tick under BULWARK is 58; under a first-turn -4% it is 58 in round 1 and 60 after; under TALOS/VIGIL -5% it is 57 above half HP and 60 below. With that a damage-over-time soul of the same number became strictly weaker than the generic one, so the four are rebalanced to three times their tier’s generic: BOGHOPPER and ASHWALKER Feet -3% → -9%, GOLGOTH and SIMOOM Feet -4% → -12%. Why 3×: POWER values a generic damage-taken soul on all the damage taken (hits + ticks) and a damage-over-time one on the measured tick share (16.3% of what an ally takes, tools/soul_uptime.csv dotTakenShare), so a DoT soul beats the generic of its tier exactly when ticks are a third or more of the incoming damage and loses otherwise — on the seed account a -12% DoT Feet is +132 POWER against a flat -4%’s +270 (0.49× = 3 × 0.163). The seeded-fight check (160 Auto PVP mirror fights of the seed party per cell, the T7 harness): on the natural pool (ticks 16% of damage taken) the generic -4% saves 3.9% of the damage taken and wins 122 fights, the new -12% DoT soul 3.7% and 119, the old -4% DoT soul 0.9% and 104 (no soul: 98); on a DoT-heavy pool (every ally opening with a 30-round Scorchrot, ticks 42–46% of damage taken) the party lasts 1587 rounds bare, 1668 with the generic, 1633 with the old DoT soul and 1738 with the new one. ZARATAN Feet already read “while above 50% HP” (2026-10-01); no Team Feet soul says “while you”. Engine: src/battle.js endRoundTick (shipped in play/battle_module.js); valuation: src/power.js, the damage-taken term; gate: tools/probe_t10.py (6/11 on the old tree, 11/11 now; the three recorded fights replay unchanged).
2026-10-22 — a soul is worth the stat it adds to the units it affects, times its measured uptime (Tower rulings form card T7, D 2026-10-18: “Build it, with the simulation”). LIVE on release since 2026-10-23 (0.89); every player’s POWER and rank move at the next daily sweep. Until now a soul paid twice: once in the stat (an HP soul in the avatar’s maxHp) and once more as a flat team multiplier, 1 + (damage dealt + HP + thorns + utility)/200, that never looked at the build — 70–87% of an HP soul’s power ignored what it was worn on, so +10% HP was +640 on a MAGE (ATK) build and +724 on a WARRIOR (HP) build of the same account, beating that MAGE’s own +10% Attack (+203); and every conditional weight was hand-picked (0.75 / 0.5 / 0.25). Now the flat term is gone and each soul term is valued on the unit it affects: Attack, Defense and HP souls are the stat they add (the same +10% HP is +62 on the MAGE and +122 on the WARRIOR; +10% Attack is +201 and +71 — the shape D accepted on the 0.89-4 board, +120 / +40 and +140 / +60); damage dealt is valued from Attack, damage taken from Defense + HP/5, DoT potency and +turn by the DoT share of damage and the base duration, debuff and buff duration by their share and magnitude. The uptimes are MEASURED, not picked: tools/soul_uptime.mjs runs 504 seeded fights with the server’s own engine (resolveRound, the code every ranked fight is re-simulated with) on three builds of the seed party — HP, ATK, balanced, in a round robin — and writes tools/soul_uptime.csv (one row per condition shape and effect class: the three builds, the mean, the value used); conditions clamp to 0.25–0.75 so the weights stay close, first turn stays at D’s 0.5 (the 0.87 ruling, measured 0.09), shares are raw. src/power.js carries the table verbatim. Two shapes the seed kits cannot measure take the codex pool’s share so the soul still counts (2026-09-28): a move kind (multi-hit 5% of moves.csv; Heavy and Nuke are soul words no move row carries, so they take the smallest kind share — a C5 note) and buff removal (0.5%). The seed save moves 9,752 → 9,318 (−4.5%); the worker’s battle_pw_bad floor, (2000 + 1500 × floor)/9, still clears it at its floor with 5% to spare, and its claim-vs-calc list (pwDiff) is watch-only, so a client still on the old module for a day is logged, never flagged. Gates: tools/probe_t7_soulpower.py 2/9 → 9/9 on the two builds; probe_soulpower.py 11/11, probe_power_parity.py and probe_power_share.py green (its no-number-moved baseline is retired by this ruling).
2026-10-22 — the leaderboard follows the sheet: refinement counts 0 and an illegal piece counts 0 on the board too (Tower rulings form card T8, D 2026-10-18: “Leaderboard follows the sheet … No one should be wearing gear they can’t wear so investigate this as a bug.”). LIVE on release since 2026-10-23 (0.89); the ranked POWER number moves for a save wearing refined gear or a class-locked / wrong-slot piece. The board and the sheet ran the one power function (src/power.js, shipped to the client as DFMC_POWER) under two rule sets: the sheet told it “refinement is 0, an illegal piece is inert” and the daily sweep told it nothing, so the board paid +3% per refinement level on gear (+1.5% on tags) and counted a weapon of another class or an armour piece in the wrong slot at full value. Both rules are now the function’s own, whoever calls it: the server’s legality check (illegalOf, the same class lock and slot test the sheet runs, read off items.csv) is the default, and refinement multiplies by 1 (D 2026-08-10). The sweep still derives the ranked number from the stored save alone — the client’s own figure is recorded only as a cross-check — and now also counts how many saves wear an illegal piece. The bug behind the illegal piece: the slot test read the record’s own slot before items.csv, and a worn piece carries the slot it sits in, so wrong-slot armour (or a right-class weapon in the wrong hand) read legal on the sheet; the table is read first now, and EQUIP itself refuses a piece the picker would never have offered (“WARRIOR ONLY” / “WRONG SLOT — … CANNOT BE WORN THERE”). The 2026-07-28 rule stands: a piece that becomes illegal after a class change stays worn, reads ILLEGAL and contributes nothing — now on the board as on the sheet.
2026-10-01 — the scroll theatre says EXCELLENT, and PERFECT only at the top (Rulings board card scrollwords, D picked “EXCELLENT”). LIVE on release since 2026-10-23 (0.89) (client); no roll, odds or band moves. The screen that plays a scroll roll after the socket commits used to shout PERFECT on the whole top 15% of the band. PERFECT now means the true band maximum only (the highest value the server can hand back: a whole point for flat, 0.1% for increased and more - e.g. 76 on a T10 flat band, 27.0% on its increased band). The rest of the top 15% shouts EXCELLENT (75 or 72 on 40–76, 26.9% on 13–27%); 15% is the band the screen already used for A POOR ROLL at the bottom and the band PERFECT used to cover, so no cheer went quiet. With D’s note on the same card (“a dark fantasy rework ... bigger and more theatrical ... a magic scroll being applied to an item”) the theatre became a stage-sized arcane scene: an Aether ink field drifts behind two counter-turning alchemical circles tinted by class (flat gold, increased green, more ember), the parchment unfurls between its rollers, the class/stat/band and the roll are written on it in ink, and a wax seal stamps the word (gold PERFECT with rays, violet EXCELLENT, ash for a poor roll). It names the item the scroll is inscribed upon. The art is baked in code by tools/make_scroll_art.py into play/img/scroll/; every keyframe moves transform or opacity only. Click still skips to the stamp and a second click closes it. Gate: tools/probe_scrollwords.py.
2026-10-01 — ZARATAN Feet says what the fight does (Bug Run 0.89 ENGINE, Rulings board row souls3). LIVE on release since 2026-10-23 (0.89); no number moves. “Allies +6% Defense while you are above 50% HP” now reads “Allies +6% Defense while above 50% HP”: the fight judges each ally’s own HP, as TALOS and VIGIL Feet were retexted to say in SOULBAT (both sentences parse to the same condition, so nothing in the fight changes). The sentence reaches players with the next DATA_PIN.
2026-10-01 — the POWER sheet runs the server’s power function (Bug Run 0.89 POWER-SHARE, D board Q4: “this need to be server authoritative. The client should NEVER dictate power.”). LIVE on release since 2026-10-23 (0.89); no number moves. The leaderboard was already the server’s: the daily sweep computes POWER from each stored save (src/power.js) and the client’s own figure is only a cross-check. But the sheet ran a hand copy of that formula, kept equal to it only by parity probes. Now src/power.js ships in the client (battle_module.js) and the sheet, the save and the auto-optimiser all call it; the client copy is deleted. Measured over 572 loadouts of a real save (every soul, gear in and out of slot, every class and stage, every class-soul grade): 0 numbers moved, and one power evaluation got cheaper (0.54 → 0.36 ms). Two sheet rules the board does not read yet stay on the sheet only, open for D: a class-locked or wrong-slot piece is inert, and refinement does not exist (D, 2026-08-10). Gate tools/probe_power_share.py.
2026-10-01 — damage-taken souls pay only on their own source, and the character reads its conditional Defense souls (Bug Run 0.89 SOULBAT, D board Q5 “Fix both”). LIVE on release since 2026-10-23 (0.89); battle numbers move for these souls. A Team damage-taken soul limited to a source — “Allies take -4% damage from damage-over-time”, “… from multi-hit moves”, “… from Construct enemies”, “… from Spectral and Fey enemies” — or to a wearer type (“Avian allies take -5% damage”) had no condition, so it cut every hit against every ally, and the damage-over-time ones never touched a damage-over-time tick. Each now pays only on its stated source: the damage-over-time souls cut the end-of-round tick and no hit; the multi-hit souls cut each hit of a multi-hit move; the typed souls cut hits from an enemy of a named type; the Avian soul cuts hits on Avian allies. Eleven souls changed (4 damage-over-time, 3 multi-hit, 4 enemy-type) plus the Avian one. The rule for every damage-taken soul is now: it reduces damage from moves; only a “from damage-over-time” soul reduces a lingering tick (superseded 2026-10-22, T10 below: every damage-taken soul reduces a tick). The character’s own Defense readout now includes its conditional Defense souls while they hold (the fight already judged them per hit since 2026-09-30); the fight-start Defense snapshot still leaves them out. Descriptions reviewed against the fight for every damage-taken soul; three sentences in souls.csv said something the fight never did and now say what it does: TALOS and VIGIL Feet read “while above 50% HP” (the fight judges each ally’s own HP, not the wearer’s), and PHYLACTERY Feet reads “Allies take -8% damage while below 25% HP” (there was never a first-ally or two-turn rule). The sentence change reaches players with the next data pin. POWER is unchanged. Gate: tools/probe_soulbat.py retests every damage-taken soul and the seven Personal Defense heads, 46/69 → 69/69.
2026-10-01 — the New Only count and the slot shop title (review lane AB, dev 2a4e144). LIVE on release since 2026-10-23 (0.89). The New Only chip in both bags now counts only the new records the other filters would still show, like every other chip since lane L: with ABILITY Heal on it read 167 when one soul matched, and with GRADE Epic on it read 170 over a grid of 108. The slot shop titles no longer begin with “Steam ·”, since Wraithgold purchases are not live: they read Soul Slots · Wraithgold and Inventory Slots · Wraithgold. Gate tools/probe_review_laneab.mjs and tools/probe_laneab_boot.py.
2026-09-30 — the item detail, the class soul crystal and the bag filters tell the truth (client review F323 / F325 / F331 / F332 / F333 / F340). ON DEV. An item above the wearer's level no longer shows a green +delta and a live EQUIP that the slot picker then refused: the compare rows read LV n in red, the EQUIP button greys, and pressing it says LEVEL n REQUIRED, the picker's own words. The class soul crystal, its STAGE row and its effect line read the acknowledged stage, as the portrait has since #102, so the stage-up arrow and the crystal no longer disagree. In both bags every filter group's chip counts now apply every other group, ABILITY and NEW included, and an ABILITY-only filter counts as a filter (no empty tiles, no + tile under it). The slot shop footer reads the open bag's real capacity, the same number as its headline, and no longer promises Steam checkout: Wraithgold purchases are not live in the alpha. No rule changed.
2026-09-30 — conditional Defense souls are judged on every hit, and Personal conditional souls stay on the character (client review F060 / F070 / F071, X5 settled half). ON DEV. The rule above — every conditional soul is evaluated per hit in battle — did not hold for Defense. A conditional +Defense soul was judged once, at the start of the fight, and the answer was frozen for every round: “Allies +4% Defense on the first turn” kept its 4% all fight, a “while below 50% HP” soul never switched on (every unit starts at full HP), and the character never received any conditional Defense soul at all, so the seven Personal ones were dead. They are now judged at the moment each hit lands, against the unit being hit and its attacker, for the character and the creatures alike; the always-on typed Defense share is unchanged. Second half: a Personal conditional soul (“The first hit against you each battle deals −10%”, “+5% Attack the turn after gaining a Defense buff”) also reached all three creatures, because every unit carries the same soul package; Personal now means the character only, as it already did for Defense. Team souls are unchanged. The damage-taken souls limited to a source (damage over time, multi-hit moves, an enemy type) are untouched — that is D’s open X5 question. Shared battle engine, client and server alike.
2026-09-30 — review drift fixes: F324, F335. Codex only. The page caught up with the code; no rule changed. F324: the key button in item details is labelled ENTER THE FLOOR n BOSS and always lands in the den via travelToBossRoom() (c1a972a, same day as the first build); the page still described GO TO FLOOR n and a walk in from the approach floor. F335: the soul picker sorts level > grade > line (D 2026-08-19), following the soul bag; the breeding partner picker still leads with grade.
2026-09-30 — a key door survives the forfeit question, and the item comparison builds the candidate as equipping would (client review F321 / F322). ON DEV. Opening a boss key in a fight asks to forfeit; after YES the trip was retried without the key's approach rule, so a player who had reached the approach floor but not the boss floor lost the fight and went nowhere. The retry now carries the key's rule. The item-details comparison scored the candidate as its bare base but the worn piece with the slot's LOCAL soul multiplier; the soul stays on the slot, so the candidate is now scored under it too, with its own folded scrolls (the worn gems and runes go back to the bag and are not counted for it). The NEW figure is what the slot would read after EQUIP.
2026-09-30 — a cancelled auto-optimize really changes nothing (client review F151). ON DEV. The item census gate refuses an optimisation whose item count would not balance and says OPTIMIZE CANCELLED — NOTHING WAS CHANGED. That was not true of the base bag: the gear and stones the plan took from it were removed before the census ran, and the refusal never put them back, so the next save made the loss permanent. A refused commit now restores the bag exactly as it was before the click. Gate: tools/probe_review_c13c09.mjs.
2026-09-30 — the leaderboard reads the class stage you have accepted, and buff-duration souls sit in their own bucket (client review F145 / F149). ON DEV. Since #102 the POWER sheet builds the avatar from the acknowledged class stage, min(level stage, clsAdv), but the leaderboard figure (src/power.js) still took the stage from class level alone: a player past class level 33 or 66 who had not clicked the stage-up ranked about 5% above the POWER on his own sheet (MAGE, class Lv40, not advanced: sheet 8,573, board 9,023). The server now reads the saved clsAdv the same way. Separately, “your buffs last +1 turn” souls (Herne Gloves) were filed server-side under debuff duration; they now land in the buff-duration bucket as on the client (#127). No weight changed, so that half moves no number today. Rankings move only for players holding an unaccepted stage-up. Gate tools/probe_power_clsadv.py 4/10 → 10/10; tools/probe_soulpower.py 11/11.
2026-09-28 — every soul counts toward POWER, scaled by its grade (D, 0.88 carry-over). ON DEV; rankings shift for every player. After 0.87 counted the 11 opener souls, 197 of the 310 passive souls still scored 0: every conditional soul (HP thresholds, target conditions, untouched, not attacked, ally type) and every soul whose effect was not Attack / Defense / HP / thorns / own damage — team damage, damage taken, DoT and debuff potency, durations, healing. Each now counts by the weights in the POWER section above. Two defects found on the way: the two personal “+Defense while you have not attacked this turn” souls were applied at full to creature Defense (on the sheet and in battle) — a personal soul now stays on its wearer; and “while any ally is an Insect” read the type as “an”, so LOCUST Head never held in battle either — fixed. Every player’s POWER and leaderboard rank move, by how many conditional and utility souls they wear. Gate tools/probe_soulpower.py 8/11 → 11/11.
2026-09-28 — equipping, socketing and locking can no longer copy an item (Q6 “audit every minter”, D; auto inv_dupe_uid). SHIPPED IN 0.87. One scroll (uid a6cc15da) was sitting in two sockets on two different Superior Carapaces. Every picker card remembers the bag record it was drawn from, but a lock, an upgrade or the uid stamp replaces that record with a copy before the click lands. Equip and socket looked the record up only by that memory, and when the lookup missed they seated the item anyway and consumed nothing: the bag kept the piece, the slot or socket got it too, and its scrolls came along to a second host. Both now find the live record by its uid and refuse (“THAT ITEM MOVED — NOTHING WAS CHANGED”) when it is gone. The detail panel’s LOCK button left the panel pointing at the replaced copy (the cause behind #961’s scroll jump); the panel now follows the live record. Taking one piece off a stack no longer gives it the stack’s uid, and unequipped gear keeps its uid (only creatures used to). Gate tools/probe_mintaudit.py 1/9 → 9/9.
2026-09-28 — opener souls count toward POWER at half weight (player feedback relayed by D). SHIPPED IN 0.87. A player found that a soul reading “+N% Attack on the first turn” left POWER where it was, while HP and Defense souls moved it. Every conditional soul is evaluated per hit in battle and POWER reads only the always-on totals, so it scored 0. D’s ruling: first-turn-only effects “should add power points as if it was adding the stats static but the points are multiplied by 0.5”. POWER is now measured twice, without the opener souls and with them treated as always-on, and each opener adds half the difference. The opener family is first turn of battle, first hit/attack each battle and first hit against you each battle. An opener whose always-on form would not move POWER still adds nothing: team damage-dealt, damage-taken and debuff-duration souls (“Allies deal +5% on the first turn”, “Allies take −4% damage on the first turn”) are not in the POWER formula even when unconditional. This also fixes a drift: the team “+4% Defense on the first turn” soul counted at FULL on creatures when the last battle had ended on round 1 and at 0 otherwise. Combat is unchanged. The leaderboard’s server figure (src/power.js) applies the identical rule; gate tools/probe_soulpower.py 7/7, client equal to server.
2026-09-16 (e) — the victory screen’s tier badges stay on their own spoil row (#937, D). ON DEV (0.81). “At the bottom right above the continue button, there is a X icon that should not be there. It is on the ichor reward line.” The X was a tier-X badge belonging to an item row higher in the SPOILS list. When the badge replaced the text plaque in (h) of 2026-09-02 (#684), the victory row’s copy took the inventory tile’s corner placement (absolute, bottom-right), but the victory row is not a positioned box like the tiles are, so the badge left its row and settled at the bottom-right corner of the whole SPOILS block — the right end of the last row, which is always the Ichor line. Every tiered drop stacked its badge there; on a floor-117 run that is a pile of X. The badge is now an inline part of its own row, in the slot the roman text used, 30px as before. The currency row never carried a tier; nothing changed in what the rows are given. Gate tools/probe_937.py 2/4 → 4/4 on the painted DOM.
2026-09-16 (d) — the soul passive scope icons ride inline, bigger, and the stripes are gone (#919, D). ON DEV (0.81). “You added the icons for TEAM, PERSONAL, LOCAL to the soul passives in the last pass, but you added them to the vertical stripes where the text used to be. Remove those stripes and add the icons inline before the soul passive descriptions. And make the icons bigger.” #803 had painted the approved icon into the 28px coloured strip that used to carry the stacked word. The strip is deleted; the passive row is one flex line with the icon at its head, 34px (was 22), then the sentence. srScopeColor, the strip’s only reason to exist, is retired with its binding (#86). The 2026-09-09 ruling stands: the tooltip and the slot readout still say the word. Gate tools/probe_srscope.py re-aimed at the inline shape, 2/5 → 5/5 red-first, check 4 (the ruling) untouched.
2026-09-16 (c) — the character’s HP / ATK / DEF / SPD never end in an ellipsis (#918, D). ON DEV (0.81). “ATK under my character art shows 261… NEVER show … at the end of ANY numbers. Size the number font down instead so it can fit its container. Put the name on the top left of the container, shift the numbers down a bit and center them so they are using the entire container length.” Measured on his 1423×687: the cell is 118px, the number was capped at 62% of it (73px) with an ellipsis, and four Metamorphous digits already used 63px — a fifth digit was cut. The name now sits top-left in 11px Cinzel, the number owns the full width at the bottom, centred, and its size is a container-query rule (statFitFs: 26px until the digit count would not fit, then proportionally smaller) with text-overflow:clip — proven in the painted DOM at 4 to 9 digits, never clipped, 26px through five digits.
2026-09-16 (b) — gems lose the word under their socket number, runes say INC (#904 + #905, D). ON DEV (0.81). “For runes and gems, the ATK or DEF text underneath the number in the sockets is redundant. Remove it and increase the size of the number font instead” — corrected two minutes later: “do what I said only for gems. For runes instead of ATK or DEF or HP, say INC.” A gem’s cell shows the bare number, sized to its column by the #918 rule at an 18px cap (the old clamp(10px, 2.4cqw, 19px) resolved to its 10px floor on every cell — the column is 27px wide); a rune’s word is INC whatever stat it raises; a scroll keeps MORE / INC / its rolled stat exactly as #907 left it. Both copies of the cell — gear rows and creature tag rows — take the change.
2026-09-16 — a move’s buff and debuff icons ride the battle parchment in the equipment window (#899, D). ON DEV (0.81). “The attack up icon is invisible in the equipment window because it is blue. Show the icons on the moves with the same square parchment background as we have them on the battle scene appearing on their affected targets.” Reused, not reinvented: the battle chip is a 36×38 box painted with status_buff.webp or status_debuff.webp behind the icon. fxIconEls now names that same buff / debuff split on every effect icon it builds, and one CSS rule keyed on the window’s root paints the matching paper behind it — soul rows, creature move rows, the current-slot panel, the hover panel and the picker cards. The battle move selector and the creature sheet are outside that root and do not change.
2026-09-14 (d) — the header makes room, and no currency is hidden (#893, D). LIVE on release since 2026-09-14 (0.80). The word EQUIPMENT is gone from the header, the ACCOUNT box moves to the front beside the MENU plate and the title block’s clearance comes 216→180. That takes the currency strip from 379css to 587 against the ~494 its content needed: it was justify-content:center under overflow:hidden, so it was clipping BOTH ends — a Scrap count of 148 painted as “48” and Ink was cut off by the INVENTORY button. Only the right-hand loss ever looks like truncation, which is why the left one was never reported.
2026-09-14 (c) — move effect icons stop hiding behind the level number (#891, D). LIVE on release since 2026-09-14 (0.80). The level badge is absolute at z-index 4 over row content at z-index 1 and “Lv 100” measures 72px, so anything reaching the right end was painted over by an opaque number with a heavy shadow — measured, three effect icons each covered by their own full width. Two causes: the icon group also carried margin-left:auto, which pushed it into the badge every time. The icons join the left cluster and the line reserves the badge’s 80px; either fix alone looks like it worked.
2026-09-14 (b) — the equipped soul’s card matches the ones offered beside it (#890, D). LIVE on release since 2026-09-14 (0.80). Two producers built one card: the list built the candidates, a second built the panel above them, and only the list one ever asked about moves — although the soul lookup has carried the built move the whole time. The slotted card shows its move, power, cooldown and effect icons now, from the SAME object the candidates use, and the markup row is the candidate’s cloned rather than a second layout.
2026-09-14 — a soul equipped elsewhere still shows its details on hover (#892, D). LIVE on release since 2026-09-14 (0.80). A blocked card wears a full-card reason plate at inset:0, z-index 3, and it was taking the pointer, so the move row underneath never saw a mouseenter. pointer-events:none on the plate; clicking stays harmless because a blocked card’s pick is already a no-op. Both copies take it — the soul picker and the gear picker are one template.
2026-09-09 — the six move names that could never fit now fit (#796 / #519, D). LIVE on release since 2026-09-09 (0.73). A handful of move names were too long for their cell at any legible size, so they stayed cut short no matter what the resizer did — on a 1067-wide window “Hundred-Head Storm” had 55 pixels and needed 126, and the row had four pixels of slack once the gaps and padding were counted. The move row is now two rows inside the same height: the name has the full width to itself on the first line, and the power, cooldown and icons sit on the second. Each stays a single line, nothing wraps, nothing sits on top of anything, and the list is no taller than it was — every one of the six now fits at its normal size without being shrunk at all. Gate tools/probe_moverow.py 5/5, red 4/5.
2026-09-09 — text that has to shrink to fit no longer flashes abbreviated first (#796, D). LIVE on release since 2026-09-09 (0.73). The rule that names and numbers are resized rather than cut short with an ellipsis has been in force since August, but the resize ran a fifth of a second after the screen had already been drawn, so every name that needed shrinking appeared cut short and then corrected itself. Measured on the reporter’s own window size, two drawn frames carried the abbreviated form; now none do. The resize happens before the frame reaches the screen instead of after it. Still open, and a separate question: six move-name cells are too narrow for their names even at the smallest legible size, and stay abbreviated. Making those fit is a matter of the cell’s width or letting the name wrap, not the text size. Gate tools/probe_fitflash.py 3/3, red 2/3.
2026-09-07 — attacks show their name only on hover; the row keeps the numbers and icons (#788, D). LIVE on release since 2026-09-08 (0.71). An attack row now prints its power, cooldown, duration and effect icons, and the name appears while the pointer is on that row — the same hover the detail card already uses, so nothing new to learn. On a phone the row’s press-and-hold card is still how the name is read.
2026-09-07 — the equipped character soul plays its grade effect behind its portrait (#785, D). LIVE on release since 2026-09-08 (0.71). The soul crystals window and the equipped-soul rows already carried it; the equipment window’s own portrait did not. Same layer, same producer: Abyssal and above only, behind the art, click-through.
2026-09-06 (a) — the scroll rework (board 3.5, D). LIVE on release since 2026-09-06 (0.70). A scroll now decides its own roll. Its tier sets the band the enchantment is drawn from (the bands overlap at the edges); its grade bends the draw toward the top of that band; the item it is applied to no longer changes the number. Both tables live in codex/scroll_bands.csv and are read by the server, the client and this page. The scroll’s tooltip and the socket picker print its band and where its grade tends to land, and applying one plays the roll across the band and stamps it (PERFECT for the top slice). Calibrated so a same-grade scroll keeps today’s share of an item’s final stat within a few points at every tier and host grade; the working is in the client repo, docs/scroll-rework-2026-09-05.md.
| Scroll tier | Flat | Increased | More |
|---|---|---|---|
| T1 | 4–7 | 10.0–20.0% | 12.0–22.0% |
| T2 | 8–15 | 10.3–20.8% | 12.4–22.8% |
| T3 | 12–23 | 10.7–21.6% | 12.9–23.6% |
| T4 | 16–30 | 11.0–22.3% | 13.3–24.3% |
| T5 | 20–38 | 11.3–23.1% | 13.8–25.1% |
| T6 | 24–45 | 11.7–23.9% | 14.2–25.9% |
| T7 | 28–52 | 12.0–24.7% | 14.7–26.7% |
| T8 | 32–61 | 12.3–25.4% | 15.1–27.4% |
| T9 | 36–68 | 12.7–26.2% | 15.6–28.2% |
| T10 | 40–76 | 13.0–27.0% | 16.0–29.0% |
The draw picks a position 0–100% inside the band as u1/K.
| Scroll grade | K | Typical landing | Top quarter | Bottom quarter |
|---|---|---|---|---|
| Worn | 1.0 | 50% | 25% | 25.0% |
| Common | 1.2 | 56% | 29% | 18.9% |
| Uncommon | 1.45 | 62% | 34% | 13.4% |
| Rare | 1.75 | 67% | 40% | 8.8% |
| Epic | 2.1 | 72% | 45% | 5.4% |
| Legendary | 2.5 | 76% | 51% | 3.1% |
| Abyssal | 3.0 | 79% | 58% | 1.6% |
| Divine | 3.6 | 82% | 65% | 0.7% |
| Primordial | 4.3 | 85% | 71% | 0.3% |
| Void | 5.0 | 87% | 76% | 0.1% |
2026-09-05 (a) — Dismantle by tier (board 3.8, D). LIVE on release since 2026-09-06 (0.70). A fourth option in the Dismantling window beside “by grade”: pick any tiers (T1–T10) that have items, see the count per tier, confirm once. Same rules as by grade — only the bag you are looking at, never equipped items, never creatures, boss keys or tags in bulk. The by-grade footer also lost a stray escape code that had been printing as text.
2026-09-04 (c) — Dismantling and Filters become menu plates (#732, D). LIVE on release since 2026-09-06 (0.70). Two new plates in the menu set. The Filters plate wears the active-filter count as a small pill; the Dismantling plate shows selection mode as a red rim with the usual “stop selecting / dismantle N selected” words in a pill along its edge. On the soul bag the Hatch All button no longer stretches across the line — it keeps a plate’s width and the four plates sit to the right, as on the inventory tab.
2026-09-04 (b) — the equipment header is readable again (#731, D). LIVE on release since 2026-09-06 (0.70). The EQUIPMENT title had ended up under the Menu button; it now starts just to the right of it. The five material icons are twice the size with larger counts, and the INVENTORY and SOUL BAG plates are 56px (the breeding window’s SOUL BAG plate 64px). The header grew a little to fit; the player panel gave up the same space so the creature panels below are not clipped.
2026-09-04 (a) — the last three text buttons become menu plates (#707 sweep, D). LIVE on release since 2026-09-06 (0.70). A walk through every window found three cross-window buttons still drawn as text, all on the inventory / soul bag window: the tab switch (SOUL BAG on the items tab, INVENTORY on the souls tab) and BREEDING on the souls tab. All three now show the menu plates at the same size as the EQUIPMENT plate beside them, read from the same single source as the menu. No other window had one left.
2026-09-03 (q) — the auto-optimise finish message names its stat (#729, D). LIVE on release since 2026-09-03 (0.69). With Attack as the target, the message read “OPTIMIZED · 81,161 → 81,161” — an attack figure with no label, read next to the header’s POWER 99,019 as a lower power. The two never disagreed (checked on the reporting save, on the floor and mid-battle). The message now reads “OPTIMIZED · ATK 81,161 → 81,161 · POWER 99,019 → 99,215”, the same pairing the preview header got in (k). Gate tools/probe_optpreview.py 7/7.
2026-09-03 (p) — the lock gets its icon (#721, D). LIVE on release since 2026-09-03 (0.69). Two generated icons in the currency-icon style — a closed padlock and the same padlock open — replace the padlock glyph everywhere the item lock shows: the badge on a locked item’s tile (inventory, equipment slots, tags, creature cards) and the LOCK / UNLOCK button on the item detail window and the creature sheet, which is now larger and shows the lock closed when the item is locked and open when it is not. Prompts and raws in the wiki repo’s parking; the game ships 64px derivatives. Gate tools/probe_lockicon.py 4/4.
2026-09-03 (o) — the cross-window buttons become the menu plates (#707, D: “all the buttons need to be replaced”). LIVE on release since 2026-09-03 (0.69). The four old-style buttons — EQUIPMENT on the inventory and soul-bag window, INVENTORY and SOUL BAG in the equipment header, SOUL BAG on the breeding window — now paint the #688 generated plates as their faces, with the menu’s own treatment: the plate fitted whole, the rim glow on hover, the label kept in the page for readers. The inventory plate is 64px tall (its old button’s height); the header plates are 34px, the old chips’ height, so the equipment header stays at its pinned 52px. The equipment header’s material icons can no longer be squeezed by a crowded row. Gate tools/probe_plates.py 5/5 (2/5 before); probe_curicons 5/5, probe_eqheader 5/5 unchanged.
2026-09-03 (n) — the inventory header currency icons fill the header (#706, D). LIVE on release since 2026-09-03 (0.69). A follow-up to (d): the bare icons in the inventory and soul-bag header grow from 24px to 42px, nearly touching the header’s top and bottom edges, and the header is pinned at its former 49px so the bigger icons cannot grow it. Names and numbers are unchanged; the equipment strip and the dismantle rows from (d) are untouched. Gate tools/probe_invhdr.py 3/3 (1/3 before), pre-transform offsets on the painted window; probe_curicons 5/5 unchanged.
2026-09-03 (m) — the equipment header (#705, D). LIVE on release since 2026-09-03 (0.69). The title, POWER and ACCOUNT blocks sit flush left instead of 204px in; the account level bar is half its length. The ULTIMATE box prints only ULTIMATE “Name”: its measured figure (damage per target, DEF bonus, counter and stacks, rounds, echo) now rides the hover together with the ultimate’s authored effect sentence, and a click opens the same description window items use, with those figures as rows. A locked ultimate keeps its LEVEL n in the box and says so in the window. One helper produces both the hover and the window, so the two can never disagree. Gate tools/probe_eqheader.py 5/5 (0/5 before), measured on the painted window in pre-transform offsets; probe_itemdet 8/8 unchanged — the description overlay now lives at stage level, shared by the inventory and equipment windows.
2026-09-02 (l) — the optimiser ranks a socketed gem by the value it pays. LIVE on release since 2026-09-03 (0.69). Detail and gates on Sockets. Surfaced by probe_optgrade, whose seed paid one value under another grade's label: the pass evicted the stone, the plan ended 81,161 → 80,879 and the accept gate refused it — the “neither number goes backwards” rule held, the ranking underneath it did not. probe_optpaid 5/5, probe_optgrade 4/4, the (k) neighbours unchanged.
2026-09-02 (k) — the auto-optimise preview shows the work it is about to do (#708, #709). LIVE on release since 2026-09-03 (0.69). D's two reports were filed on 0.67 and replayed on his save of that minute: the pass was right — with attack selected it moved three Epic Runes of Aegis over Rare and Uncommon on three armour pieces, overall power 85,378 → 85,412, attack flat, exactly the rule above. The preview hid it twice. The header printed the chosen target only (“ATK 69,829 → 69,829”), and each socket chip printed name and tier with no grade, so an Epic replacing a Rare read as identical text on both sides. A real upgrade looked like a no-op and was cancelled; the save is unchanged from 12:39 to 12:44 UTC. The “better runes in my bag” were Defense and HP runes, which cannot sit on an Attack weapon under the same-stat rule (2026-07-28); the weapon's Rare had no Epic of its own kind spare. Three changes: socket chips carry the grade (“Elder Rune of Aegis · Epic T10”); when the target is not overall power the header carries the overall pair beside it; and the window says how many spare stones fit no piece the finished loadout wears. The comparison that decides “changed” still runs on the grade-aware identity, never on the label (#514). The (j) self-check narrowing stands — this evidence says the pass and the check were both right. Gate tools/probe_optpreview.py 6/6 (2/6 before), asserting on the painted window; probe_optfocus 8/8, probe_optfix 14/14, probe_optdup 8/8, probe_optconserve 5/5, probe_optimal_toast 13/13, probe_optcheck 4/4, probe_optcensus 9/9 unchanged.
2026-09-02 (j) — the auto-optimiser's self-check judges the stat it was asked about (#631), and its reports carry the loadout (#575). LIVE on release since 2026-09-02 (0.68). The “a spare stone beats one you wear” check compared every stat while the pass optimises one: both pairs it filed against an ATK pass were Defense stones, which an ATK pass correctly leaves alone (its 4-stone Defense plan moved the score 67760 → 67760 and was rightly refused). The checker now considers only stones whose stat feeds the chosen target; OVERALL POWER still considers all of them. And because the loadout that produced #575 was gone by the time anyone read it, every optimiser report now carries the loadout it judged — target, equipped pieces, every socket, the spare stones — so the next one can be rebuilt in the harness. Gate tools/probe_optcheck.py 4/4 (2/4 before); probe_optfocus 8/8, probe_optfix 14/14, probe_optdup 8/8, probe_optconserve 5/5, probe_optimal_toast 11/11 unchanged.
2026-09-02 (i) — the currency icons reach the battle-reward window (#699). LIVE on release since 2026-09-02 (0.67). The SPOILS list’s currency row (the Ichor gain) drew the plain dot every icon-less row draws; it now paints the currency’s own icon, bare and without a container per the icon ruling, beside the text. Item rows keep their tiles. Gate tools/probe_rwcur.py 1/3 → 3/3.
2026-09-02 (h) — the tier plaque becomes a generated badge, I to X. LIVE on release since 2026-09-02 (0.67). Player report #684: the roman-numeral text plaque looked poor. Ten square badges were generated from one committed prompt in the house icon style — a gold numeral on crimson enamel in a gold filigree frame — and D approved the set; masters live in the wiki repo’s parking (eight cut by remove.bg, IX and X cut locally when the credits ran out, flagged for D), the game ships 64px derivatives. The badge replaces the plaque at every surface that showed one — inventory tiles at 30px, gear, tag and picker tiles at 24px, reward tiles at 30px, the craft toast inline at 24px — same corner, same layer; the roman text survives only as the fallback for a tier outside I–X. Gate tools/probe_tierbadge.py 0/6 before, 6/6 after.
2026-09-02 (g) — the grade effects reach the rest of the equipment screen. LIVE on release since 2026-09-02 (0.67). Player report #683: the item grade effects now also play behind the creature portraits (by creature grade), the tag icons (by the tag’s grade), the equipped-soul readouts (by the soul’s grade) and the class soul crystals (owned classes, by the class soul’s grade). Each family gets the effect layer beneath its art or panel, clipped to the tile, with the Void hover aberration riding along. Gate tools/probe_eqgfx.py 1/5 before, 5/5 after, on live cases from D’s own save (a Void creature, four Divine tags, a Divine class crystal, two Abyssal-plus souls).
2026-09-02 (f) — the Void effect stays inside its tile everywhere. LIVE on release since 2026-09-02 (0.67). Player reports #682 and #685: the Void key and Void item/creature background effect bled outside the icon and painted over neighbouring tiles and the tile’s own content. The drift animation rotated and scaled the effect layer itself, and nothing clips a layer’s own transform; the spin now runs on an oversized inner layer inside the clipped box, so it rotates fully contained — in front of the tile’s backing, behind the art and the badges, exactly as the report words it. One change, every site at once. Gate tools/probe_voidfx.py 2/4 before, 4/4 after, measured on live effects from D’s own save.
2026-09-02 (e) — the six currency crops redone via remove.bg on D’s ruling. LIVE on release since 2026-09-02 (0.67). The local border-flood crops were rejected (the cropper ate into the bodies through dark edge channels); the remove.bg cuts at full resolution were approved — Wraithgold keeps its approved local crop. Full-size masters live in the wiki repo’s parking beside the raws; the game ships 64px derivatives (2–4 KB each) per the asset rule, since a tiny icon never needs the full file. Asset-only swap, zero code; gates 5/5 and 7/7 over it.
2026-09-02 (d) — Wraithgold gets its icon, and the currency icons shed their boxes and grow. LIVE on release since 2026-09-02 (0.67). D’s second ruling on the icon set: Wraithgold had been left out deliberately (premium, not a material) and now joins — a spectral gold coin, generated from the same style-anchored prompt family, cropped the same way, prompt and originals preserved in parking. And the icons no longer sit in bordered square chips: they render bare and larger — 24px in the inventory strip, 18px in the equipment strip (painted on the same 17px line so two rows still fit the header exactly and nothing moves or overlaps; the row gap tightened so a normal wallet stays on one row), 30px in the dismantle rows. The colour-matched glyphs remain only as fallbacks. Gate updated to the ruling first (red on the boxed tree), then 5/5; the material-strip gate 7/7.
2026-09-02 (c) — socket chips: the grade effect draws behind the socketable again, and the currency icons are cut to transparency. LIVE on release since 2026-09-02 (0.67). D’s report: on the equipment screen the grade background effects sat on top of the socketables’ icons instead of behind them like the gear icons. The effect layer paints above anything that does not claim its own level; the gear tiles have claimed one since the effects shipped, but the socket chips — on the gear rows and on the party creatures’ rows both — never did, so the effect washed over the icon, the fallback glyph and the number. All three now sit on the gear tiles’ level; the effect stays underneath, as designed. Separately, on D’s order the six currency icons were cut from their black backgrounds locally (the border-flood method used for earlier UI art; the glow is kept as soft transparency), so the chips now show the box’s own backing through the icon. Gates: tools/probe_sockgfx.py 2/4 before, 4/4 after; the icon and material-strip gates re-run 5/5 and 7/7.
2026-09-02 (b) — the five currency icons are in the game; the placeholder glyphs retire to fallbacks. LIVE on release since 2026-09-02 (0.67). Backlog 9.6: six icons (Scrap, Ichor, Dust, Essence, Ink, and the speed boost) were generated with the current image model from prompts style-anchored to the approved menu-tile icon set, and D approved all six. The five currency icons now paint inside the existing bordered chip boxes at every site that renders currency — the inventory header strip, the equipment header strip, and the dismantle-results rows, which step up from a bare glyph to the same boxed treatment so the family reads as one. Wraithgold keeps its glyph (premium, no icon authored). Originals and prompts live in the wiki repo’s parking under currency-icons/; the game ships 64px derivatives per the asset rule. The speed icon is shipped as an asset and takes its place with the premium-buff icon work (9.1–9.3). Background crops are pending the remove.bg key — the raws sit on flat black, which the dark chip boxes absorb; swapping in cropped art later touches no code. Gate tools/probe_curicons.py 0/5 before, 5/5 after, plus the material-strip gate re-run 7/7.
2026-09-02 — the equipment window’s header shows the five material counts. LIVE on release since 2026-09-02 (0.67). A player ask (backlog 3.9): the inventory header prints Scrap, Ichor, Dust, Essence, Ink and Wraithgold, the equipment window printed nothing, and grading is paid from there. D’s ruling: compact chips — the glyph and the number only, the name on hover, Wraithgold left out because it is premium currency, not a material. Both windows read the one wallet, so the numbers agree by construction. The strip sits between the title group and the INVENTORY button; a very rich wallet wraps it to a second row inside the header rather than growing the header or clipping. Gate tools/probe_eqmat.py 1/7 before, 7/7 after.
2026-09-01 — three soul passives that never fired now fire, and every “your
debuffs last longer / hit harder” soul now works against enemies. On release (0.65, 2026-09-01). Found by the consistency audit opened on a player’s report. The catalogue
sentence was read, the equipment window and the details window printed it scaled, and nothing in the
fight ever applied it: Cenotaph Gloves (“your buff-removal effects also deal 5% of
target max HP”), Ashbat Feet (“enemy healing received −5% while any ally
is Vampiric”) and Inkmantle Head (“enemy buffs expire 1 turn sooner when you
debuff them”). All three are wired: the extra damage lands whenever a strip or a steal actually
removes a buff (nothing removed, nothing dealt), the healing cut applies to every enemy heal the way
“healing received” applies to allies (drains and regeneration are outside both), and the
shortening applies to the enemy’s strippable buffs when an ally’s debuff lands
on it (a buff you give an enemy is not a debuff). Stage and grade scale them like every other
passive. Separately, and wider: the fight engine read the applier’s soul package
off the target, and an enemy has none — so every “your debuffs last +N
turns” and “your debuffs are +N% more potent” soul (about thirty) did nothing
against enemies. It reads the applier now. Gate: tools/probe_deadsouls.mjs, seventeen
rounds resolved on the live engine, red before and green after.
2026-08-31 — twenty-four “allies take −N% damage” souls paid exactly double their listed value; they now pay what they print. And the creature sheet's soul panel quotes the effective value. On release (0.65, 2026-09-01). Found by the consistency audit opened on a player's report that the codex, the soul bag and the equipment window gave three different answers. The engine's sentence parser carried two rules for the same shape (“take −N% damage” and “takes −N% damage”, the second a superset of the first added on 2026-07-26), so every matching Feet / Team soul was counted twice — a −5% soul paid 25% at stage 3 / Void against an authored 12.5%, eating half the team damage-taken cap on its own. The same double-count froze the on-screen text at the base number, which is what the player saw. One rule deleted, client and server alike; the one soul that uses the word “takes” is unchanged. This is a reduction for anyone wearing one of the twenty-four. Separately, the creature sheet's “soul · when slotted in passive armour” panel printed the raw catalogue sentence — the fourth copy of that defect after 0.47's fix here — and now scales by the creature's stage with an “· S<n>” tail. Gates: the parser dumped for all 400 souls before and after (exactly 24 rows change, only that bucket, each to its written number) and the rendered sheet read from the DOM on a booted client.
2026-08-29 — a focused optimise no longer skips the slots the focus cannot see (#631). On release. With attack selected, the score cannot tell two candidates apart on a slot that carries no attack — so every candidate tied, the incumbent won, and those slots were never dealt at all. That was reported once before and fixed for gear; the socket re-deal and the final accept step both kept scoring on the target alone, so an armour socket upgrade that raised overall power without moving attack was built and then thrown away for not moving attack. All three now follow the same rule: the target decides, overall power breaks the tie, a tie keeps what you already wear, and neither number may go backwards.
2026-08-28 — a weapon now says its class, always (#603). On release.
Weapons have always been class-locked and the game has always enforced it — a wrong-class main or off hand is
refused, and one already equipped is stripped at boot — but the item itself never said which class it belonged
to, so the only way to find out was to be refused. The detail window now carries a CLASS row beside SLOT
and TIER on every weapon, not only on one you cannot use. items.csv has no column called class;
its Family column already holds the class line on all 120 weapons and agrees with recipes.csv
120/120, no gaps and no disagreements. The label reads the same field the enforcement reads
(itemsDb.fam, what equipOk gates on), so it can never contradict the refusal. Verified against
the rendered screen rather than the payload — the #109 lesson, where a “fixed” object turned out to be
bound in no markup at all.
2026-08-28 — the no-work toast reads “ALREADY OPTIMAL” (D report #598). On release.
The spare-count suffixes (“N SPARE STONES BEAT NOTHING YOU WEAR” / “NO SPARE STONES”) are gone from the toast;
the spare-vs-worn arithmetic behind them stays, because it feeds the self-check that files an auto-report when a strictly better
spare is ignored — that branch keeps its own louder message. Same day, conservation re-proven end to end for D's #594: the
gate tools/probe_optconserve.py now presses the real APPLY (it had been opening the plan window and measuring an
untouched save), and five full open+apply runs on D's live save hold every gem, rune and scroll count by description invariant
(95 before, 95 after; the first run committed 67,049 → 67,470 with three planted better spares placed). The one-pass
counterexamples (#513, #575) could not be reproduced from today's live saves — both accounts reach the fixed point in one
apply now; the tripwire keeps filing with pair and plan detail for the next occurrence.
2026-08-25 — level requirements enforced: account level gates gear, the creature's level gates its tags, stones and scrolls test their host's level; the tag rows show ILLEGAL / LV n; the server's power figure mirrors it (D report and ruling). On release. Section above. Gate tools/probe_lvlreq.py 20/20, red first at 3/20 on a level-88 copy of D's save; parity gate green; registry entries in tools/patch_registry.py (APP, MK, SRV).
2026-08-17 — auto-optimize cancelled every run because the item census refused on its own key (on release). Report #175 and six [auto] rows from four accounts. A stone socketed on a worn instance was counted with a category derived from the socket index; the same stone folded into a displaced piece’s socks was counted with none, so a scroll moving between the two homes read as an item destroyed plus a different one created. All six live refusals are scrolls and all six balance exactly once the prefix is ignored. One derivation now, shared by both homes; the guard still refuses a real loss and a real duplication. Probe tools/probe_optcensus.mjs AFTER 9/9, BEFORE 4/4, with the six live diffs replayed as fixtures. New section above.
2026-08-13 — the auto-optimiser no longer places one stone twice (report #124, with [auto] #138/#142/#144 — three players' applies refused by the census in one day). On release.
The keep candidate wrote a socketed stone into its plan before checking whether the shared pool
still had the entry backing it — an earlier slot's fresh fill could take a stone a later slot was
wearing, and the later slot kept it anyway: the same stone in two sockets, its displaced neighbour
refunded, one becomes two. Value-ranking made the stolen copy always the higher-graded one, which is
why the measurements read as free grade upgrades; the census gate caught every bad commit, so
nothing ever duplicated live — the cost was the button refusing about four runs in five. The fix:
the pool entry is claimed first, and a stone another slot already took is simply not kept
— the fresh fill wins that socket honestly. Gate: tools/probe_optdup.py — the
collision shape injected onto the owner's real save; the unfixed build never lands an apply, the
fixed build goes 8/8 with every stone conserved by description across two passes and power pinned
monotone.
2026-08-13 — the party picker tells the truth about its creatures (reports #136 + #137). On release.
The currently-slotted mon now carries the same aptitude radar and type badges its challengers carry
— same creatures.csv row, same chart, cannot drift — and every candidate card gained the type
badges. Candidate levels derive from the creature's own XP instead of the hatch-time "Lv 1" label
that never updated, and the meta line prints the stage (grade · S# · Lv#), so a Stage-1
individual can never again read as its line's advanced form. Measured before fixing: all 36 rendered
cards already matched their record's current-stage art — the "Stage 1 images" were true Stage-1
individuals wearing an eternal Lv1 label. Gate: tools/probe_partypick.py 8/8 on the live
save.
inline-flex box at all badge sizes. The tag half was a two-sided gap — invMons[].tags[]
emitted no hasTier/tierRoman AND the tile had no badge and no position:relative
— the same dead-prop family as #86 and #128, inverted: nothing computed and nothing bound.
Gate: tools/probe_tagtier.py 13/13, 5/13 before, including a forced empty-slot case and a regression
pin on the eight gear badges. Dev only.optApply() never performed the “arriver unfolds” half of
D ruling other #4 that equipItem() does, so a scroll folded onto a bag record when the optimizer
unequipped a piece was destroyed the moment it equipped it again — the same class as the class-switch defect
that destroyed 41 stones. (2) Measured on a real save under ATK, the plan went
35,887 → 35,694 with every gear row reading “X → X”, because
fillFor rebuilt every host from a clean slate and a slot served earlier took stones a later slot was
already wearing; optOpen refuses a regressing plan with “ALREADY OPTIMAL!” while the next
click builds a different one, which is why it took several clicks. Fixed by making keep-what-you-wear a candidate
evaluated first (with the existing strict-greater-than, the pass is monotone by construction), by serving slots
biggest-host-base-first (a rune is a percentage of its host — rearrangement inequality; statFits
keeps each stat's hosts in their own relative order so one pass serves every stat), and by re-dealing the whole
loadout under several slot orders with hosts final, baselined on the sweep's own result. Real save: ATK
35,887 → 36,160, Overall Power +440, DEF +124, HP
+2,742, plan build ~142 ms. Known remaining limit: repeated clicks still find a little more
(36,160 → 36,452 over four) — a host-by-stone assignment problem across duplicate item instances, whose
fix needs the plan's bag accounting restructured because consume rides on it.
(3) The incumbent was scored with its enchant scroll and challengers without, because the socket
strip only removes ki 0/1; a challenger's own ki >= 2 scroll is now folded in, and it
cannot collide with the fill, which assigns ki 0/1 only. (4) sameFill
compared chips on an identity prefix with no |@gi instance suffix though keys have been
instance-keyed since bug #8, matching 80 keys where 24 belonged to the slot on a save with two Signets and
four Fangs, so it could never be true and every row reported “changed” on every run. Also recorded so
it is not re-reported: a T4 helmet outranking a T10 helmet is correct — socket capacity is
grade-driven via SLOTG and tier is not an input by design. New section above.monGenderKnown (which can answer
“unknown”) rather than monGender (which defaults to M and would invent one);
a name compare ends it so the order is total. Rarity ranks off the creatures.csv spelling
(Common, Uncommon, Rare, Super Rare, Super Super Rare) and explicitly not the RARITY
colour map, whose key is SSR and would silently drop the top two rarities. The comparator is gated on
both sides being creatures so the gear pickers sharing that code keep their order — the sort is stable, so a
no-op comparator is byte-identical. New section above.souls.csv constant (shipped in 0.47).
The passive list led with the raw Effect_S3 sentence and then arrowed to the effective value, so the
first number shown was the unmultiplied base and never moved when the soul staged up; the slot's hover tooltip
quoted the raw sentence too, so one soul showed two different numbers on one screen. Both now quote the effective
text, and the passive list follows it with the lever (stage × grade = combined). Only the magnitudes
parseSoul identified are scaled, so a condition threshold is untouched — “+5% Defense
while above 50% HP” cannot become “above 77.8% HP”. New section above.markup.html carried no tier binding at all — the tierBadge prop had been populated and unrendered since the inventory was written, so the fault was never occlusion. Probe tools/probe_tierplaque.py: BEFORE 4/7, AFTER 10/10.souls.csv constant while gearBonus paid stage × grade on top of it: measured, +4% on screen against 6.22 applied. Only magnitudes scale — the first cut also scaled a condition threshold, turning above 50% HP into above 77.8% HP, which a probe caught. Report #47, D ruling on placement. Verified 9/9 against gearBonus.BAG.concat(bagAdd), so a Divine sat wherever it was hatched. Report #46. Probe tools/probe_soulorder.py, seeded so bag order is not the answer and with a grade tie so the second key has to do work — BEFORE 5/5 (asserting it is not ordered), AFTER 7/7.grMult defaulted to 1 and upgrading a soul changed nothing (measured on a live save: persDefPct 4.2 at Worn, 4.2 at Void). Grade now reaches the passive path. The combined stage×grade surface is rebased to ×1.00 (S1/Worn) → ×2.50 (S3/Void) — nothing sits below 1 any more — with grade carrying 2.00× and stage 1.25×, reversing which lever held the wider range. LIVE on release since 2026-08-05 (0.32).EQUIP[gi].stat — the SLOT default, Attack for accessories — instead of statKind, the item’s own identity. The number shown was always the right one (a Crude Relic read +10 ATK, and 10 IS its HP base), which is what made it look like a display quirk rather than a wrong field. Every other surface already used statKind; only this one line disagreed.optScore() lifts the freeze while measuring a candidate, without which every candidate scored the same mid-battle. Page created — nothing owned the equipment window, the gear lock or the optimizer before this.