Skip to content

feat: integrate the Door Randomizer (DR and OWR) - #446

Draft
kqesar wants to merge 190 commits into
masterfrom
door-randomizer
Draft

kqesar wants to merge 190 commits into
masterfrom
door-randomizer

Conversation

@kqesar

@kqesar kqesar commented Oct 29, 2026

Copy link
Copy Markdown
Owner

The Door Randomizer (DR, and its Overworld fork OWR) in full, as the 2026-10-29 study plans it: about half of the alttpr races over the past year. Draft until every step is in and green.

Done (study steps 1–3):

  • Autotracking accepts DR (DR156_…) and OWR (OR…) ROMs. Their race flag is vt's pair at the same address (DR 1.5.6 Rom.py:1113, 1483-1484), so an unreadable flag keeps Race Mode on. Outside Race Mode it reads DR's and OWR's settings. "What I read" names the version and says when it is not 1.5.6, the pinned one.
  • Run types Door Randomizer and DR Crosskeys add DR's settings as selects. Anything not modelled yet is named on the map.
  • Seed import reads an avianart permalink's meta or a Hyrule Town Hall .bps. Fixtures come from record-dr-seeds, cut to the bytes the decoder reads.
  • New locations come from DR's tables via import-dr, which writes no ROM bytes: 33 key drops, 812 pots, 714 enemies, 32 shop items and 9 take-any items. They show in the Checks list and the dungeon counters. Flags are read at $7F:6018/$7F:6268/$7F:64B8 (z3 d0f4651 sram.asm:390-392), each table behind a switch set from the run's settings. Race Mode reads a table only while its mode is shuffled (v2.0: "only considered locations in modes where they are shuffled"). Rooms and transitions are never read.

To do: 4. DR's own logic with vanilla doors, including its key rules, held to DR's tests. 5. DR's entrance pools. 6. Door shuffle, with door notes. 7. The OWR overworld shuffles. 8. The review of DR spoiler logs.

Verified so far: lint, typecheck, test:coverage, check:docs, build, e2e (plus door-randomizer.e2e.ts). Progress notes: ~/alltp-handoff/dr-progress.md (local).

kqesar added 10 commits October 29, 2026 16:02
About half of the alttpr races of the past year ran on DR or OWR ROMs,
which the game check refused for their DR/OR titles. Their race flag sits
where vt's does, so the probe and its fail-closed answer stay the same.
…nd shops

DR 1.5.6, which most DR races of 2026 ran, adds locations vt has none of.
A script reads them from DR's own tables (never its ROM bytes), so the
tracker can count and autotrack them as the seed's settings make them.
…slots

Outside Race Mode the ROM tells DR's door, drop, pottery and shop settings
and whether it is a Mystery seed. The three tables DR keeps beside the save
are v2.0's locations only in modes that shuffle them, so each is read behind
a switch the run's own settings turn on.
…nd shops

DR seeds now take run types of their own, whose settings say which pots,
enemies and shop items are locations: they join the Checks list and the
dungeon counters, and autotracking reads their tables only while those
settings shuffle them. What the tracker cannot model yet is named.
Most DR races roll their seeds there, not on alttpr.com. A permalink's
meta names every DR setting; a Hyrule Town Hall patch writes the bytes the
ROM decoder reads, so neither needs the seed generated. Fixtures are cut
to those bytes by a script, never holding where an item is.
…read

The tracker's DR data is 1.5.6's, while races run 1.5.2 to 1.5.8: the
readout says so when a seed's version differs. Its tables' bits are listed
for the run's own locations only.
…ce reads

The pack README is where each address, table and source is cited; the
rules and the share doc say where the new state lives and how links carry
it, so the next phase (DR's own logic) starts from them.
Its settings' selects, its key pots in the Checks list and the map's note
are new UI: the browser test holds them to every width and to axe, and
the import's placeholder now fits a phone's field.
The form now names avianart.games and Hyrule Town Hall beside
alttpr.com: the browser test still asked for the old words.
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

🔍 CI Results

Check Status
Linting ✅
Type-check ✅
Tests ✅

📊 Test Coverage

Metric Coverage Status
Lines 99.04% 🟢
Statements 98.17% 🟢
Functions 98.59% 🟢
Branches 93.07% 🟢

✅ All checks passed!


Updated at 2026-10-30T23:47:06.829Z

kqesar added 19 commits October 29, 2026 16:40
A DR run's review reads DR's own log, which is neither alttpr.com's JSON nor
anything sniffed so far: its text opens with its version line, and its JSON
leads with entrance and door lists that may fill any head. Name the format so
the ALttPR pack can route it.
A DR log names items as DR's Items.py does, which differs from the entrance
randomizer's table: a price after the code, events as code 999, a dungeon byte
after a prize's sprite bytes, and pickups vt has no item for. Join them to vt's
classes on the ROM code by the same importer, so the names stay generated.
Reviewing a DR run needs DR's own log, text or JSON, as DR 1.5.6's Spoiler
writes it: names with the dungeon a door shuffle moved them to and an enemy's
note, the pack's DR locations for key drops, pots, drops and shops, a meta keyed
by player, and the seed as the ROM's title states it, so a run autotracked from
the ROM binds to it. The fixtures are real spoiler races' logs, recorded by a
script, since avianart keeps a race seed's placements to itself.
…rules-core.mjs

The Door Randomizer's Rules.py descends from the entrance randomizer's: its port reuses the
same walk and translation under its own dialect (settings, items, methods, helper prefix).
The entrance randomizer's rules.json comes out byte-identical.
A DR log binds to the run by the seed in the ROM's title (or avianart's hash for a
run started from a link), by which randomizer wrote it and by the settings the ROM
states exactly. Its key drops, pots, enemy drops and shop items are known places of
the board and read "taken, not timed", since the timeline never dates them.

The waits are not shown for a DR log: the pack has none of DR's rules yet, and
alttpr.com's would say "in logic" from rules the seed was not made with.
The binding evidence, the new hidden reason and what a DR place reads in the
Run tab, for developers and for players.
DR's logic needs its own regions, doors and wiring, not the entrance randomizer's: import-dr
now also writes src/door-rando/world/ (913 regions, 1,200 doors with their logic flags, 25
portals and the connection tables) from Regions.py, Doors.py and the shuffle modules, read as
data, every entry with its line.
…(stage B)

DR's world needs DR's own rules. They go through the entrance randomizer's walk under a DR
dialect (Open, Standard and Inverted as settings, DR's items and methods); crystal reads, door
flags, the dry trench paths and the bunny's side are data the world gives. What is code stays
listed in unported with its line, held by rules.spec.ts, which compiles every rule.
…drops and pots

DR builds those rules in code from its sprites, enemy stats and damage table, so they are
computed as EnemyLogic.py does and handed to the walk where set_rules calls them. A
random.choice tie is followed every way and anded (none in vanilla); health that depends on
the world's side reads it through derived settings. js-yaml reads the damage table.
…ixtures (stage C)

DR's world will be held to its own unit tests and customizer seeds. The cases are read at
1.5.6, the harness at 626691a, where upstream made the suite run again; the everything-but
pool comes from ItemList.py's lists. Every file is listed pending in upstream.json until the
world is built.
# Conflicts:
#	packs/alttpr/test/manifest.ts
#	packs/alttpr/upstream.json
…anilla tests

DR's rules are now a world the engine solves: its regions linked as DR's
vanilla world links them (overworld, entrances with the Inverted swaps,
doors and portals), the flute and mirror exits it makes in code, the
Menu's save-and-quit exits, the bomb shop's case, the derived settings the
connections decide and the bunny's paths. The fact solver moves to
facts.ts, which the entrance randomizer's world shares. DR's four vanilla
test files are held: 572 of 573 cases, the last one waiting on the key
analysis. Barriers and key doors stay maybe until the dungeons are ported.
…s dungeons

The dungeons now run as DR's traversal does: each region a node per crystal
state some path brings it in, barriers passing their colour only, switch
doors both. Key doors stay strict-safe until the key analysis is ported:
every key of the dungeon (the chests' and the vanilla drops', swept as DR
sweeps its forced keys) or maybe, never no; big key doors and chests ask the
big key, Mire's barriers their two-key alternative. DR's eleven dungeon test
files are held: of the 181 cases upstream fails itself, 169 are listed with
the reason its own rules give, 12 wait on the key analysis.
…ted tests

The world builder already swapped Inverted's entrances; it now also takes
the connections to build on, which the tests use to show where DR's own
vanilla Inverted world differs from the old expectations: it never links the
Dark Sanctuary's exit, and it names the castle's entrance Agahnims Tower
whatever it leads to. Four inverted files are held (51 cases listed with
why); Turtle Rock's tests are all disabled upstream and the bomb rules test
holds no case, so those two stay pending.
…as code

Standard's world gains its save-and-quit to Uncle and Zelda's way back from
her cell, the rules of the first path DR finds, set on the tapestry and her
drop-off. Murahdahla joins a triforce hunt, a boss's items wait on its map
and compass when restricted, and Mire's spikes' pot asks the spikes once
pots shuffle. A fact reached at maybe only now reads maybe, so a maybe key
door no longer turns what lies past it into no; the escape's big key drop
stands for the item DR's rules read.
DR's key rules come from its analysis of each dungeon (KeyDoorShuffle.py), which
walks the dungeon's doors without item rules and records, for each set of key
doors a player may have opened, what lies behind them. This ports that walk on
the vanilla world: the overworld only a dungeon reaches (find_inaccessible_regions)
and its logical doors, each dungeon's start regions (determine_entrance_list and
vanilla_key_logic's enabling loop, Python's rstrip quirk in
_find_portal_for_region kept), ExplorationState's key paths and
create_key_counters. Not wired into the world yet; the rules come next.

Held: the start regions and inaccessible regions per mode, each dungeon's
counters (chest keys, forced drops, counters, keys spent at most), the max
counter reaching every location. DR contract unchanged (not wired).
The rule DR gives each key door: the counter that spent the most keys elsewhere
and still gains from the door sets its worst case, create_rule its partial
number, check_for_self_lock_key and bk_restricted_rules the alternatives a
placement may grant, then the swap of a big key alternative that asks more and
vanilla_key_logic's adjustments (the escape's doors, Palace of Darkness's front
door, Mire's blue barriers, Ganons Tower's alternates). The fill-side placement
rules are left out: they choose where keys go, never what a door asks.

Held to KeyDoorShuffle.py's own validate_vanilla_key_logic, which no DR code
calls, now imported as test/dr-upstream/vanilla-key-logic.json (python.mjs reads
set displays): every rule and big key restriction it checks holds in each mode
but three Ganons Tower doors, listed with why (the port asks one key more;
DR's own GT adjustment outdates two of its location sets). DR contract
unchanged (not wired yet).
Each key door of DR's world now asks what DR's analysis gives it, partial
logic's number (strict with wild keys: every chest key; dangerous read as
partial), instead of every key of its dungeon. What a placement grants beside
it (the key in the one location behind a door, the big key behind it) is maybe;
with the keys in their dungeons a door short of its number is maybe, never no.
Misery Mire's two-key alternative to its blue barriers is maybe only: DR's code
grants it, its own key logic seeds refuse what lies behind it. Other connections
than the vanilla ones keep the strict-safe rule (portals may be unknown).

Soundness: key-model.spec.ts draws placements of the dungeons' keys DR's fill
may make and plays them out with DR's own rule for each; the model never says
yes where the play does not reach (Open and Standard with the keys in their
dungeons, Open and Inverted with wild keys), and a model one key short or one
taking a placement's alternative for granted is found unsound.

Contract, per suite (pass / held cases, upstream's own failures listed):
- vanilla: 573/573 (was 572, KEY_PENDING 1: Mimic Cave now holds)
- dungeons: 302/483, STALE 181 = upstream's 181 failures (was 169 + 12
  KEY_PENDING: AT Dark Maze now holds; 11 upstream fails itself, its key rule
  counting drop keys TestDungeon never sweeps; AT's Dark Maze without weapons
  is DR's bombs)
- inverted: 615/666, STALE 51 (unchanged)
- KEY_PENDING: 13 -> 0, the list removed
- suite seeds, false => not yes, now held: partial 3/3, partial_2 5/5,
  partial_3 34/34 (1 a big key only bk_restricted forbids), strict 5/5,
  default (dangerous) 3/3; true => yes 4/4, 2/2, 67/79, 2/2, 2/9
… game refuses it

The DR review (4bc460a) renamed what the refusal calls an ALttPR log, since DR's
logs are A Link to the Past's too, but the Super Metroid and SMZ3 review specs and
their browser tests still expected "alttpr.com spoiler log": the unit specs failed
in the full run. The e2e files are updated untried (no e2e run under machine load).
kqesar added 30 commits October 30, 2026 23:33
Setting the bag on reset the bombs to none, even after the player had
recorded a +10 upgrade, which in a bag seed is the bag itself: sound,
but the find was lost without a word. Each +10 beyond the start of 10
now carries over as a bag.
Each door, entrance or overworld note builds DR's world again, key
analysis included, inside the click; only the vanilla-door solve had a
budget. A fully noted crossed shuffle at intensity 3 now rebuilds under
150 ms (50 ms measured), so a slower build cannot creep in unseen.
LogicNote and the tricks dialog each kept their own copy of the same
"A, B and C" joiner, and the DR map note needs it too.
Door shuffles had a sentence over the map; the fork's whirlpools, flute
spots, crossed edges and Mixed flips had none, so a Mixed run with
nothing noted read everything out of logic with no word of why. The
map's note now names the shuffles and points to the Overworld tab.
…lain

The quick control unmounts once it has noted every kind it listed, so
its focus fell to the page's start and a keyboard or screen reader user
had to find their place again in a list of a hundred rows (WCAG 2.4.3).
Focus now moves to the summary the control just changed.
The 67 Edges rows, and their selects' names, read the fork's internal
codes ("Kakariko Fortune ES - Kakariko Pond WS"), which no player can
map to the overworld. Each row now names its two screens as the tracker
does, and the part of the edge where two screens meet more than once;
a spec holds each edge's screen to the pack's data.
…the game

The game's entrance ids were mapped to targets from the entrance
randomizer's pools alone, which never shuffle Link's House (entrance 1)
or the Tavern's back (0x43). DR and its fork do, so walking out of a
moved Link's House left its door unnoted (bench seed c0PDd2qiJ). Both
now map to their targets; a pool that never offers them still drops the
reading, so alttpr.com runs read as before.
… unset

Lite's and Lean's kept caves read both: defaulting them to off and none
kept a fairy cave or the Good Bee Cave where a seed may have moved it.
Unstated, each now may move, so its cave waits for a note.
The Overworld Randomizer's door contract reads its logs as DR's does, on
its own release's data: the reading moves to test/dr-doors.ts, taking the
data bundle, and DR's contract holds the same answers on it.
A seed was read over the current run's settings, so a DR Crosskeys seed
loaded after an OWR Mixed District run kept District, Mixed, bonk drops
and the fork's prize and follower shuffles (bench seeds c0PDd2qiJ then
toInhuUYy): every check read maybe. A spoiler's meta now names the whole
seed, what it leaves out unset or DR's default; a ROM read keeps the
run's hand-set settings, but none of the fork's on a seed of DR itself.
Under a door shuffle every key door asked all its dungeon's keys, though DR analyses each layout
it builds. Where the notes tell all that analysis reads (intensity 1 or 2, coupled doors, vanilla
entrances, the layout linked and its kinds known, its starts certain), the world now runs DR's own
analysis on it; elsewhere, where DR's starts rest on random portal choices, strict-safe stays.
The logic note said a key door always asks every key of its dungeon, which no longer holds below
intensity 3 with coupled doors and vanilla entrances: once a dungeon is noted whole, DR's own
key analysis counts its keys. The note and the guide now say when it runs and why not otherwise.
DR builds Skull Woods from its holes and the castle from its rat path as
from a lobby (dungeon_drops). A room joined only to Skull Pinball kept its
vanilla dungeon, so its key doors counted another dungeon's keys; an OWR
partitioned seed puts Swamp's map ledge in Skull Woods that way.
…ptions

In a crossed shuffle each door's select lists every door it may lead
into, so Ganon's Tower's 104 rows held some 6,800 options, measured at
440 ms (150 on a quieter run, long tasks of 54 and 94 ms) between
picking the dungeon and its first paint. A row now shows the target
noted alone and its options come in a render React defers: about 40 ms,
no long task, held under 200 ms by a browser test.
The search said nothing of how many doors it showed, left bare headings
when none matched and kept its words across dungeons; lobbies read
"Ganons Tower entrance" beside "Ganon's Tower"; at 390 px the noted
target was cut off; the kind placeholder read "? Kind" where every
other select says "? Not known", and the keys field's "?" was faint.
The door model was held to DR 1.5.8's seeds only. Nine avianart seeds of
the Overworld Randomizer's door shuffle (basic, partitioned and crossed,
with Mixed, crossed entrances, decoupled doors, Inverted, Standard) now
hold it on the fork's data both ways, each door noted from the log.
… pairs

With eight flute spots ticked the other boxes turned disabled with no
word beside them, the count living far above; the hint that describes
each box now says why. A forced whirlpool pair read "(always)" alone,
where a sighted player looked for a select: it now says no note is
needed.
Unlike a chest, a bonk pin had no tooltip, so a mouse or touch player
got no name or state, and a review never said what its drops held. It
now has the chest's text tooltip and a held line. Its round shape was
nearly invisible at 24 px, the chest's square tile clipped: a ring
draws it.
The noted analysis reads a pool dungeon's chest keys from its own key doors (calc_max_chests),
never from the player's count. On the six seeds it analyses, each layout's count now must equal the
small keys the log's fill placed for that dungeon, 76 layouts in all.
CLAUDE.md, the data rule, the pack README, the review and the guide
still said the app solves only vanilla doors and entrances, OWR not at
all or with its overworld vanilla, Swapped not modelled, no Standard
seed held; the review's reasons lacked doors and overworld and bonk
drops; several comments saw only whirlpools and flute spots. The
guide's one 2,500-word paragraph is split, and names the static key
logic cause it left out.
…alone

Timed with the other browser tests running, the first paint came within
reach of its budget (144 ms of 200). The options a dungeon shows at first
paint cannot be blurred by load: 725 now, some 6,800 before.
… data

The fork's door shuffle is now held to its seeds, so a door-shuffled OWR
run takes the fork's world instead of showing every check maybe. The
Doors tab read DR's door data for every run; it now reads the run's
release's (the fork's has the bosses' Spoils rooms), from its solver's
module, so an OWR run never loads DR's data.
Read on the 390 px screenshot, a door's two selects both said "? Not
known" one above the other, which told the link from the kind only by
their accessible names. The kind's now says "? Kind not known".
The Doors tab now reads the fork's own data for an OWR run, so the
browser test notes a lobby from the keyboard and a link there, keeps them
across a reload, and holds the tab at four widths.
The tab now loads its release's world data on demand, which took longer
than a query's wait under coverage; loaded once before the tests, none
races the import.
Every door note rebuilt the world and ran DR's key analysis again on all thirteen noted layouts,
though a note moves one. Each analysis is now kept by all it reads (its layout's links, kinds and
dests, its starts, the run's settings), the inaccessible regions by mode: a fully noted crossed
world rebuilds in about 30 ms after a note instead of 75 to 90, with the same answers.
Brings every OWR entrance shuffle, OWR Standard and the door shuffle on
the fork's data beside the Swapped partners, District picker, small keys
only and DR's key analysis on noted layouts. Reconciled:

- owrEntrancePool: the fork's pool for any mode (owrShuffled(shuffle,
  ...), dungeon split, Insanity decoupled), Swapped keeping its derived
  partners, District alone narrowed to its district's targets.
- owrNotedConnections: every mode, Swapped's told partners linked; a
  target a partner tells no longer counts as unplaced, as a note's.
- import-dr --owr writes both the pool tables apart (entrances.json) and
  the districts (districts.json); each side's importer, run again, gives
  the merged files.
- poolOf routes every OWR run to owrEntrancePool with the worlds' and
  towers' readings District and Swapped need.
- The doors note names DR's key analysis for DR runs only: the analysis
  reads DR's data alone, so an OWR door shuffle's key doors stay
  strict-safe (guide too).
- Tests: the OWR pool settings in A's specs gain the run's fields, the
  District follow test keeps one district's doors, the e2e helper takes
  a settings change.
The fixes of the review of steps 5-7 on top of both DR branches. Each
reading kept the stricter side; reconciled:

- Coupling unset reads as decoupled (H1): the doors note promises DR's
  key analysis only for a coupling stated off, as the pack's gate reads
  it; A's note test states the coupling.
- OWR release and overworld unset (H2, M1): an OWR run's Mixed not
  stated leaves the start house, the towers' trade and District's
  worlds not known (poolOf); A's and B's OWR tests state the fork's
  overworld and release, and every recorded OWR seed is held to carry
  0.8.0.2. The pool's bonk drops and take-any caves stay B's reading
  (unstated may move), the world's bonk drops C's (unstated shuffled).
- A spoiler or ROM read drops the fork-only settings on a DR seed (B
  defect): A's nearbyItems is one of them.
- Every reviewed maybe yes once the keys are held (L-S2): held on B's
  OWR logs too, Standard's escape and Insanity's exits named as no key
  opens them; B's door-only copy of the check is gone.
- The Doors tab's deferred options (M7) on the run's own release data;
  the share table appends A's then C's options; the perf spec keeps
  both budgets (a fresh world's, a note with the analyses kept).
- Docs no longer limit the fork's world to District and Swapped.
…unset

Every DR and OWR release's meta names both (BaseClasses.py:2574, :2597 at
1.5.8; :2570, :2593 at 1.5.6; owr :3139, :3164), yet a meta without them
read as none, DR's default: a guess the seed may contradict. Now each is
left out, the ROM-like reading of the take-any caves unrecognized.
…mid as not known

A spoiler's word this version does not know for one of the four, or a
meta naming none, fell back to DR's default: an unknown enemy shuffle read
as none, a newer pyramid option as auto, so the logic could say yes on a
seed whose setting asks more. The four now have no default: the run types
state DR's, a seed its own, and unset is a cause the map names (every
check maybe) while the world reads it the stricter way.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants