Prompted by "do sighted players have a minimap we don't have access to?"
No widget, but two map surfaces were only half exposed: the world map
screen, and the level view itself, which IS the explored-floor map --
rememberedFoV keeps everything ever seen drawn, and a sighted player pans
over it and reads the fog edge at a glance.
The world map dropped the three things the picture carries: what kind of
place each area is (the icon shape), which way it lies, and how long
travelling there takes. The last is the number the decision turns on and
was only ever in the side panel. states/worldmap.lua is now a read() plus
details() rather than probe/render -- necessarily, because render() gets
no state table and the travel time needs the screen's currentAreaId, an
upvalue like options and selectedOption. Travel time is the game's own
getDistanceMessage, the same call the screen makes. The blurb runs to
several sentences, so it goes on = and the settle timer instead of the
focus line.
Two gotchas: options[i].icon is already an image object, so the icon id
string has to come off the model area; and describe.direction only tests
the sign of each delta, which on the map's floating-point coordinates
called almost everything diagonal, hence a 25% deadzone carrying the same
4/3 x-correction the game's own distance maths uses.
New frontier.lua answers "where haven't I been", which nothing did:
surroundings is local, examine is one tile, and the tracker only finds
things it already knows about. A frontier tile is seen, not blocking
movement, with an in-bounds unseen CARDINAL neighbour -- cardinal because
the diagonal test fires on the unseen tile behind every wall corner, so a
lit corridor would report an unexplored way per corner. Clustered
8-connected and reduced to each cluster's nearest member, so a wide
doorway is one way but two openings that both lie north stay two. Reads
only FoV/rememberedFoV and reports only THAT an opening exists, never
what is beyond it -- exactly what the fog edge shows.
Shift+End names the nearest exit without disturbing the tracker
selection. Note for later: level.exits does not exist at runtime, it is
packed as nil and never assigned. The primitive is the game's own
getListByTag("exit"), which already gates on FoV or rememberedFoV, skips
hidden tiles and sorts by distance. Exits were already a tracker
category, so this is a shortcut rather than a new capability.
"remembered" moves from verbose to normal verbosity. tileFacts reads
actors only when a tile is currently VISIBLE, so a remembered tile is
silent about whatever may be standing on it now, and at normal verbosity
that silence was indistinguishable from "nothing there". A safety flag,
not a nicety.
Also adds descriptor.probeNames: a read() descriptor has no probe table,
so menus.selfTest could only report "custom read()" and the version-bump
canary was lost on exactly the screens reaching for the most upvalues.
worldmap and research now declare their game locals and get the same
PROBE BROKEN check.
120 testbed checks, 0 failures. It caught two things review had not. The
world map said "You are currently here." twice, because
getDistanceMessage and getLastVisitedMessage return the same sentence for
the area you are standing in -- fine as two panels on screen, a stutter
in one utterance. And the first frontier check walked a step east and
demanded the numbers change; it failed on a run where nothing was wrong,
the step landing but both openings keeping the same Chebyshev distance.
Replaced with the deterministic property: every reported distance must
equal Chebyshev from the player's current position, nearest first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alchemist Accessibility
A blind-accessibility mod for Sulphur Memories: Alchemist 0.4.2 — screen reader speech, an object tracker, tile-by-tile exploration and directional wall tones.
Speech goes through Tolk, so it uses whichever screen reader you already have running (NVDA, JAWS, System Access) and falls back to SAPI 5 if none is.
This repository contains only the mod. It does not contain the game, and you need your own copy. Developed against version 0.4.2.
Installing
You need the game as an extracted source folder — a directory with main.lua
and conf.lua in it — rather than a packed .love or the Steam build. The mod
loads its TTS libraries by absolute path, which cannot work from inside a
.love archive.
1. Copy the accessibility/ folder from this repository into the game
folder, next to main.lua:
Sulphur Memories Alchemist 0.4.2/
main.lua
conf.lua
states/
models/
accessibility/ <- this repository's accessibility/ folder
2. Add one line to main.lua. Find love.load() (around line 95) and add
the marked line immediately after console.prepare():
function love.load()
...
user.initialize()
states.setup()
console.prepare()
require("accessibility").initialize() --ACCESSIBILITY MOD: sole entry point
local messages = require("models.messages")
That is the entire game-side change — one line. Everything else the mod does
is applied at runtime from inside accessibility/, which is what lets it be
lifted onto a new game version cheaply.
The placement matters: by that point templates are preprocessed, keybindings are
loaded, states.setup() has run and the console canvas exists, and it is still
before the first states.switch().
If you prefer, install.patch in this repository applies the same change:
git apply install.patch # from inside the game folder
3. Run the game.
"C:\Program Files\LOVE\love.exe" "C:\path\to\Sulphur Memories Alchemist 0.4.2"
You should hear "Main menu" once it loads.
Checking it works
Run with lovec.exe instead of love.exe to get a console window. Every
spoken line is mirrored there, and at startup the mod prints a self-test:
[accessibility] TTS initialised
[accessibility] TTS: available
[accessibility] speech channel: yes
[speech]! Main menu
TTS: UNAVAILABLE— the DLLs inaccessibility/lib/were not loadable.speech channel: NO— Tolk loaded but found no screen reader and no SAPI, so nothing will be audible.
The self-test also reports every patch and every menu probe. If a future game
version renames something the mod reads, it shows up here at startup as
PROBE BROKEN: no upvalue 'x' rather than as silence three screens into a
playthrough.
What it adds
Full key list and explanation: accessibility/README.md.
In brief:
| Message log | [ / ] walk back and forth, \ repeats, Shift+\ copies. New messages are spoken automatically. |
| Looking around | / scans all eight neighbours, ; says where you are, Ctrl+numpad probes one direction without moving. The game's own examine mode (x) is fully spoken. |
| Object tracker | PageUp/PageDown with Ctrl/Shift to move through categories, kinds and instances; Home for distance and path length; Ctrl+Home to walk there. |
| Your character | ' for health and effects, , for the selected action. Damage and healing are announced unprompted. |
| Wall tones | Each step, a tone sounds from the direction of any wall within five tiles, positioned in 3D. |
| Menus | All 23 screens are spoken, including the journal's page text, inventory, crafting, dialogue and the options screens. |
The tracker never reports anything outside the game's own field of view or your remembered map — it is an accessibility aid, not a cheat.
Reporting problems
Run with lovec.exe and include the console output. [speech] lines show
exactly what was said and when — ! interrupted, * urgent, queued.
The most useful report is something that was silent when it should not have been, together with the screen you were on and the key you pressed.
For developers
Start with CLAUDE.md for orientation, then
accessibility/docs/ — docs/README.md maps it, and
docs/scratch.md is the living handoff document with the current state of play
and the traps already discovered.
There is a code-driven test harness that plays the game by itself and checks what gets spoken:
"C:\Program Files\LOVE\lovec.exe" . --a11y-test
Design goal throughout: change as little game code as possible, so the mod re-applies cleanly to a new game version.
Credits and licensing
The mod is built on love2talk
(vendored in accessibility/vendor/love2talk/, see VENDORED.md there for the
local modifications and the licence).
accessibility/lib/ contains:
- Tolk — screen reader abstraction, LGPL 3.0
- nvdaControllerClient64.dll — NVDA controller client
- SAAPI64.dll — System Access API
These ship with the mod because Tolk loads them through the OS loader, so they must sit on disk beside the game rather than inside an archive.
Sulphur Memories: Alchemist is the property of its developers and is not included or redistributed here.