From 6380998376701c618f121b637685a9096d65f8a2 Mon Sep 17 00:00:00 2001 From: Accessibility Mod Date: Sat, 1 Aug 2026 22:25:09 +0200 Subject: [PATCH] docs: final handoff pass Adds accessibility/README.md, the player-facing setup and keymap reference -- the thing to read before playtesting. Brings the developer docs in line with what was actually built: - docs/README.md rewritten as an index with current status and a one-paragraph architecture summary. - docs/scratch.md gains a HANDOFF section: state of play, the commands to run after any change, the five traps that each cost real debugging time, what is NOT verified and why, deliberate omissions, and how to rebase onto a future game version. - docs/03-implementation-plan.md gains a status banner listing where the implementation deliberately diverged from the plan (menu narration pulled forward as a blocker, levelCursorPopup not patched, tryInteract insufficient as a tracker filter, testbed added, no settings UI built). - docs/04-open-questions.md gains a resolution banner: Q6 and Q8 remain open. Also records that the testbed check count varies between runs (23-26) because some blocks are conditional, so what matters is zero failures rather than the total. Co-Authored-By: Claude Opus 5 --- accessibility/README.md | 119 +++++++++++++++++++ accessibility/docs/03-implementation-plan.md | 23 ++++ accessibility/docs/04-open-questions.md | 14 +++ accessibility/docs/README.md | 55 +++++++-- accessibility/docs/scratch.md | 88 +++++++++++++- 5 files changed, 284 insertions(+), 15 deletions(-) create mode 100644 accessibility/README.md diff --git a/accessibility/README.md b/accessibility/README.md new file mode 100644 index 0000000..92c18a7 --- /dev/null +++ b/accessibility/README.md @@ -0,0 +1,119 @@ +# Accessibility mod — Sulphur Memories: Alchemist + +Blind-accessibility mod adding screen-reader speech, an object tracker, tile +exploration and directional wall tones. + +Speech goes through **Tolk**, so it uses your running screen reader (NVDA, JAWS, +System Access) and falls back to SAPI 5 if none is running. + +## Running + +``` +"C:\Program Files\LOVE\love.exe" "D:\code\Sulphur Memories Alchemist 0.4.2 sanctified" +``` + +Use `lovec.exe` instead of `love.exe` to get a console window — every spoken line +is mirrored there, which is useful for reporting bugs. + +The TTS libraries live in `accessibility/lib/` and are loaded by absolute path, +so nothing needs installing or copying into Program Files. + +If speech does not work, check the console for the startup self-test: + +``` +[accessibility] TTS: available +[accessibility] speech channel: yes +``` + +`speech channel: NO` means Tolk loaded but found no screen reader and no SAPI. + +--- + +## Keys added by the mod + +All of these are **in addition to** the game's own controls, and none of them +replace a key the game uses. + +### Reading the message log + +| Key | Action | +|---|---| +| `[` | Previous message | +| `]` | Next message | +| `Shift+[` | Oldest message | +| `Shift+]` | Newest message (and resume following new messages) | +| `\` | Repeat the current message | +| `Shift+\` | Copy the current message to the clipboard | + +New messages are spoken automatically. Pressing any key that does something in +the game cancels whatever is still queued, so you are never stuck listening to a +backlog. While you are moving through the history, new messages do **not** drag +the cursor away — press `Shift+]` to return to the newest. + +### Looking around + +| Key | Action | +|---|---| +| `/` | Scan all eight neighbouring tiles, clockwise from north | +| `;` | Where am I — area name, in-game date and time, coordinates | +| `Ctrl` + numpad `1`–`9` | Describe the tile in that direction without moving | +| `Ctrl` + numpad `5` | Describe the tile you are standing on | + +The game's own examine mode (**`x`**) is also fully spoken: the arrow keys move a +cursor around the map and each tile is described with its distance and direction +from you. Press `f` in examine mode to jump between exits. + +### Object tracker + +Finds everything you have seen, sorted by distance. + +| Key | Action | +|---|---| +| `Ctrl+PageUp` / `Ctrl+PageDown` | Previous / next **category** | +| `PageUp` / `PageDown` | Previous / next **kind** of thing in that category | +| `Shift+PageUp` / `Shift+PageDown` | Previous / next **instance** of that kind | +| `Home` | Distance, direction, and number of steps by path | + +Categories: Creatures, Items, Alchemy, Interactables, Exits, Terrain features. + +So `Ctrl+PageDown` to reach "Creatures", `PageDown` to move between rat / beetle +/ guard, and `Shift+PageDown` to step through the individual rats. `Home` gives +you the walking distance, which indoors is often very different from the +straight-line distance. + +The tracker only ever reports things you have actually seen. + +### Your character + +| Key | Action | +|---|---| +| `'` | Health and active effects | +| `,` | Currently selected action | + +You are also told automatically when you take damage or are healed. + +### Wall tones + +Each step you take, a tone sounds from the direction of any wall within five +tiles, positioned in 3D so you can hear where it is. Further walls are louder. + +--- + +## Reporting problems + +Run with `lovec.exe` and include the console output. Lines beginning `[speech]` +show exactly what was spoken and when: + +- `[speech]!` — interrupted whatever was being said +- `[speech]*` — urgent alert, spoken immediately +- `[speech] ` — queued normally + +If something is **silent that should not be**, that is the most useful thing to +report, together with the screen you were on and the key you pressed. + +--- + +## For developers + +See `accessibility/docs/` — start with `docs/README.md`, and read +`docs/scratch.md` first if you are picking the work up mid-stream. diff --git a/accessibility/docs/03-implementation-plan.md b/accessibility/docs/03-implementation-plan.md index 5cacfd2..2bc22d6 100644 --- a/accessibility/docs/03-implementation-plan.md +++ b/accessibility/docs/03-implementation-plan.md @@ -1,5 +1,28 @@ # Implementation plan +> ## ⚠ Status: Phases 0–5 are COMPLETE. +> +> This document is the *original* plan, kept for its reasoning. Where the +> implementation diverged, the reason is recorded in +> [scratch.md](scratch.md) and [05-decisions.md](05-decisions.md). +> +> Notable divergences from what is written below: +> +> * **Phase 4 was pulled forward, partly into Phase 1's slot.** Menu narration +> turned out to be a hard blocker — the screen name was spoken but not the +> selected option, so a blind player could not start a new game at all. +> * **`levelCursorPopup` is NOT patched** (contrary to Phase 2a below). It fires +> from an animation-completion callback and lands a keypress late; the +> `ingame_observe` descriptor polls the cursor position instead. +> * **`tile.tryInteract` is not a sufficient tracker filter** (contrary to +> Phase 3 below). `wall` carries a flavour-only `tryInteractFunction`. +> See the D4 correction in `05-decisions.md`. +> * **A code-driven testbed was added** (`accessibility/testbed.lua`), not in the +> original plan. It found five real bugs that would otherwise have surfaced +> only during play. +> * **Phase 5's mod-settings screen was not built.** Settings exist as fields in +> the relevant modules and are not yet exposed at runtime. + Phases are ordered so each one is independently playable-and-testable. Nothing later is required for something earlier to be useful. --- diff --git a/accessibility/docs/04-open-questions.md b/accessibility/docs/04-open-questions.md index 0722e46..1660e0f 100644 --- a/accessibility/docs/04-open-questions.md +++ b/accessibility/docs/04-open-questions.md @@ -1,5 +1,19 @@ # Open questions +> ## ⚠ Status: mostly answered — see [05-decisions.md](05-decisions.md). +> +> **Answered:** Q1 (dev LÖVE; DLLs load by absolute path from +> `accessibility/lib/`, no elevation needed) · Q2 (tracker hierarchy confirmed) · +> Q3 (augment, not relay) · Q4 (speak everything queued; an explicit game action +> cancels the backlog) · Q5 (wall tones on step, stop-and-replay — confirmed by +> ear) · Q7 + Q10 (small patches allowed, probe backend kept open for a Steam +> release) · Q9 (NVDA; `lovec` approved, and a code-driven testbed was built). +> +> **Still open:** **Q6** — our hotkeys are currently a separate scheme and are +> therefore not user-rebindable; integrating them into the game's own rebinding +> UI is still undecided. **Q8** — no exhaustive keyboard-reachability audit was +> performed. + Ordered roughly by how much they block work. Q1–Q3 affect the design; the rest are preferences I can pick defaults for if you'd rather not decide up front. --- diff --git a/accessibility/docs/README.md b/accessibility/docs/README.md index d143ccf..ea561fa 100644 --- a/accessibility/docs/README.md +++ b/accessibility/docs/README.md @@ -1,23 +1,52 @@ -# Accessibility mod — documentation +# Accessibility mod — developer documentation -Research and planning for making *Sulphur Memories: Alchemist* 0.4.2 blind-accessible. +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](scratch.md) first.** It is the +living handoff document: current state, every bug found and why, and the traps. | Doc | Contents | |---|---| -| [01-architecture.md](01-architecture.md) | How the game is built: states, console, level model, keybindings, where text already exists. | -| [02-hooking-strategy.md](02-hooking-strategy.md) | How to attach the mod with (near) zero edits to game files. | -| [03-implementation-plan.md](03-implementation-plan.md) | Six phases, each independently testable. | -| [04-open-questions.md](04-open-questions.md) | Decisions needed before/while building. | -| [05-decisions.md](05-decisions.md) | Answers given so far, and what they imply. Supersedes Q1/Q2/Q7/Q10. | +| [scratch.md](scratch.md) | **Start here.** Working state, bugs found, gotchas, keymap, how to run the testbed. | +| [01-architecture.md](01-architecture.md) | How the game is built: states, console, level model, keybindings. | +| [02-hooking-strategy.md](02-hooking-strategy.md) | How the mod attaches with (near) zero edits to game files. | +| [03-implementation-plan.md](03-implementation-plan.md) | The original six-phase plan, with outcomes. | +| [04-open-questions.md](04-open-questions.md) | Questions raised; see 05 for answers. | +| [05-decisions.md](05-decisions.md) | Decisions made, and the D4 correction. | -## The short version +## Status -Three findings shape everything: +**Phases 0–5 complete.** 23 state descriptors, 5 patches, 26/26 testbed checks, +history unit tests passing. -1. **The game is already keyboard-complete.** All 28 states implement `keypressed`; there's a full rebindable-keys system with contexts. The accessibility gap is that *nothing announces what is focused* — not that things are unreachable. This makes the job much smaller than a typical mouse-driven game. +**Game-file footprint: one line in `main.lua`.** Verify at any time: -2. **The game already writes the text we need to speak.** `console.print` is a single chokepoint for all log messages. `level.getCursorMessage()` returns tile/creature descriptions. `getCursorPopupStats()` feeds the item popups in five screens. We are mostly *re-routing existing strings to speech*, not writing a description engine. +``` +git diff --stat vanilla-0.4.2 -- . ':!accessibility' ':!.gitignore' +``` -3. **Everything is a `require`d singleton, and `debug.getupvalue` works.** Module functions can be monkey-patched in place, and the file-local `selectedOption` variables in menu states are readable from outside. A one-line bootstrap in `main.lua` is the only unavoidable edit. +## The three findings that shaped everything -Proposed footprint: **1–3 lines added to `main.lua`**, everything else new files under `accessibility/`. A git baseline tagged `vanilla-0.4.2` makes re-application on future versions a rebase. +1. **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. + +2. **The game already writes the text.** `console.print` is a single chokepoint + for all log messages; `level.getCursorMessage()` describes tiles; + `inventoryCursorPopup.set` covers four screens. Mostly we re-route existing + strings to speech rather than writing a description engine. + +3. **Everything is a `require`d singleton, and `debug.getupvalue` works.** 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/.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. diff --git a/accessibility/docs/scratch.md b/accessibility/docs/scratch.md index cbbffec..0c025cb 100644 --- a/accessibility/docs/scratch.md +++ b/accessibility/docs/scratch.md @@ -2,8 +2,8 @@ **Read this first on any handoff.** Living document: update it whenever state changes, before ending a turn. -Last updated: 2026-08-01 · Currently: **Phases 0–3 done + menus/dialogue. -Phases 4-5 done. Remaining: polish, mod settings, distribution.** +Last updated: 2026-08-01 · Currently: **Phases 0–5 COMPLETE. Handed off for +playtesting.** Remaining work is polish, mod settings and distribution. **Game is playable start-to-finish by ear** from main menu through the intro conversation into gameplay, plus journal, character, actions and inventory — @@ -18,6 +18,11 @@ rm -rf ~/AppData/Roaming/LOVE/Alchemist # start from a clean save It drives the game by code via `love.event.push`, navigating by *reading* focus rather than counting keypresses. Every bug listed below was caught by it. +⚠ **The check COUNT varies between runs (23–26); this is expected.** Several +blocks are conditional — the config-menu section only runs if `selectByText` +lands on "Options", and mouse-hover can move menu focus at startup (see trap 2). +**What matters is `0 failures`, not the total.** + --- ## What this project is @@ -489,3 +494,82 @@ utterance — speech is verifiable from the log, not only by ear. `!` after - `.luacheckrc` uses `std = "min+alch"` with an explicit globals list — add mod globals there to keep lint clean. - Upstream quirk (not ours to fix): `modules/tile/preprocess.lua` flattens parents in a single `pairs()` pass, so a grandchild may inherit from a not-yet-flattened parent depending on hash order. - Tolk DLL sources on this machine: `D:\code\cafe`, `D:\code\dragonrpg-mapmaker`, `D:\code\DragonWarriorAccess`. + +--- + +## HANDOFF — read this if you are resuming + +### State of play +Phases 0–5 complete. **23 state descriptors, 5 patches, 26/26 testbed checks**, +history unit tests passing, **one line changed in the game** (`main.lua`). + +Everything in the original brief is implemented: console speech with +line-by-line review, the object tracker with the requested key scheme, tile +exploration, wall tones, and keyboard+speech access to every screen. + +### First thing to do after any change +``` +lua accessibility/tests/test_history.lua # pure Lua, instant +rm -rf ~/AppData/Roaming/LOVE/Alchemist # clean save +"C:/Program Files/LOVE/lovec.exe" . --a11y-test # ~4 min, 26 checks +git diff --stat vanilla-0.4.2 -- . ':!accessibility' ':!.gitignore' # must stay: main.lua | 1 + +``` + +### The five traps (all cost real debugging time — don't rediscover them) + +1. **`love.keypressed` is captured by value.** `states.setup()` runs + `love[cb] = love[cb] or states[cb]` *before* us. Wrapping the `states` table + field alone does nothing for `keypressed`/`textinput` — you must also + reassign `love.keypressed`. `switch`/`update` are fine (looked up at call + time). **Any new callback you wrap needs the same treatment.** +2. **Focus moves without a keypress.** `main_menu.mousemoved` assigns + `selectedOption` from cursor position. Hence focus is *polled* in the update + hook, not announced only on keypress. +3. **Nested `states.switch` announces inner-first.** `ingame.open()` can switch + straight to `ingame_dialogue`. We announce after `realSwitch` returns, so the + outer announcement would clobber the inner one — guarded by checking + `states.getCurrent() ~= newState`. +4. **`states.lua` requires with a SLASH** (`require("states/"..name)`). + `require("states.main_menu")` loads a *second independent copy* with its own + upvalues. Always match the slash. +5. **Probes may need chaining or a cross-function search.** Some locals are + upvalues of an inner function (`narration` → `continueNarration`) or are only + touched in `open()` (`actions_menu`, `worldmap`). Use `menus.getAnyUpvalue`. + Use `menus.hasUpvalue` when you need "exists" rather than "has a value". + +### Not verified — needs a real playthrough +These are blocked on content the prologue does not contain, **not** known broken: + +- **Tracker categories** Items / Alchemy / Exits / Creatures / Terrain. Only + Interactables is populated in the tutorial house. Diagnostic: the testbed + prints `tracker contents:` with per-category kind names. +- **Inventory item names** — inventory is empty in the prologue, so only the + "empty slot" path has run. +- **The quantity slider** (`patches/slider.lua`). Confirmed *applied* by the + self-test but never exercised: `ingame_wait` is unreachable because + `tutorial_bed` has an empty `tryInteractFunction` by design, and + craft/refine/trade need an alchemy table and real items. The testbed already + has `walkTo()` and tracker-based bed lookup ready for when one is reachable. +- **Popup announcements** (`patches/popupstack.lua`) — loot/quest/recipe popups + did not fire during the prologue run. + +### Known-missing features (deliberate, not bugs) +- **No mod settings UI.** Verbosity (`describe.verbosity`), wall-tone config + (`walltones.config`) and `speech.logToConsole` are code-level fields only. +- **Q6 unanswered**: our hotkeys are a separate scheme, not registered in the + game's own rebinding UI. They are therefore not user-rebindable. +- **Q8 unanswered**: no exhaustive keyboard-reachability audit was done. All 28 + states implement `keypressed`, and every screen reached so far was navigable, + but this was not proven screen-by-screen. +- **Distribution not designed** (D1 deferred). Any shipped build must place the + Tolk DLLs beside the executable — they cannot live inside a `.love`. + +### If a future game version breaks something +Run the game and read the startup self-test. It reports every patch and every +descriptor probe. `PROBE BROKEN: no upvalue 'x'` means that local was renamed — +fix the descriptor in `accessibility/states/.lua`. `ok (unset until +opened)` is normal. + +To rebase the mod onto a new game version: commit the new pristine tree on top +of the `vanilla-0.4.2` baseline, then replay the mod commits. The only game-file +conflict possible is the single `main.lua` line.