OPENU5 Home ES

These pages are generated from the project's internal documentation. References to source files and binary offsets are kept as provenance — so you can see where each claim comes from — but they are not followable yet: the engine is not published. Once it is, they will become links.

4 · The debug panel

The full QA drawer, section by section.

How to open it: the ` ` key (backquote) or F4, or ?debug=1 in the URL. And from ⚙ → "Debug (QA)" → "Open debug menu". It exposes window.__u5debug` for the console and for e2e.

DEV only. The SYSTEM menu row and the shortcut are announced only when the openDebug dependency exists: in the production faithful/shader skins nobody is told this panel is there.

New screenshots produced for this document on 2026-08-04 against a dedicated dev server. They live in docs/verdicts/debug-drawer/.


The whole panel

Debug drawer

A 360 px right-hand drawer, in plain DOM, with a field search box at the top and an accordion of sections below.

Census, measured live on 2026-08-04

#SectionFields
0Shortcuts3
1Teleport7
2Party23
3Resources9
4World / Clock15
5Plot / Quest45
6Transports7
7NPCs (dead / met)65
8Dungeon · cleared rooms17
9Moonstones32
10Inventory · Reagents8
11Inventory · Equipment (stock)48
12Inventory · Spells48
13Inventory · Potions / Scrolls16
Total14 sections · 343 fields

The four architectural rules

Before the tour, because they explain almost everything you see.

1 · Zero-rand, by construction

Every write goes through a single facade (debug/debugApi.ts) that writes straight into live state — the same struct that gets persisted to the native save — and never through Intents or anything that consumes the RNG stream. Each method is a handful of flat writes plus a refresh().

Why it matters: if the panel consumed rolls, using it would change the game invisibly, and any comparison against DOS would be contaminated. The invariant is asserted by a test (debug-api.test.ts) checking that liveSeed() does not move after any operation.

Even the teleport respects the rule: it replicates the deep-link recipe by setting position and, on small maps, enterMap + hydrateInteriorObjects — both deterministic.

2 · The registry is declarative; the panel knows nothing

registry.ts declares what can be edited ({label, get, set, widget}); panel.ts draws it. Adding a new field is one more entry in the registry, without touching the panel.

3 · It does not get in the game's way

The canvas stays playable with the drawer open, and the panel does not capture the game's keys except when one of its inputs has focus (stopPropagation only for events originating in the panel). Refresh is per field: get() is re-read and the control updated without rebuilding the DOM, so the teleport map and the focus survive.

4 · No cap is invented

An explicit mandate of the original brief. Every limit is derived from the field type in SAVED.GAM and the cap the game applies, or from a formula already derived from the binary, and it is cited in the constant:

CapValueWhere it comes from
Gold / food / experience9999u16 fields, but the game saturates them at CAP_WORD (add_word_capped, kernel 0x9C84)
Keys, gems, torches…99u8 fields, but the model treats quantities as two-digit values
Karma99u8 at 0x2e2, two digits in Ztats
STR / DEX / INT30, not 99⬅️ see below

The 30 is the pretty detail. The turn scheduler and the binary's thresholds operate as CONST − stat in unsigned bytes: a stat above 0x23 (35) wraps. With DEX 99 the party ends up immobile and untouchable — the game "works", and it is broken. So the panel's cap is 30: genuinely fast and inside the regime. The field label says so, and the derivation is in the internal record audit-byte-wrap.

It is the perfect example of why a state editor needs to know the binary: a control with max=99 would have been "correct" according to the data type and would have produced an irreproducible bug.


Section 0 · Shortcuts

Shortcuts

Three one-click buttons, right at the top because they are what gets used most:

ButtonWhat it does
Max everythingParty (HP/MP/stats/level/status) + resources + inventory to their caps + all special items (grapple, LB regalia, shards, spyglass, sextant, watch, badge, box, HMS Cape)
Best equipment for everyoneEquips each member with the best item per slot, derived from the attack/defence tables in the data — not from a hand-written list — respecting the two-handed rule
Full party at maxFills the party with real roster members (up to 6) and applies the other two

All three are pure logic in debug/shortcuts.ts (no mutation of Game, no DOM), and the facade applies them. All zero-rand.

Existing verification: docs/verdicts/debug-maxall/.

Shards and regaliaBadge and specials
maxall shardsmaxall badge

Section 1 · Teleport

Teleport

Two routes: the dropdowns (location + floor, or dungeon) and — the good one — the map picker.

The map picker

Teleport map picker

A large centred modal, because cramming a map into a 360 px drawer is not usable. Three tabs:

One click teleports to that exact cell. There is also numeric x,y entry + Enter. Hovering shows (x,y) and the tile name, and in a dungeon the subtype (e.g. "Room #8"). It remembers the last destination.

The detail that decides whether it can be trusted

Every map is rendered from LIVE core data, never from stale JSON. The town grid comes from getActiveMap(world, loc, floor).tileAt — the same source the game uses to decide where you can walk. A picker fed by an exported JSON would be a map of another version of the world, and its errors would look like game bugs.

Two UX decisions

And the one action marked dangerous

The section has an "Enter dungeon (real flow)" button with a red badge:

"REAL dungeon entry — may consume RNG from the stream (not zero-rand)."

It is the declared exception to rule 1, and the panel marks it visually instead of hiding it. A state editor that does not distinguish "write a byte" from "run the game's flow" is an editor you cannot trust to set up a comparison.

Screenshots of teleporting to the five destination types: docs/verdicts/debug-teleport/ (01-overworld, 02-overworld-zoom, 03-underworld, 04-town, 05-dungeon), and of the tiling and the gem fix in docs/verdicts/debug-picker-tiling/ and docs/verdicts/debug-picker-gemfix/.

Before the gem fixAfter
gem picker, beforegem picker, after

Section 2 · Party (23 fields)

Party

A character selector and, per member: name, gender (0x0B/0x0C), class (the nine letters of "AMBFDTPRS"), status (G/P/C/S/D), HP, max HP, MP, level, experience, STR/DEX/INT, partyStatus, months at the inn and every equipment slot.

The labels carry the warning attached: "Strength (max 30 — >35 wraps the scheduler, audit-byte-wrap)". The derivation travels with the control, not in a separate document nobody will open.

Also: party size (1..6) and active character.


Section 3 · Resources (9 fields)

Resources

Gold, food, keys, gems, torches, skull keys, magic carpets, karma and the other global counters. Each with its derived cap (§"No cap is invented").


Section 4 · World / Clock (15 fields)

World / Clock

Year, month (with the six real names: Deep Winter, Awakening, Time of Sowing, Season of Sun, Time of Harvest, Fruitful Winter), day, hour, minute, wind (Calm/N/S/E/W), transport, and the runtime fields the save persists:

That the two latched moon phases are editable is not a whim: they are what determines where a moongate takes you, and without them you cannot set up the scenario for a moon-travel comparison.


Section 5 · Plot / Quest (45 fields)

Words of power spoken (×8), shadowlords killed (×3), in-doom, game-won, Blackthorn… and every live key in questFlags, raw, including the ones nobody knows how to derive (marked "underived").

Total enumeration, not curated — an explicit mandate from the user. The difference matters: a curated list only shows the flags someone already understood, which are exactly the ones you least need to inspect.

Trade-off: you can write a flag nobody understands

The price of enumerating everything is that the panel offers editable controls over underived keys. You can see them — which is the point — but you can also write them, and writing a bit whose meaning is unknown produces a state the game may not know how to read, with no warning at all.

The section marks them "underived", and that label is all the protection there is. It is consistent with the rest of the panel (this is a QA tool, not a fool-proof interface), but it is worth knowing that the noise is not only in the reading.


Section 6 · Transports (7 fields)

Transports

transportTile as a raw byte, plus the frigate and wind state: hull, skiffs, sail direction, wind-drift counter, HMS Cape toggle. And a QA button to empty the enemy pool.

Existing verification: docs/verdicts/debug-save-editor/01-transportes.png.


Section 7 · NPCs (dead / met) — 65 fields

The largest section: by (location, NPC index), a location dropdown and 32 + 32 checkboxes (dead / met).

⚠️ Careful: the index is not the position in an array — it is an index belonging to the NPC. Confusing them means marking the neighbour instead.

Existing verification: docs/verdicts/debug-save-editor/03-npcs-dead.png.


Section 8 · Dungeon · cleared rooms (17 fields)

Cleared rooms

The bitmap of already-cleared rooms: 7 slots × 16 rooms.

And an oddity of the binary that the panel reflects instead of papering over: Deceit and Despise collapse into slot 0. The eight dungeons share seven slots. The label says so — "Deceit / Despise" — and the derivation lives in dungeonClearedBitIndex.

An editor that had "fixed" that by giving eight slots would have written bits the game does not read.

Existing verification: docs/verdicts/debug-save-editor/02-mazmorra-salas.png.


Section 9 · Moonstones (32 fields)

Moonstones

Position and state of the eight moonstones.


Sections 10-13 · Inventory (120 fields)

SectionFieldsScreenshot
Reagents8reagents
Equipment (stock)48equipment
Spells48spells
Potions / Scrolls16potions

Every cell is a numeric field labelled with the item's real name, read from InventoryDetails.json. There are no bare indices forcing you to consult a separate table.


The guard that stops this falling behind

The save editor keeps a coverage manifest — COVERED_STATE_KEYS and EXCLUDED_STATE_KEYS — which is the source of a completeness test: if someone adds a new field to GameState and does not put it in either list, the guard goes red.

That is the difference between a debug panel that ages well and one that six months later edits 70% of the state with nobody knowing which 70%. The decision it forces is not "add the field to the panel", but the cheaper and more useful one: decide and declare whether it travels or is excluded.


What this panel is not