New sessions land in what looks like a game repository and start treating the
task as game development, so the accessibility framing had to be re-explained
every time. CLAUDE.md states it up front: everything outside accessibility/ is
the vanilla game and is read-only reference, and a request phrased as a game
bug ("the journal doesn't work") means a screen reader user cannot use it.
Deliberately a pointer rather than a copy, so it cannot drift out of sync with
accessibility/docs/. It sends readers to docs/scratch.md first and carries only
what a fresh agent would otherwise get wrong: that the user is blind so no
verification may depend on seeing anything, the small-footprint rule, that
speech must never throw, the FoV rule, how to run the testbed, and how to read
the startup self-test when something is silent.
CLAUDE.md sits in the game tree, so it would show up in the footprint check
that must stay "main.lua | 1 +". Excluded there and in the three places the
docs quote that command.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2.5 KiB
Accessibility mod — developer documentation
Making Sulphur Memories: Alchemist 0.4.2 blind-accessible.
Player-facing keymap and setup: accessibility/README.md.
Picking this up mid-stream? Read scratch.md first. It is the living handoff document: current state, every bug found and why, and the traps.
| Doc | Contents |
|---|---|
| scratch.md | Start here. Working state, bugs found, gotchas, keymap, how to run the testbed. |
| 01-architecture.md | How the game is built: states, console, level model, keybindings. |
| 02-hooking-strategy.md | How the mod attaches with (near) zero edits to game files. |
| 03-implementation-plan.md | The original six-phase plan, with outcomes. |
| 04-open-questions.md | Questions raised; see 05 for answers. |
| 05-decisions.md | Decisions made, and the D4 correction. |
Status
Phases 0–5 complete. 23 state descriptors, 5 patches, 26/26 testbed checks, history unit tests passing.
Game-file footprint: one line in main.lua. Verify at any time:
git diff --stat vanilla-0.4.2 -- . ':!accessibility' ':!.gitignore' ':!CLAUDE.md'
The three findings that shaped everything
-
The game was already keyboard-complete. All 28 states implement
keypressed, with a full rebindable-keys system. The gap was that nothing announced what was focused — so the job was narration, not reimplementation. -
The game already writes the text.
console.printis a single chokepoint for all log messages;level.getCursorMessage()describes tiles;inventoryCursorPopup.setcovers four screens. Mostly we re-route existing strings to speech rather than writing a description engine. -
Everything is a
required singleton, anddebug.getupvalueworks. Module functions can be patched in place, and file-local focus variables (selectedOption, …) are readable from outside. Hence the one-line footprint.
Architecture in one breath
main.lua calls require("accessibility").initialize(). That patches module
singletons (console.print, popupStack.add, inventoryCursorPopup.set,
modules.slider), wraps states.switch / update / keypressed / textinput,
and installs a global hotkey layer. Per-screen narration lives in
accessibility/states/<statename>.lua descriptors, read via debug.getupvalue.
A startup self-test reports which patches and probes still resolve — the canary
for a future game version moving something.