Skip to content

Handoff: state of the tracker and what's left (2026-10-17) #202

Closed

Description

@kqesar

Where the work stands, and how to pick it up on another machine.

State of master (v2.42.0)

  • Logic: @alltp/engine and the complete ALttPR v31 pack (packs/alttpr).
    • All 2,969 official tests/NoGlitches cases, all 3,571 tests/Inverted cases and the 32 RetroBowTest cases pass (sporchia/alttp_vt_randomizer@219fcafd).
    • Worlds: Open, Standard, Inverted and Retro; and the entrance randomizer's own world for Crosskeys, its 510 official dungeon and Death Mountain cases passing (cassidoxa/ALttPEntranceRandomizer@8a1ac4d4).
    • Settings not known yet (a Mystery run's) read both ways by the engine, sound by property test.
    • Key model for untracked keys; save v7; a "seen" map state; keysanity maps and compasses.
  • Autotracking end to end:
    • @alltp/autotrack, the usb2snes link to SNI / QUsb2Snes;
    • the ALttPR memory map, with Race Mode by construction;
    • an in-app setup wizard per OS, and "What I read";
    • the board follows the game, one tab at a time;
    • outside races, a Crosskeys run notes each door or hole Link goes through, and the door he comes out by;
    • outside races, a dungeon's prize is read from the ROM's prize tables and shown when the game would show it (dungeon finished, its map held under map mode, Sahasrahla's or the Bomb Merchant's mark), listed in "What I read";
    • Race Mode follows the ALTTPR Auto-tracker Rules v2.0, deliberately strict pending TARC; a setting Auto (the ROM's flag) / Always on; no alttpr.com seed file in races.
    • Reading is confirmed on a real setup: CachyOS, RetroArch + bsnes-mercury, SNI, Firefox.
  • Archipelago (packages/archipelago, games/archipelago/): a read-only Tracker on the player's slot of an AP room, through archipelago.js 2.1.0 (pinned) behind an outgoing whitelist; AP's server is the authority (no logic, never mixed with our packs). Items (ALttP, SM and SMZ3 skins with our art on AP's item names; a generic list for any other AP game), checks by location group, the room's feed, hints, players, while-you-were-away, the run timer from the room's countdown to the slot's goal, the stream view (&feed=1, &timer=1), items-only overlay; self-hosted rooms from the site (Local Network Access flow) and from the desktop app (Linux verified); join links. Pinned to AP 0.6.8, with a weekly real-server CI job and an ap-drift watch. Checked live against archipelago.gg from the site.
  • Three games, one module each (the device's game, loaded on demand):
    • A Link to the Past Randomizer (alttpr.com), as above;
    • Super Metroid as Map Rando plays it: items, the seed's objectives, autotracking (no logic: the rooms are reconnected);
    • SMZ3 (samus.link): both games' logic from tewtal/SMZ3Randomizer, held to its four test sets (5,490 cases); the maps of Hyrule and Zebes; keysanity's keys and keycards; autotracking in both games, race-safe (never the rewards, never a race seed's thresholds or medallions).
  • Desktop app (Tauri 2): Windows (NSIS), macOS (arm64 and x64 .dmg, ad-hoc signed), Linux (AppImage, .deb, .rpm), built and attached to each release; signed updates (latest.json, minisign key); autotracking: RetroArch read directly over UDP with no SNI, SNI as the fallback for every other device (and for Safari's users on the web); global hotkeys (Windows, macOS and X11 through the global-shortcut plugin, Wayland through the xdg GlobalShortcuts portal, alltp-tracker --step <action> for any window manager); Export through the native Save dialog; live follow on the LAN (the app relays the run to ?view=follow pages on phones on the same network); on Linux, the AppImage offers to add itself to the application menu.
  • Web: PWA (offline, install, F5 always latest), a Share dialog with the link and its QR code (lean-qr, lazy chunk), OBS stream view (?view=obs), configurable item shortcuts, measured-fit toolbar with a More menu, greyed missing items, transparent art with 1-game-pixel outlines.
  • Run analysis: what each check is missing (tooltip and the Checks list's "Why?"), a Checks tab beside the map, go mode per goal beside the map's tabs.
  • Run timer: beside go mode, started and stopped by the game while autotracking (race-legal), the ROM's play time outside races, optional on stream.
  • Run tab: splits at each boss, a timeline of every find timed by the run timer (saved with the run, undo-exact), and outside races the ROM's own end-credits stats.
  • Voice commands: "tracker hook shot" marks a find while the game has focus, recognized on the device (Vosk, WASM), opt-in.
  • The ROM picks the run type: a fresh board switches to the seed's; otherwise a prompt offers Switch or Keep mine. Outside Race Mode, settings the ROM does not state come from the seed's file on alttpr.com; in Race Mode the racer picks the run type.
  • Start from a seed: an alttpr.com link or hash in the run type dialog names the run type.
  • CI and releases:
    • one pipeline per merge (ci.yml; the deploy job publishes the e2e-tested build to Pages);
    • release-please, with the release PR merged at the end of each PR series;
    • a weekly logic-drift workflow;
    • pnpm check:docs (scripts/check-docs.mjs, own docs.yml workflow since ci.yml ignores Markdown);
    • actions pinned by commit SHA; Dependabot sends one weekly pull request for npm, actions and the desktop crates (cargo in the all-dependencies group, ci: let Dependabot update the desktop app's crates #315; unconfirmed until its first weekly run accepts it).

Waiting on the owner

  • SignPath Foundation application (Windows signing): the owner's step, next on the roadmap.
  • Social preview: the upload (GitHub repository settings) is still pending.
  • Announcement (the first drafts were lost with a wiped /tmp; write them again when it is due): first ask the ALttPR racing council whether race mode's two uncovered behaviours are OK (the seed's file fetched from alttpr.com for its settings; a Mystery race seed switching the board to Mystery); then review the texts; decide whether GitHub Discussions stay open.
  • Stream view and overlay API on other setups: passing in real OBS 32.2.2 and 33.0.0 on Linux (Flathub; CEF 127 and 150), 2026-10-23/24 (docs: record the overlay API and stream view run in a real OBS #327, fix(desktop): serve the web manifest instead of a 404 #328). Still to do by the owner: another computer on the network, and the stream server on macOS and Windows.
  • TARC questions (ALTTPR Auto-tracker Rules v2.0): nine points to ask on the TARC Discord: the seed file in races, Mystery logic, the timeline, the overlay's timer, prize classes, play time, WRAM, a prize the map shows, the override. The text is ready in the owner's home (~/tarc-questions.md). The answers decide the series that loosens race reads.
  • Archipelago on macOS and Windows desktop, and Safari/iOS: unverified.
  • Live follow on a real phone: scan the QR with an iPhone on the same Wi-Fi (Safari iOS on a plain-http LAN page is unverified); check the Windows firewall prompt.

On master, not yet released

Nothing.

Done in v2.38.2 to v2.42.0 (2026-10-24/25): Race Mode v2.0, Archipelago, ROM prizes

Done in v2.38.1

Done in the series before (v2.37.0 to v2.38.0): stream server, overlay API

  • v2.38.0 released, its 14 desktop installers and latest.json attached (15 assets, checked once the Desktop release run finished).
  • refactor(desktop): split the relay's building blocks out of follow.rs #322: the relay's building blocks split out of follow.rs into relay.rs.
  • feat: serve the stream view to OBS from the desktop app #325: the desktop app serves the OBS stream view (?view=obs&token=) from a second token-gated server on port 23076 (16 seats), loopback by default, with an opt-in "Allow other devices on the network" switch. Settings, port and tokens are kept in stream.json (0600). Port order: saved, 23076, 23077-23085, any. Exclusive bind on Windows, with a 2 s same-port retry on restart. The token travels as a WebSocket subprotocol. The Stream view toolbar button is back in the desktop app, with the Stream tools dialog. Windows CI now runs cargo test --locked -- stream:: relay::.
  • feat: the overlay WebSocket API #326: the overlay WebSocket API (/overlay on the same server, its own API token; protocol v1 with state, update and beat, seq; a race-safe whitelist per game, SMZ3 sending items only, no boss tokens); docs/overlay-api.md.
  • fix(web): put the visible text in the accessible name of four controls #323: a11y fixes for axe-core 4.14's label-content-name-mismatch. Dependabot build(deps): Bump the "all-dependencies" group with 2 updates across multiple ecosystems #316 (axe-core 4.14 group) waits on a Dependabot recreate.
  • Lessons:
    • validate workflow YAML after every edit: an unquoted : broke desktop.yml silently (the job never started, so the PR looked green);
    • a test that holds a port must bind the address the server binds (Windows lets an exclusive 127.0.0.1 bind share a port with a plain 0.0.0.0 one);
    • a stale-build e2e used ?v=2, which loads the entry module twice.

Done in the series before (v2.36.0 to v2.37.0): QR share, docs audit, deduplication, live follow

Done in the series before (v2.35.0): Half Magic, durable share links, test flakes

Done in the series before (v2.34.0): Rando Atlas, the public repo, release after the deploy

Done in the series before (v2.33.0): the application menu entry, and a faster CI

Done in the series before (v2.32.0): desktop global hotkeys (phase 7, series 3)

  • PR feat(desktop): global shortcuts in the desktop app #281: the desktop app steps the tracker from a key that works while RetroArch has the focus, the same actions as the page's shortcuts, opt-in per action, off by default. A mechanism is picked once per process (shortcuts/detect.rs, a pure function): hotkeys (tauri-plugin-global-shortcut) on Windows, macOS and X11; portal (the xdg GlobalShortcuts portal through ashpd) on Wayland (KDE, GNOME 48+, Hyprland), touching nothing until the first binding; command (alltp-tracker --step <action>, bound in the window manager) anywhere else. The command works under every mechanism, handed to the running app by tauri-plugin-single-instance.
  • A Global tab in the Shortcuts dialog (desktop only; the site gets no byte of it), key labels from the system keymap on Linux. No global-shortcut:* permission is granted: the page registers a key only through the app's command, which keeps a floor (a character key needs Control, Alt or Meta), so a compromised page cannot make the app a keylogger.
  • Fixes on the way: X11 keys released in one request, the Exec line of the desktop file escaped, a --step that finds no page dropped, report types compiled without the portal; clippy now also runs on Windows and macOS. CI: a release commit skips ci, E2E, deploy and CodeQL (ci: skip everything for a release commit #283).
  • Release PR chore(master): release 2.32.0 #285: the 4-target matrix succeeded, 14 assets plus latest.json, every platform with a URL and a signature, the AppImage's checked against the app's key (a tampered copy rejected). No Pages deploy for the release commit, as intended: the live site is still the previous master commit (1c5e889).
  • Update test (2026-10-20), v2.31.0 AppImage on Linux in the private bench (Xvfb kept alive this time): the notice appeared, Restart replaced the file (sha256 equal to the v2.32.0 asset) and relaunched at once: one alltp-tracker process, one window, the app working on 2.32.0. <appimage> --step advance:flute against it reached the running instance (the flute cell changed). This proves the update and the relaunch from 2.31.0, which has no single-instance plugin, so no D-Bus name was held: whether a 2.32.0 holding the name relaunches cleanly is seen at the next release.
  • Not tested by hand: Windows, macOS, GNOME, Hyprland (built and linted in CI); KDE Wayland is the owner's checklist above.

Done in the series before (v2.31.0): RetroArch read directly (phase 7, series 2)

  • PR feat(desktop): read RetroArch without SNI #277: the desktop app reads RetroArch itself over its UDP network commands (VERSION, GET_STATUS, READ_CORE_MEMORY), with SNI as the fallback for every other device (FX Pak Pro, BizHawk, NWA). RetroArchTransport in @alltp/autotrack (eight pieces in flight, short replies read on, CONTENTLESS told apart from a core without a memory map); a retroarch Tauri command (a socket per command, only the three read commands pass).
  • FirstAnsweringTransport and the superseded rule: a fallback is kept only once it lists a device; a fallback with none gives way to RetroArch the moment it answers, and the session retries at once.
  • Real rig (RetroArch 1.22.2, release build, private Xvfb): ALttPR Crosskeys on bsnes-mercury connected directly; app opened before RetroArch moves from SNI to RetroArch; no content gives "No game is running"; Snes9x gives "Cannot read the game's memory"; SMZ3 title read at $C0:FFC0. The player's saves were left unchanged. Measured: no read-size cap, one command per frame; the retroarch skill's 4096-byte cap was false and is corrected. Site bundle: startup chunk unchanged, lazy chunks +477 B.
  • Two web fixes on the way: the wrong-ROM message names the game it is checked against; the Safari wording points at the desktop app.
  • Release: the 4-target matrix succeeded, 14 assets plus latest.json, every signature valid against the app's key (and a tampered file rejected), Pages deployed on the release commit.
  • Update test (2026-10-20), v2.30.0 AppImage on Linux in the private bench: it offered "A new version is available"; Restart downloaded, verified and replaced the AppImage in place (sha256 equal to the v2.31.0 release asset). The relaunch found no display only because the bench kills Xvfb when the first process exits; launching the updated file again showed the app with no update notice.

Done in the series before (v2.30.0): the desktop app (phase 7, series 1)

  • apps/desktop (feat(desktop): the tracker as a desktop app for Windows, macOS and Linux #275): Tauri 2.12 around apps/web built in a desktop mode (base /, no service worker, no speech model: it is fetched from the site); no byte of desktop code in the site (inline import.meta.env.MODE tests, checked on the bundle).
  • Autotracking through SNI as on the web: the app's origin is not https, so loopback is open; detectEnvironment now gates https: pages only (feat(desktop): the tracker as a desktop app for Windows, macOS and Linux #275's first commit).
  • Updates: tauri-plugin-updater reads releases/latest/download/latest.json; the notice offers "Restart". The minisign private key is in the repo secrets (TAURI_SIGNING_PRIVATE_KEY, ..._PASSWORD) and with the owner: losing it ends updates.
  • Releases: the release-please workflow builds 4 targets in parallel when it cuts a release (macOS arm64 and x64, Ubuntu 22.04, Windows), about 10 min, and attaches 14 files plus latest.json. Verified on v2.30.0: every signature checks out against the app's key, the .deb downloads through the API URL latest.json gives.
  • PR CI: a rust job (fmt, clippy, tests) only when apps/desktop changes.
  • Tested on Linux in a private Xvfb bench: rendering, SNI autotracking with RetroArch, links opened in the system browser, Export, persistence across relaunches. macOS and Windows are built in CI, not tested by hand yet.
  • Docs: docs/desktop.md (architecture, updates, releases, signing), the README's per-OS first run (SmartScreen, Gatekeeper).

Done in the series before (v2.27.0 to v2.29.1): Super Metroid and SMZ3 (phase 6, series 2 and 3)

  • v2.27.0: Super Metroid (Map Rando): items, objectives, autotracking, tested on a real seed.
  • v2.28.0: SMZ3's board, logic and maps; v2.29.0: its autotracking, tested on a real samus.link seed (three bugs found and fixed); v2.29.1: ALttPR reads everything every 500 ms (one cadence, so the state and the saves are read together).

Done in the series before (v2.26.1): one module per game (phase 6, series 1)

  • ALttPR's board, map, logic, store, autotracking and voice moved under apps/web/src/games/alttpr/ (refactor(web): one module per game, loaded on demand #259); src/ keeps what every game shares.
  • games/registry.ts: the device's game (alltp-tracker-game, ALttPR by default) and its module, a chunk loaded on demand, so a player of one game never downloads another's board.
  • Startup JS unchanged for ALttPR (571 kB against 569 kB, 169 kB gzip against 167); CI time unchanged (e2e 292 s, ci 129 s).

Done in the series before (v2.26.0): Crosskeys entrance autotracking

  • The pack reads where Link went (feat(alttpr): read from memory the doors and holes Link goes through #256): decodeEntrances reads the door or hole Link went in by and the door he came out by, as slots and entrance numbers, and linksOf names them.
    • The randomizer writes where each door and hole leads into the ROM's door and hole tables (Rom.py:490-539). The base patch keeps the last overworld door's slot ($7F:5099) as the game loads its entrance ($7E:010E). The exit is told by the room Link leaves (zelda3 overworld.c:1784-1789).
    • Every range is the race profile's entrances category, so Race Mode never reads one: no new switch, autotracking's casual mode is the opt-in.
    • The importer adds door slots, hole bytes, exit_ids and the base ROM's exit rooms, cross-checked against the game's own tables (59 pairs).
  • The board notes them (feat(web): note the doors Link goes through in a Crosskeys run #257): as the game's news, once per change, loading the entrance tables only in a Crosskeys run. A note the game contradicts gives way. Of rooms alike (the healer fairies), the one noted there, else one noted nowhere.
  • Checked on the real rig with 0yAObedRMV:
    • offline, the decoder read the rebuilt ROM's 126 doors, 12 holes and 58 exits as the spoiler has them;
    • in game, three doors were noted as Link went through them, the Elder House crossed to its east side included;
    • deaths and a save and quit noted nothing.
  • Found on the way: a respawn keeps the last door's slot (it was said to clear it); the docs now say what the game does.

Done in the series before (v2.25.0): Crosskeys logic

Done in the series before (v2.24.0): Crosskeys, the entrance assistant

  • The entrance randomizer's tables (feat(alttpr): the entrance randomizer's entrances and targets #246): packs/alttpr/scripts/import-er.mjs reads cassidoxa/ALttPEntranceRandomizer@8a1ac4d4 (the commit vt pins) as data, never runs it.
    • Door positions come from door_addresses. Hole positions come from the base ROM's hole table, read via --rom and checked by its MD5, 7 rows down as CreatePyramidHole shows.
    • crossedPool lists what alttpr.com's crossed shuffle mixes (126 doors, 12 holes; Ganon's Tower is shuffled by default).
    • It is held to a real Crosskeys seed's spoiler.
  • The assistant (feat(web): note where each shuffled entrance leads #248, feat(web): checks follow the entrance noted to their place #249):
    • shuffled doors and holes are on the map (dashed ring until noted, then a glyph per kind of place);
    • a searchable picker offers only what the shuffle allows, and notes a hole with its cave's door;
    • an Entrances tab lists them all;
    • the checks inside a place stand above the first door noted to lead there;
    • pointing at a door rings the other sides of the same place.
    • The tables load only for such runs.
  • Checked on the real rig with the Crosskeys seed: the lake shop's door led into Elder House's west side and its east side out of Good Bee Cave, as the spoiler says. Fixed on the way: overlapping door markers.

Done in the series before (v2.23.0): the "logic soon" run types, part 1

  • No more Open logic under "logic soon" (fix(web): no Open logic on a run type the pack does not model #242): a run whose rules the pack does not model (Crosskeys today) shows every check as maybe, with a note over the map; logicModelled follows the settings.
  • Retro (feat(alttpr): the retro world, its rupee bow and generic keys #243, feat(web): Retro runs in logic, with their arrow and key shops #245):
    • the pack ports vt's retro world: the rupee bow (an arrow bought, shopArrow), generic keys (every door once a key is for sale, shopKey), wild keys. The official RetroBowTest is imported and passes;
    • the key model never vouches for a generic key: a key door is maybe until a key shop is found;
    • the board's last row holds an Arrow shop and a Key shop in Retro runs (click, shortcut, voice "tracker arrow shop" / "tracker key shop"), drawn in the grid's own pixel scale;
    • checked on the real rig with a Retro seed generated on alttpr.com: the tracker switched itself to Retro, and the generic key from Link's House stayed no dungeon's.

Done in the series before (v2.22.0): Mystery

Done in the series before (v2.21.0)

  • Voice commands (feat(web): the voice commands' words, grammar and meaning #234, feat(web): mark finds by voice, recognized on the device #236), Phase 4 series 3:
    • Evaluated first:
      • The Web Speech API is missing in Firefox (the owner's browser), and Chrome sends the audio to a server by default.
      • OBS docks have no reliable microphone.
      • So: Vosk on the device, opt-in, light and free (the owner's conditions).
    • The vocabulary (logic/voice.ts):
      • every command starts with the wake word "tracker", so talking about an item marks nothing;
      • a command says a find, not a click (held stays held, a level up never wraps, bits apart, a named level never lowers);
      • the grammar is the commands, then [unk];
      • names the model's dictionary lacks are said as sound-alikes ("samaria", "bom boss", "a gannon", "gannon tower"), checked against synthesized speech.
      • 1,393/1,395 commands were recognized right in three synthesized voices.
    • The engine:
      • @lichess-org/vosk-browser runs in a Web Worker, fed by an AudioWorklet;
      • the 40 MB model is kept in IndexedDB, and "Remove the speech model" frees it;
      • it reached "Listening" in Firefox, and recognized the commands end to end in Chromium;
      • about 15 % of one core while hearing speech.
    • Weight:
      • about +5 kB gzipped on first load;
      • the engine is lazy, its worker and WASM are cached on demand by the service worker, and the model is never precached;
      • CI fetches the model with its pinned SHA-256.
    • The UX:
      • the Voice button beside the run timer, always in sight while listening, shows each command for 3 s;
      • a live region says each command;
      • the dialog has the microphone, "What to say", and the failures with what to do;
      • one tab listens (Web Lock).
    • Still to do: the owner's test with a real microphone, Firefox included.

Done in the series before (v2.20.0)

  • Run timeline and stats (feat(alttpr): read the ROM's own stats outside races #230, feat(web): keep when each find of the run happened #232, feat(web): show the run's splits, timeline and the ROM's stats in a Run tab #233), Phase 4 series 2:
    • The pack reads the ROM's own stats, those its end credits show: first sword, boots, flute and mirror times, boss kills by sword, damage, magic, bonks, save and quits, deaths, faerie revivals, menu and lag time, items collected, play time.
      • Three ranges of category other, so Race Mode never reads them; the race read is unchanged at 364 bytes.
      • Checked on the real rig: MenuFrames counts one per frame (the credits' 8-bit shift is not its unit), and a save and quit adds one.
    • The timeline (logic/timeline.ts): every item level, bit (the bow's, the boomerangs') and location found is stamped with its instant and the run timer's reading, by the player and by autotracking.
      • It is part of the run: undo and redo give back the very stamps, and exports carry it.
      • No save version: older saves restore none, and 2.19 still imports a 2.20 export.
    • The Run tab beside Map | Checks has four parts:
      • the run at a glance;
      • splits (a table of the bosses in the order they fell, with segments, then the goal);
      • the timeline, newest first, with sprites;
      • "From the game", outside races once in play.
    • Three view tabs: a narrow panel lays them out as a full-width segmented control. The toolbar e2e caught them pushing a 320 px page wider at 150 % text.
    • Checked on the real rig: the game's check stamped at 0:00 as the auto-timer started, and the ROM's stats read live.

Done in the series before (v2.19.0)

  • Run timer (feat(alttpr): read when a game starts, and its play time outside races #226, feat(web): time the run, started and stopped with the game #228, feat(web): show the run timer on stream, in the counters' pixel font #229), Phase 4 series 1:
    • The pack reads when a game starts (beforePlay: the title, the file select, the spawn select, all in the race-legal game-start category). Outside races, it also reads the ROM's frame counter (NMIFrames, category other, never read in Race Mode).
    • The map panel's timer sits beside go mode: start, pause, resume, reset. It is saved with the run but stays out of undo.
      • It starts when play follows the screens before it (connecting mid-run starts nothing). It stops at the goal.
      • It survives a reload and follows the other tabs and the stream view. The automatic switch to the seed's run type keeps it.
      • Outside races, its popover shows the in-game time (frames / 60.0988).
    • On stream, optional (&timer=1, a checkbox in the Stream view dialog): one row under the grid, in the chest counters' pixel font and frame.
    • Checked on the real rig (RetroArch + bsnes-mercury + SNI): title → file select → spawn select → play started the timer; a menu in play did not restart it; the IGT was read.
  • Fix: the README's stream view sizes now count the grid's eighth row.

Done in the series before (v2.18.0)

Done in the series before (v2.17.0)

  • Keysanity polish (feat(web): record the castle dungeons' keys on the grid #220):
    • 8th grid row, shown with the key cells: Hyrule Castle's, Castle Tower's and Ganon's Tower's key cells, each beside its short name (HC, CT, GT), as the reference keysanity pack lays its key rows out (CodeTracker).
    • Key cells show what their dungeon's pool holds.
    • Logic and autotracking: Ganon's Tower goes into logic in MCS / Full Shuffle once its keys are recorded, and autotracking lays those keys too.
    • Save v11: 13 small-key counts.
  • pnpm 12.14.0 (build: move to pnpm 12.14.0, pinned in devEngines too #219), pinned in packageManager and devEngines.packageManager. The lockfile records each pnpm binary's sha512 (pnpm 12 does it from packageManager alone).

Done in the series before (v2.16.0)

  • Inverted (feat(alttpr): lay out the Inverted world and import its tests #214, feat(alttpr): port the Inverted overworld and escape #216, feat(alttpr): port the Inverted dungeons #217, feat(web): track Inverted runs #218):
    • Scaffold. The importer reads each world's locations through its class chain and the removeItems on it, and imports tests/Inverted.
    • The 24 app/Region/Inverted/** files. Classes that keep their parent's rules (parent::initalize()) derive from it with extendModule: the escape and eleven dungeons. The overworld and Turtle Rock replace them all.
    • Contract. All 22 Inverted test files run in the contract. A test's "Dummy Boss" is given to its region.
    • App.
      • markersOf gives each world its markers: Link's House sits at the Dark World's Bomb Shop, the Bomb Merchant takes Link's House, and Castle Tower and Ganon's Tower swap doors.
      • The big bomb comes from checking the Bomb Merchant (EVENT_CHECKS), which opens Inverted's Pyramid Fairy.
      • The Inverted run type now has logic.
    • Upstream quirks, ported as written:
      • config('cantTakeDamage') has no region. prefix, so it is never set;
      • has('PegasusBoot') names no item;
      • hasABottle(2) ignores its argument;
      • turtlerock.wastekey is never set.
    • Real setup. Checked with the Oct 16 Daily (Inverted, race): the board switched to Inverted, and Link's House read as done in the Dark World.

Done in the series before (v2.15.0)

Done in the series before (v2.14.0)

Done in the series before (v2.13.0)

  • PR F (feat(web): say what each check is missing, and define go mode per goal #199, feat(web): Checks panel and go mode (PR F, second half) #203): what each check is missing, worked out at idle; a Checks tab beside the map (filters with counts, a zone select, search, "Why?", a skip link); go mode beside the map's tabs, what it lacks in a popover. Two deviations from the plan, for simplicity: go mode left the toolbar (there it cost every button its label at 1440 px), and the list has no sort control (filters, zone and search cover it).
  • The ROM picks the run settings (feat: read a seed's settings from its ROM #200, feat(web): the ROM picks the run settings (second half) #204): a fresh board switches to the seed's run type and says so; otherwise a prompt offers Switch (one undo step) or Keep mine (remembered per seed). The dialog and "What I read" show the seed's settings. "New game" now keeps the run's own settings. Checked on the real setup: an Open seed (settings equal to alttpr.com's), the prompt, Switch then Undo, Keep mine across a reload, and a Daily Challenge in Race Mode.
  • Known oddities: in keysanity the grid cannot record Hyrule Castle's, Castle Tower's or Ganon's Tower's keys (fixed in v2.17.0); Hyrule Castle's key spots show unprefixed names.
  • Follow-up idea: a manual crystal-count picker in PresetSelector.
  • Platform decision (2026-10-17): no proxy, the seed's file read straight from alttpr.com's bucket.
    • Why it changed: alttpr.com/hash/{hash} sends no CORS header, but the file the site's own seed page loads, https://alttpr-patch-data.s3.us-east-2.amazonaws.com/{hash}.json, sends Access-Control-Allow-Origin: * (checked with curl -H "Origin: https://kqesar.github.io", then in Chromium against the real bucket).
    • What is read: hash and spoiler.meta only; the patch in the same file is downloaded and never read (no schema names it). A tournament seed's spoiler holds only its meta; a "mystery" one drops its world and goal, which then read as unrecognized. Approved by the user on 2026-10-17.
    • Behaviour: non-race seeds still get their settings from the ROM; a race seed's ROM gives only its name (the hash) and logic, and the file gives the rest. Offline, or for a seed alttpr.com does not have, the run type stays the player's.

Next on the roadmap

  1. Finish the two second halves, then merge the release-please PR. Done: v2.13.0.
  2. Phase 3 — modes (plan approved 2026-10-17, one release per series):
    • series 1: the seed's file on alttpr.com, seed import from a link Done: v2.14.0;
    • series 2: the logic settings as run settings, read from the seed's file; pickers; MC / MCS / Full Shuffle Done: v2.15.0.
    • series 3: Inverted (scaffold, overworld, dungeons, map) Done: v2.16.0;
    • series 4: keysanity polish (the castle dungeons' keys on the grid) Done: v2.17.0.
    • Follow-ups of series 4: Ganon's Tower's counter (v2.18.0). Hyrule Castle's checks keep their own map markers. Castle Tower holds two checks. Labels on the ten other key cells were dropped: their boss portraits name them.
    • swordless as a run type (preset since feat: read a seed's settings from its ROM #200).
    • CI on Node 26 once it is LTS (2026-10-28), straight from 22: approved by the user.
  3. Phase 4:
    • timer and auto-timer Done: v2.19.0;
    • a run timeline and stats Done: v2.20.0;
    • voice commands Done: v2.21.0 (a real-microphone test by the owner pending).
  4. Phase 5: Mystery (v2.22.0); Retro logic (v2.23.0); the entrance randomizer (Crosskeys): series 2, the entrance assistant without logic (v2.24.0); series 3, the ER logic ported from cassidoxa/ALttPEntranceRandomizer@8a1ac4d4, held to its tests (v2.25.0); series 4, entrance autotracking outside races (v2.26.0).
  5. Phase 6: Super Metroid alone, then SMZ3, each on the randomizer the community plays most (measured on racetime.gg race links, 2026-10-19): Map Rando for SM alone (~99 % of 600 races, Aug-Oct 2026; rooms reconnected, logic in sm-json-data, so an item and objective tracker without logic), samus.link for SMZ3 (~97 %; logic ported from tewtal/SMZ3Randomizer and held to its tests). series 1, one module per game (v2.26.1); series 2, Super Metroid (Map Rando): items, the seed's objectives, autotracking (v2.27.0; tested on a real Map Rando seed through SNI); series 3, SMZ3 (samus.link): the ExHiROM mapping (feat(autotrack): read ExHiROM games, as the SMZ3 combined ROM #266), the logic of tewtal/SMZ3Randomizer@production held to its four official test sets, 5,490 cases (feat(smz3): port SMZ3's logic, held to its upstream tests #268), its weekly drift watch (ci: watch SMZ3's upstream for logic drift too #269), the board: both games' items, the maps of Hyrule and Zebes by that logic, keysanity's keys and keycards (feat(web): track SMZ3 runs #270) (v2.28.0); SMZ3 autotracking in both games, race-safe: never the rewards, never a race seed's thresholds or medallions (feat: autotrack SMZ3 seeds #271) (v2.29.0; tested through SNI on a real samus.link seed, which found and fixed three bugs). CI: the browser tests run on two runners since ci: split the browser tests over two runners #264 (about 3 min, from 5.9).
  6. Phase 7: a Tauri desktop app for Windows, macOS and Linux, no app stores, no paid signing:
    • series 1, the shell, distributed and updated (v2.30.0);
    • series 2, a direct RetroArch UDP transport, SNI as the fallback for other devices (v2.31.0);
    • series 3, global hotkeys: the plugin on Windows, macOS and X11, the xdg GlobalShortcuts portal on Wayland (KDE, GNOME 48+, Hyprland), a CLI fallback for any WM (v2.32.0; KDE Wayland checked by the owner, all 8 points, 2026-10-20);
    • then: apply to SignPath Foundation for Windows signing (the owner's step).

Planned features, with their status

Legend: ✅ done (on master) · 🚧 in progress (draft PR) · ⬜ to do.

Retained with the user

Feature Status Notes
Offline PWA, installable ✅ F5 always loads the latest version; a stale tab is offered the update; never reloads mid-run (except the OBS view)
OBS stream view ✅ ?view=obs: grid only, transparent, crisp scale; dock + browser source sync to verify in real OBS
Per-item keyboard shortcuts, configurable keymap ✅ physical keys (AZERTY-safe), rebinding dialog, master switch; global hotkeys need the desktop app
Zero-config autotracking ✅ SNI / QUsb2Snes, setup wizard per OS, diagnostics, auto-reconnect, one tab at a time
Race Mode compliant with spec v1.2 ✅ reads only permitted memory; turns on from the ROM's race flag; race-legal badge
"What's blocking me?" (blockers) ✅ "Missing: …" in the tooltip and the Checks list's "Why?"
"Doable now" Checks panel ✅ a Checks tab beside the map: filters with counts, zone, search, "Why?"
Go mode indicator ✅ per goal (Ganon, fast Ganon, pedestal), beside the map's tabs
The ROM picks the run settings ✅ auto-switch on a fresh board, else a prompt; a race seed's settings from its file on alttpr.com
Auto timer ✅ beside go mode; starts at play, stops at the goal (race-legal); the ROM's play time outside races; optional on stream (&timer=1)
Run timeline and stats ✅ the Run tab: splits, every find timed by the run timer (saved with the run), the ROM's end-credits stats outside races
Voice commands ✅ "tracker ", recognized on the device (Vosk), opt-in; English; not in OBS docks
Seed import ✅ an alttpr.com link or hash in the run type dialog: world, goal, swords, crystals, dungeon items
QR / state sharing ✅ share link (#301/#302), QR code (#314), live follow on the LAN from the desktop app (#320)
Entrance randomizer assistant ✅ v2.24.0: doors and holes on the map, a picker per door, an Entrances tab, checks following their door; v2.25.0: the logic through the doors noted
Mystery seeds ✅ settings hidden until found, the run kept; a race seed's file known as Mystery (v2.22.0)

Modes and games

Feature Status Notes
Open / Standard, NoGlitches v31 ✅ all official cases pass; weekly logic-drift watch
Keysanity, MC and MCS Shuffle ✅ maps, compasses and keys tracked per level, the castle dungeons' too (v2.17.0)
Inverted ✅ ported from app/Region/Inverted/**, all official cases pass (v2.16.0)
Swordless run type ✅ a preset since #200
Retro ✅ rupee bow, generic keys, the arrow and key shops on the grid; RetroBowTest passes (v2.23.0)
Crosskeys (entrance shuffle) ✅ recognized from the seed's file; the entrance assistant (v2.24.0); the entrance randomizer's logic, go mode and Why? (v2.25.0); entrance autotracking outside races (v2.26.0). Inverted crossed and other shuffles stay maybe
Item placement, pool, functionality, accessibility, crystals ✅ run settings, from the seed's file or tuned in the run type dialog
Super Metroid alone ✅ Map Rando (the community's): items and the seed's objectives (v2.27.0), autotracking read from the ROM and the game (v2.27.0); the app loads one game's module at a time (v2.26.1)
SMZ3 (one combined ROM) ✅ samus.link (the community's): pack smz3 from tewtal's source, held to its tests; the board with both maps and keysanity (v2.28.0); autotracking in both games, race-safe (v2.29.0); ALttPR alone, SM alone and SMZ3 are all first-class
Desktop app (Tauri), Windows / macOS / Linux 🚧 the shell, releases and signed updates (v2.30.0); direct RetroArch UDP, no SNI (v2.31.0); global hotkeys, portal / X11 / CLI fallback (v2.32.0); live follow on the LAN (v2.37.0); stream view and overlay API served by the app (v2.38.0); next: SignPath application; later a BizHawk bridge (Zelda 1, Metroid, OoT)

Ideas from the competitor research

  • Race-illegal assists in casual mode: entrance autotracking (v2.26.0); door mapping dropped (2026-10-24).
  • An overlay / event WebSocket API for stream tools (v2.38.0). Next after Race Mode v2.0: Archipelago as its own randomizer.
  • A browser runtime for PopTracker packs: dropped (2026-10-24).

Picking it up with Claude Code

  • git clone, then pnpm install (pnpm is pinned in package.json). CLAUDE.md and .claude/rules/*.md carry the conventions; packs/alttpr/README.md and packages/*/README.md are the design docs.
  • Checks: pnpm lint && pnpm typecheck && pnpm test:coverage && pnpm build && pnpm test:e2e. Judge them by exit code, and gate pushes on them.
  • CI and local versions: CI runs Node 22, so run Node-API code (WebSocket…) on 22 too.
  • Docs: pnpm check:docs before a Markdown change (its own docs.yml workflow).
  • rtk hook: rtk rewrote pnpm lint as ESLint and exited 1 on a clean tree; fixed machine-locally with exclude_commands = ["git", "pnpm lint"] in ~/.config/rtk/config.toml (add it on another machine).
  • After a Playwright bump (Dependabot), run pnpm exec playwright install chromium in apps/web before the e2e.
  • e2e in parallel: a second worktree takes E2E_PORT=<free port>.
  • Desktop app: Rust 1.90+ (rustup) and Tauri's system libraries (Linux: webkit2gtk-4.1, base-devel, librsvg…), then pnpm --filter @alltp/desktop dev (HMR on the Vite server) or pnpm --filter @alltp/desktop tauri build. docs/desktop.md covers the rest. A local build is unsigned for updates unless TAURI_SIGNING_PRIVATE_KEY is set.
  • Home test checklist (autotracking):
    1. Open seed: items and chests update within ~1 s.
    2. A manual mark is kept; "Sync from game" overwrites it, and undo brings it back.
    3. A finished dungeon fills its prize.
    4. Keysanity key count; maps and compasses.
    5. A second tab shows "Running in another tab".
    6. A race seed ("Generate Race ROM") shows the race-legal badge, and the withheld fields stay manual.

Working agreements (for a Claude Code session on another machine)

These come from the previous machine's session memory, which is not in the repo.

  • Language: the user writes in French; code, comments, commits and PRs are in English. Never add a Co-Authored-By trailer, nor a "Generated with Claude Code" line anywhere (commits, pull requests, issues).
  • Merging: merge your own and Dependabot PRs with gh pr merge <n> --rebase once every check is green and you have reviewed the diff. The owner granted a local permission rule so Claude merges its own green, reviewed PRs (in .claude/settings.local.json, not committed: add it on another machine).
  • Releases: merge the release-please PR at the end of each PR series, after reading its changelog.
  • Checks: judge every check by its exit code, never by its last line. Gate every push on the checks (ok=1; …; [ $ok -eq 1 ] && git push).
  • Quality bar (CLAUDE.md):
    • fix what you find, in its own commit;
    • verify UI in a real browser with Playwright at 390 / 820 / 1440 / 1920, plus axe and keyboard;
    • add a dependency only when it earns its place.
  • Art and look:
    • every image has a transparent background (through apps/web/scripts/transparent-icons.mjs);
    • everything not yet obtained (items, bosses, keys, maps, compasses) uses one shared greyed style;
    • item icons carry a 1-game-pixel black outline.
  • Updates (PWA):
    • F5 shows the latest version when online;
    • a stale tab is offered the update;
    • never reload on its own mid-run, except in the OBS view.
  • Platform: front-end only on GitHub Pages. When that blocks something worth building, say so and propose options. The CORS question for race seeds was settled as option 3.
  • The user's setup:
    • CachyOS with KDE (maybe GNOME or Hyprland later), RetroArch with the bsnes-mercury core, SNI, Firefox;
    • plays with a gamepad;
    • wants ALttPR alone, Super Metroid alone and SMZ3, and a desktop app for Windows, macOS and Linux with no paid store.
  • Parallel work: worktree agents are welcome. Give each its own E2E_PORT; agents in worktrees may need /usr/bin/git because of the rtk hook.

Activity

  1. kqesar commented on Oct 25, 2026

    @kqesar
    OwnerAuthor

    Superseded by #347, which keeps only what is left. This issue stays as the history.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions