Nothing is useless. Every drop dismantles into materials. Every material crafts the next tier.
craft). /api/craft answers 410 and the client has no forge. Materials (Scrap, Dust, Essence, Ink) now have one use: Upgrading — grading and enhancing what drops; drops and dismantling are on Droptables. This page forwards to Upgrading in 8 seconds unless you opened it as crafting.html#archive. It is kept as the record of the system; recipes.csv survives only as the weapon class-lock’s source. See the Changelog.codex/recipes.csv with an explicit material bill. There is no cost formula anywhere in the crafting system — change a recipe by editing its row. Output grade defaults to Worn — the forge can deliver up to Rare for a surcharge derived from the grading ladder at the recipe's tier (2026-07-31), so forging is never cheaper than grading it yourself; the climb beyond is the separate Scrap-fed gamble on the Upgrading page.| Recipe | Output | Category | Slot | Tier | Lvl | Materials |
|---|---|---|---|---|---|---|
| Loading recipes… | ||||||
There is no combining. Gear grade is raised only by spending resources on an attempt — Scrap for gear, Dust for gems, Essence for runes, Ink for scrolls, all authored in codex/grading.csv at the item's tier (360 tier-scaled rows, 2026-07-31). A failed attempt consumes the currency and leaves the item exactly as it was; nothing is ever destroyed. Those are the same resources dismantling yields, so every drop feeds the ladder. See the Upgrading page for the full table.
Authored per row in codex/dismantle.csv — 515 rows. Gear: every (grade × tier) pair. Gems / runes / scrolls / tags: every (tier × grade) pair. No yield formula.
| ID | Category | Grade | Tier | Yield |
|---|---|---|---|---|
| Loading… | ||||
There is no separate reroll action and no Ink cost. Applying another scroll over a socketed one is the reroll — it draws a fresh class from codex/enchants.csv and a fresh value from codex/scroll_bands.csv, and the enchantment it replaces is destroyed. The value is set by the scroll, not the host: the scroll’s tier picks the band for the class drawn, and its grade bends the draw toward the top (position = u^(1/K), K 1.0 Worn … 5.0 Void). The host item’s tier and grade do not change the number (3.5 scroll rework, 2026-09-05). Scrolls can never be unsocketed; gems and runes always return to the bag intact.
Scroll legality (2026-07-31): a scroll is a die, not a fixed stat — the socket stat-match rule judges its rolled result. A flat Attack/Defense/HP roll must match the host stat exactly like a gem; item-local % rolls are host-relative by definition, and Increased/More/utility rolls and unrolled scrolls fit any host. (Before this ruling the catalog text was unreadable as a stat, so every socketed scroll in the game read ILLEGAL — bug #3.)
Socket identity (2026-07-31): every equipped item is its own instance — two copies of the same item never share sockets. Unequipping or swapping an item returns its gems and runes to your bag (D ruling 2026-07-31); only the scroll stays bound to the item, travels with it, and returns to its socket when the item is equipped again. Dismantling a socketed item refunds its gems and runes the same way — the scroll is destroyed with it. Promoting an equipped item carries only that copy’s scroll across the grade.
| Rule | Requirement |
|---|---|
| Character gear | Tier N → character level ≥ (N−1)×10 |
| Creature Tags | Tier N → creature level ≥ (N−1)×10 |
| Enhancement socketing | Enhancement tier ≤ gear's tier |
| Gem/rune removal | Returns the gem or rune intact and frees the socket — enhancements are permanent and behave as portable stat blocks, so one gem can serve every item you own in turn |
| Scroll removal | Not possible. A socketed scroll can only be overwritten by applying another, which destroys the enchantment it replaces |
| Scroll roll timing | The enchantment rolls when the scroll is applied: class from codex/enchants.csv, value from codex/scroll_bands.csv (scroll tier band, scroll grade K). It can only take the stat the host item itself carries; the host’s tier and grade do not enter the value |
D order 2026-08-21 (LIVE on release since 2026-08-21 (0.58)): every dismantle door refuses a Mutant creature (cat Monster with mut:true). One shared gate does it — dismYield returns null for a Mutant, the same mechanism that protects class souls — so every door is covered without its own check: the creature sheet withholds its DISMANTLE button, the hammer refuses the mark with “MUTANT — mutants are never dismantled” (never the generic no-salvage toast, which would hide the reason), “by grade” and “all filtered” sweeps skip them, Adam and Eve treats them like levelled creatures — neither dismantled nor a bucket’s keeper, spared not favoured, and its window says how many it is leaving — and execDism’s final filter pays nothing for one even if a stale reference forces it into the marked list. The ops door POST /api/backup/dismantle carries its own copy of the refusal, because it is the one dismantle that runs server-side without the client’s gate.
The server keeps paying a Mutant claim on purpose. src/dismantle.js — the Worker’s pricing copy — deliberately does not carry the refusal: a stale release client that still dismantles a Mutant must have its claim verify and pay (additive-only backend rule), and withholding pay would protect nothing anyway — the destruction happens client-side, so the protection lives client-side. The parity suite pins this as an allowed divergence (gate B4) so it can never be “fixed” backwards. The item lock (D 2026-08-25) is a second divergence of the same kind: dismYield returns null for a locked item, while src/dismantle.js has no lock check and prices one; the parity suite does not pin this one yet.
Client: the gate in dismYield, the hammer refusal in dismSelToggle, the count and sentence in adamEveSplit and the Adam-and-Eve window, all in play/app.js. Worker: the hit predicate in /api/_internal/bulk_dismantle. Probes: tools/probe_dismmutant.py 9/9 (every client door, on a live save, red 1/9 against the build it replaces), tools/probe_dismmutant_ops.py 4/4 (the ops door, red 2/4 before), tools/probe_dismantle_parity.py 10/10 with the divergence pinned.
A completed dismantle opens a centred list panel: one row per resource acquired, a socketables-returned line where sockets came back, and a close button. It is contained by construction — an inset:0 overlay centring a card whose width is capped in percent of its container, so no yield string can push it past the edge however many currencies it names. The rows come from a fixed five-currency map (Scrap, Ichor, Dust, Essence, Ink) in dismDoneRows, summed by dismTotals over the same five keys, so a currency added later needs both edited before it appears.
It replaced a single-line toast, and the toast was a real defect. The line was white-space:nowrap, and a full sweep names five currencies plus a refund count. Measured on a 430 px phone: that line drew 963 px wide and pushed documentElement.scrollWidth from 430 to 1178. A document wider than the viewport is answered by the browser zooming out — which is exactly what “the notification is resizing the game window” describes. Even the short breeding-egg line overflowed, at 582. The toast itself now sizes to its content with a percent cap and wraps, so it can no longer widen the document whatever text it is handed.
Overlays belong to the stage, not to the device. Six panels were position:fixed, which escapes the zoomed 1920×1080 stage and sizes them against the device viewport instead. An authored 1020 px card then becomes 94% of a phone while its children keep their authored pixel widths — which is why the devour window scrolled sideways: three flex:none cards at 330 + 66 + 330 plus gaps need roughly 770 px in a box near 400. The devour row now shares whatever width exists (flex:1 1 0 with min-width:0) and keeps its authored size wherever there is room, so a desktop is unchanged.
Client: dismDone and dismDoneRows in play/app.js; the results panel and the devour row in play/markup.html. Probe: tools/probe_popupfit.py (8/8, measured on a 430 px viewport).
Build 0.47. The popup shown on a successful craft carried the item name only — no grade, no tier — on a screen whose whole purpose is telling the player what they just made. Two additions:
tierRoman / romanN). There is one numeral table in the build, so the popup and the tile
cannot disagree — which is the point of reusing the helper rather than formatting a second time.The forge/upgrade toast has no tier of its own, and now says so. A grade attempt operates on an item
whose tier is unchanged by the attempt, so there is no tier for that toast to report. It states that explicitly instead
of leaving the field undefined — an undefined field is the shape that eventually renders as
undefined or as a blank plate, and it also invites a future session to “fill it in” with a
number that means nothing.
Build 0.47. The scroll's effect string ended “Rerollable for 3×T Ink”. No ink reroll exists anywhere in the build, and three separate facts say so:
kind === 'enchant' charges no ink and has no reroll
branch. There is nothing to call.The client sentence is removed. The rule itself is unchanged — only the false claim about how to invoke it is gone. This is the standing lesson from the class-switch confirmations: UI text is a claim about a code path, and a price quoted for an action that has no code path is a promise the game will refuse to keep.
codex/items.csv, in the Effect column of the Scroll rows
ITM-0381–ITM-0390 (all ten tiers), reading
“…; rerollable for 3×T Ink”. Removed 2026-08-10 as a pure suffix strip: the clause
contains no comma, quote or newline, so the field stayed unquoted and no other column moved — verified by
re-parsing, 606 rows and 13 columns before and after, exactly 10 changed cells, all in Effect, CRLF line
endings preserved. The Scroll Effect now reads
“random Local enchantment roll — flat Attack/Defense/HP or item-local % Attack/Defense/HP”.
Nothing parses that clause for a stat — scroll legality has been judged on the rolled result since the
2026-07-31 ruling — so this is a text-only change.
ITM-0395 (the Ink material) authors
“scroll crafting + grading + rerolling”. That one is not the false price — ink genuinely
buys the replacement scroll, and applying a replacement is the reroll — but the word invites the same
misreading and is worth a ruling.2026-11-03 — archive note and forward (D 2026-11-03 codex review; dfmc-client lane codexreview-b). WIKI ONLY. The page says where the materials went (Upgrading, Droptables) and forwards there after 8 s; #archive keeps it open. hub.html and other-systems.html link it as the archived record, not as a live system.
2026-10-23 — the “dismantle.csv not loaded” banner on every boot is gone (lane small-fixes-1). The bag grid prices a piece for the first-steps arrow on its first render, which runs before the codex loader has built the dismantle table; the price check took the empty table for a missing CSV and raised the data banner once per session. It now stays quiet while the codex is still loading and warns only when the loader has finished and the table is still empty. No yield changed. Gate tools/probe_dismload.py.
2026-10-01 — CRAFTING IS REMOVED FROM THE GAME (D, Rulings board card craft: “Crafting was removed from the game. Wipe it from the code.”). LIVE on release since 2026-10-23 (0.89). #810 had taken the CRAFTING button off the menu, but POST /api/craft stayed open and minted: it booked the bundle with no balance wall and inserted items with origin craft, so a 0-Scrap account could craft 50 items, dismantle them and keep 7,550 Scrap (the free-Scrap loop, live since 0.58). The worker door now answers 410 crafting_removed and forwards nothing; the player object’s craft handler, the recipe pricing and the craft premium are deleted. The client’s forge window, its picks, its pricing and its door client are deleted too. Kept: recipes.csv stays authored and loaded — its Family column is the weapon class-lock’s fallback; dismantling is not crafting and is unchanged; ledger rows with source craft and items with origin craft stay readable, and those items still dismantle. No data migration. Everything above this entry describes the system as it was. Gate tools/probe_craftout.py.
2026-09-30 — the ops bulk sweep refuses what the client refuses (review lane Q, F125). On dev. /api/backup/dismantle (bulk_dismantle) copied only the mutant refusal. It now also skips locked items, Keys (zone keys are minted Common, inside the default Worn/Common/Uncommon sweep), class souls, and rows whose yield is null. Before, those rows were deleted for nothing. Gate tools/probe_laneq.mjs 0/15 → 15/15.
2026-09-30 — the forge’s tag requirement and its picks (client review F160, F161, F168, lane P). On dev. A Creature Tag recipe’s Equip: line was coloured against the PLAYER level; tags are worn by a creature and gate on that creature’s level. The line now reads Equip: creature level N and is green when either party creature has that level. Picking a type, class or tier in the forge now plays the select cue (it had been wired to a recipe picker nothing calls). Dead v0.5 handlers (the hatchery-upgrade branch of forge(), hatchTracks, the category/search/hover handlers) are removed. Gate tools/probe_review_lanep.mjs.
2026-09-30 — a craft the forge never answered is not “nothing was crafted” (client review F6-018). On dev.
The craft door books the spend and mints the items before it answers, and answers a repeated idem with the first execution’s uids. The client gave up at 10 s, said “COULD NOT REACH THE FORGE — NOTHING WAS CRAFTED”, and the next press rolled a fresh idem: two bookings and one orphaned mint. Now an unanswered ask is re-asked once with the SAME idem; if the forge still does not answer the craft stays pending and the toast reads “THE FORGE DID NOT ANSWER — PRESS AGAIN: IT NEVER CHARGES TWICE”. The next press reconciles that idem first: minted → delivered (debited once), refused → nothing was booked and the press forges fresh. “NOTHING WAS CRAFTED” is said only when the forge answered and refused. The pending craft lives in memory: a reload before the next press leaves the minted rows server-side. Client only. Gate tools/probe_f6_018.py 1/6 → 6/6.
2026-09-30 — review P6 Q3 (settled half): the forge reports an overdraft. ON DEV. WATCH-ONLY.
When a craft would book any currency of its bundle past zero, the craft door writes a craft_overdraft anomaly row naming the recipe, the quantity and each short currency with what was held and what was booked. Nothing is refused and the booking is unchanged — the ledger still records the spend; whether the door then refuses is D’s open Q3, after a week of live flag data. An idem replay does not flag twice. Probe tools/probe_worlddoors.py.
2026-09-30 — review drift fixes: F127, F155, F329. Codex only.
The page caught up with the code; no rule changed. F155: scroll rolls name scroll_bands.csv as the source of the value (scroll tier band, scroll grade K); only the class comes from enchants.csv, and the host no longer changes the number. F127: the item lock is named as a second client/server dismantle divergence beside the mutant rule (not yet pinned by the parity suite). F329: the dismantle-result panel reads a fixed five-currency map; the claim that a new currency appears untouched is withdrawn.
2026-09-30 — MAX crafts at most 50, and a refused craft says why (client review F114). On dev.
The server forges 1 to 50 of a recipe per call, but MAX asked for everything the wallet could pay, so a big wallet
(e.g. 2,000 Scrap on a 25-Scrap recipe = 80) was refused every time. MAX is now capped at 50. Every refusal also read
COULD NOT REACH THE FORGE; a refusal the server answered now reads THE FORGE REFUSED: <reason>, and only
a forge that did not answer says it could not be reached. Nothing is crafted or spent on either. Gate: tools/probe_review_c13c09.mjs.
2026-09-30 — a dismantled key is priced from the server’s own record; a blocked dismantle tells the server nothing (client review F122–F124). On dev.
A Key’s yield follows its floor, and the server read that floor from the claim: a row carrying
keyFloor:100, or a Grasslands Key renamed Flaming Hellscape Key, paid 60 Scrap instead of 6, on the dismantle
call and on the save-door queue alike, because the object compared only cat/grade/tier/qty. The priced row is now rebuilt
from the wire fields alone, a key’s floor comes from items.csv by name, and a key claim whose name is not the
one the server holds is refused (held-mismatch / grant-mismatch). The same lookup prices the Void Apex
keys (ITM-UKEY-*) at their Floor 10x the way the client always did — they were paying the floor-10 price.
Client side, the dismantle call and the claim queue used to fire before the gem/rune refund room check, so a
dismantle blocked for bag room left the item in the bag with its yield already booked; nothing reaches the server now until
the room check passes. Gate tools/probe_dismforge.py red 4/10 → green 10/10 (the painted client plus the real
worker). Honest yields do not move.
2026-08-28 — the grade anchor: forged upgrade claims are refused (the #623 exposure, D: “very serious. Fix it”). On release.
The grade and soul-grade roll doors resolved odds and price from the client-claimed from/to/tier. The server now keeps its
own per-item record: the first roll on an item id sets the anchor (from the grant row when one exists; otherwise the claim
is grandfathered and flagged roll_unanchored — a new detection family, watch it), every settled success
advances it, and any claim that disagrees answers grade_anchor with the true grade so an honest, stale client
resyncs instead of guessing. Both the single roll and All-in obey it; replays and unsettled attempts were already safe.
Gate tools/probe_rollanchor.mjs 6/6 on the real worker, red first (1/6): grandfather accepted+flagged, the
forgery refused on both doors, the honest walk unimpeded. Stored data untouched; legacy items anchor on their first roll.
2026-08-28 — #623 audited: the All-in math is fair by construction; nothing changed.
D: “All in seems too generous in terms of when it succeeds.” Reviewed against the build: the burst is
afford = min over the price bundle of floor(ledger-bounded balance / cost) attempts (capped 250), one keyed
draw of that many lanes, success at the first lane under the row's Success_Pct, charging only the attempts
used — distribution-identical, attempt for attempt and coin for coin, to pressing the single upgrade until it lands.
No compounding, no reroll surface (the draw key is server-advanced per attempt), and no skipped penalty: all 360 grading
rows author “no destruction, grade unchanged” on failure, so a burst has nothing to be gentler about. If success
still reads too frequent, the lever is the authored Success_Pct ladder itself (early rows are 100%) — a
balance call on grading.csv/soul_grading.csv. One exposure named while reading: the roll door
resolves its row from the CLIENT-claimed from/to/tier; honest play gets honest odds, but gear grades are client-held, so a
forged claim buys high grades at low-grade odds and prices — recorded with the socket-identity family (#591, Jeff) as
the enforcement dependency. An empirical distribution run (thousands of harness bursts against the geometric law) is
specified for a next session if the construction proof is not enough.
2026-08-21 — mutants are never dismantled (D order). LIVE on release since 2026-08-21 (0.58).
Every dismantle door now refuses a Mutant creature: the sheet button, the hammer, by-grade, all-filtered,
Adam and Eve (which counts and names what it is leaving) and the server-side ops sweep. One shared gate
(dismYield → null) covers every client door; the ops door carries its own copy.
The Worker’s pricing copy deliberately keeps paying a stale client’s Mutant claim — the parity
suite pins that divergence as gate B4. New section “Mutants are never dismantled” above. Gates:
probe_dismmutant.py 9/9, probe_dismmutant_ops.py 4/4,
probe_dismantle_parity.py 10/10.
2026-08-13 — dismantle earns are booked (the economy BOOK phase's second brick). LIVE on release since 2026-08-21 (0.58).
Every verified dismantle now books its whole authored yield into the permanent wallet ledger
— one row per currency, keyed by the item's id, and an item dismantles once ever, so a replayed
claim books nothing. Both acceptance paths book: an item the previous save carried, and an item
verified against its own grant record. The save answer reports what was booked. Nothing is refused
on balance; the ledgers count for real from the next reset. This also closes the dismantle blind
spot in the Ichor ceiling for good: the earn is in the permanent record whether or not the ceiling
ever looks. Gate: tools/probe_dismwindow.mjs 10/10 — a Worn tier-1 piece books
exactly its authored 3 Scrap, a scroll its 10 Ink, and the replay books nothing.
2026-08-13 — scrap what you just made (report #153). LIVE on release since 2026-08-21 (0.58).
An item acquired and dismantled inside one save window was absent from the previous save, so an
honest craft-and-scrap was refused its yield ("absent-from-prev") — caught by a player minutes
after 0.50 shipped. The grant records introduced in 0.50 are exactly the missing verification
source: an absent-from-prev claim now verifies against the server's own record of the mint,
identity checked on the fields the grant carries, quantity capped at what was granted, and
acceptance spends the grant — one yield per grant, forever, however often a claim replays.
Forged ids still die where they always did, and the ordinary previously-held path is untouched.
Gate: tools/probe_dismwindow.mjs 7/7.
2026-08-13 — zone keys are recorded grants, and the first-clear key comes from the server (report #143, the keys half). LIVE on release since 2026-08-21 (0.58).
Two defects in one lane: the server-rolled chance key minted an id for the answer but never
recorded the grant — it existed for one network hop, then every honestly banked key read
"unsourced" on the next sync; and the first-clear key was a pure client fabrication with no
record at all. Both now record at grant time, the same way gear and eggs record. The first-clear
grant is server-side: the battle event carries the claim, the server honours it only on the floor
directly below an authored boss floor, and once ever per zone — however many times the
event replays, one key exists. Old clients keep the local mint until 0.50 ships. Gate:
tools/probe_keygrant.mjs 5/5 on live data (recorded-grant sync, server first-clear,
once-ever, wrong-floor refusal).
2026-08-13 — the forge asks the server first (report #143; the economy BOOK phase's first brick). LIVE on release since 2026-08-21 (0.58).
Crafting previously had no server presence at all: the client resolved the recipe, debited the
materials locally, and minted the item locally — which is why 21 of one player's 22 "no grant
record" items were recipe outputs. Now /api/craft recomputes the bundle from the same
authored rows (Materials plus the grading expected-cost ladder times the craft premium), books
every currency into the permanent wallet ledger — the first non-Wraithgold rows — and mints
the items with server-issued uids, which is exactly the grant record the inventory guard checks.
Replays under the same key answer the same items and book nothing twice. The bench crafts nothing
when the forge is unreachable. Old clients keep the local path until 0.50 ships. Gates:
tools/probe_craftdoor.mjs 10/10, tools/probe_craftclient.py 9/9.
kind === 'enchant' charges no ink and has no reroll branch, and the sibling description string for the
same item already said a scroll can only be replaced, never removed, with no ink claim. The client sentence is removed;
the rule is unchanged. Shipped in 0.47. The second copy went the same day: the identical
clause in codex/items.csv, Effect column, Scroll rows
ITM-0381–ITM-0390, was stripped 2026-08-10 — 10 cells, all in
Effect, row and column counts and CRLF endings unchanged.position:fixed, which had been sizing them to the device rather than the stage, and the devour row now shrinks to its box instead of scrolling sideways. D report + #51 + #48. Verified 8/8.