7.1 KiB
SCRATCH — current working state
Read this first on any handoff. Living document: update it whenever state changes, before ending a turn.
Last updated: 2026-08-01 · Currently: Phase 0 complete. Phase 1 next.
What this project is
Blind-accessibility mod for Sulphur Memories: Alchemist 0.4.2 (LÖVE 11.5, Lua/LuaJIT), extracted to
D:\code\Sulphur Memories Alchemist 0.4.2 sanctified. All mod code lives under accessibility/.
Design goal: change as little game code as possible so the mod re-applies cleanly to new versions.
Full research is in accessibility/docs/ — read README.md there for the map.
Confirmed decisions (do not relitigate)
| # | Decision |
|---|---|
| D1 | Dev uses system LÖVE at C:\Program Files\LOVE\ (lovec.exe for console output). Distribution deferred. |
| D2 | Tracker hierarchy: category (Ctrl+PgUp/Dn) → kind (PgUp/Dn) → instance (Shift+PgUp/Dn), Home = distance+direction. |
| D3 | Small explicit patches to game files are allowed where they simplify things — but every state descriptor also carries a probe spec so we can flip to zero-edit debug.getupvalue mode if the game ships fused on Steam. render is shared by both backends. |
| D4 | Six tracker categories confirmed and coverage-verified: Creatures / Items / Exits / Interactables / Terrain features / Alchemy. |
| — | Screen reader: NVDA. Running the game via lovec during dev is approved. |
| — | User is blind — all verification must be describable in text; don't rely on screenshots. |
Still-open questions (in 04-open-questions.md)
Q3 verbosity (relay vs augment) · Q4 console auto-speak policy · Q5 wall-tone triggers · Q6 keybinding integration vs separate · Q8 full keyboard audit as deliverable?
None block Phase 0. Q4 lands in Phase 1, Q5 + verbosity in Phase 2.
Key facts worth not rediscovering
console.print(msg, colorTemplate)inmodules/console.luais the single chokepoint for all game log text. Repeated identical messages coalesce into"msg (x3)"via atimescounter. Buffer entries expire after 3s — useless as history, so we keep our own.level.getCursorMessage()returns the description under the examine cursor (actor first, then top visible tile). The game already has a full arrow-key examine mode:states/ingame_observe.lua.cursorPopup.set(...)— patchingviews/inventoryCursorPopup.lua+views/levelCursorPopup.luanarrates focus ininventory,craft,refine,trade,ingame_observeat once.- All 28 states already implement
keypressed. The gap is narration, not reachability. modules/tile.lua:234:tile.tryInteract = (self.loot or self.tryInteractFunction) and function...— authoritative "player can do something here" flag. Basis of tracker classification.- Template counts: 231 tiles, 40 actors, 344 items. Parent inheritance is already flattened by
modules/tile/preprocess.luaduringlove.load, so read fields directly. - 6 un-nameable mechanical templates to exclude:
blank,throw_blocker,movement_blocker,fire_detector,wall_features_position_dummy,ladder_down_hidden. debug.getupvalueworks on this Lua build — verified readingselectedOption/optionsListfrom a state'skeypressed.- Tolk constraint:
ffi.load("Tolk")uses the OS loader, so DLLs must sit next tolove.exe— they can never live inside a.lovearchive. tolk.luacallsTolk_Load()at require time with no error handling, andLove2talk.lualeaksbackendas a global. Alwayspcallthe require.
Current status
Done
- Full architecture research →
docs/01-architecture.md - Hooking strategy →
docs/02-hooking-strategy.md - Phased plan →
docs/03-implementation-plan.md - Open questions →
docs/04-open-questions.md - Decisions + category coverage proof →
docs/05-decisions.md
Done — Phase 0 (bootstrap) · commit 5c581ed
git init, pristine tree committed and taggedvanilla-0.4.2(commitbbb70d0, 1838 files,accessibility/deliberately excluded so the tag is genuinely clean).- love2talk vendored to
accessibility/vendor/love2talk/— seeVENDORED.mdthere for the five local modifications. - DLL elevation problem solved.
Tolk.dlletc. live inaccessibility/lib/and are loaded by absolute path vialove.filesystem.getSource(), withSetDllDirectoryApointing at that folder so Tolk can find its own screen-reader clients. No admin rights, no copying intoProgram Files. Tracked in git via a!accessibility/lib/*.dllun-ignore. accessibility/speech.lua— pcall-guarded, frame-queued,logToConsole = true.accessibility/init.lua— patch registry +states.switch/states.updatehooks- startup self-test.
accessibility/stateNames.lua— spoken names for all 28 states.- One line added to
main.lua. Nothing else in the game touched.
Verified working via lovec.exe .:
[accessibility] TTS initialised
[accessibility] TTS: available
[accessibility] speech channel: yes
[speech]! Main menu
Next — Phase 1: console history + review keys
Wrap console.print, keep our own unbounded history (the game's expires after 3s),
mirror its (x3) coalescing, add review keys. Needs Q4 answered (auto-speak policy)
but a sensible default can ship first.
Invariants for all mod code
- Every wrapper calls through to the original and returns its results verbatim (
return realFn(...)— preserve multiple returns). - Speech must never throw. Missing DLL ⇒ silent no-op, game still runs.
- No behaviour change for sighted players.
- Never reveal information outside
FoV/rememberedFoV— the tracker must not become a cheat. - Grid scans (
xMax × yMax) happen on keypress or turn change, never per frame. - Our keys fall through to the game handler unless we own them.
Modified game files (keep this list exhaustive)
| File | Change | Why |
|---|---|---|
main.lua |
+1 line: require("accessibility").initialize() after console.prepare() |
Sole mod entry point. Placed there because templates, keybindings, states.setup() and the console canvas are all ready by then, and it's before the first states.switch(). |
Verify this list is exhaustive at any time with:
git diff --stat vanilla-0.4.2 -- . ':!accessibility' ':!.gitignore'
Currently: main.lua | 1 +.
How to run
cd "D:/code/Sulphur Memories Alchemist 0.4.2 sanctified"
"C:/Program Files/LOVE/lovec.exe" .
lovec (not love) gives the console, where [speech] lines mirror every
utterance — speech is verifiable from the log, not only by ear. ! after
[speech] means the utterance interrupted.
Scratchpad / notes
.luacheckrcusesstd = "min+alch"with an explicit globals list — add mod globals there to keep lint clean.- Upstream quirk (not ours to fix):
modules/tile/preprocess.luaflattens parents in a singlepairs()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.