Opening the journal and selecting an entry announced its title and stopped, which is an index into content the player could never reach. Page flipping was silent too. The screen is three widgets at once: the tab row (q/e), the list of entry titles down the left (up/down), and the page on the right holding the actual text (left/right). The descriptor read only the first two. It now reads options[selectedOption].pages[selectedPageId] and speaks its text blocks. A page is bounded by what fits on screen, so it is safe as a single utterance. Layout newlines are flattened first -- splitTextByPages joins the game's own wrapped lines with \n, so left in they make the reader pause mid-sentence at every wrap point. Page position is announced only when there is more than one page, and the blocks are pushed into the console history so [ and ] walk back through a long lore entry after the utterance has gone by. The tab leads only when the tab changed and the title only when the entry changed, so flipping pages does not bury the new page under a repeated heading. That suppression made read() stateful, which is a trap: it is called for the same focus from four places -- the per-frame poll, the post-keypress announce, repeatFocus, and the testbed's before/after probes -- so a poll would consume the "changed" flag and the real announcement would come out headless. The descriptor now caches its last (key, text) and replays it whenever the key is unchanged, making read() idempotent; the suppression state advances exactly once per genuine focus change. Adds a generic descriptor.reset hook, called from menus.announceScreen. The memory lives in file-locals that outlive the screen, so without it reopening the journal on the entry you left it on would read the page with no title. Testbed asserts the spoken text contains the page body rather than merely being non-nil. 29 checks, 0 failures. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
153 lines
5.4 KiB
Markdown
153 lines
5.4 KiB
Markdown
# 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 |
|
||
| `Ctrl+Home` | Walk to it |
|
||
|
||
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.
|
||
|
||
`Ctrl+Home` does exactly what left-clicking the tile does with a mouse: it queues
|
||
a walk along the game's own path. If the thing itself cannot be stood on — a
|
||
creature, a chest, a bed — you walk to the nearest tile beside it instead, and
|
||
the announcement says "next to" rather than "to". As with a mouse click, the walk
|
||
stops early if something interrupts you, and while an enemy is in view the game
|
||
only ever commits one step at a time.
|
||
|
||
Your selection sticks. The list is rebuilt and re-sorted by distance every turn,
|
||
but the tracker follows the thing you picked rather than the slot it was in, so a
|
||
rat you are tracking stays selected as it moves around. It is only let go when it
|
||
genuinely stops being visible.
|
||
|
||
The tracker only ever reports things you have actually seen.
|
||
|
||
### The journal (`j`)
|
||
|
||
The journal has three parts, and all three are spoken:
|
||
|
||
| Key | Moves |
|
||
|---|---|
|
||
| `q` / `e` | Between tabs — Quests, Lore, Bestiary, Components, Recipes, Tutorials |
|
||
| Up / Down | Between entries in the current tab |
|
||
| Left / Right | Between pages of the selected entry |
|
||
|
||
Selecting an entry reads its title, its position in the list, and then the text
|
||
of the page itself. The tab name is only spoken when you change tabs, and the
|
||
title only when you change entry, so flipping pages doesn't make you sit through
|
||
the heading again.
|
||
|
||
Long entries run to several pages; you are told `page 2 of 3` when there is more
|
||
than one. The page text also goes into the message history, so `[` and `]` will
|
||
walk back through it and `\` re-reads a line — useful for a long lore entry you
|
||
want to take in a piece at a time.
|
||
|
||
### 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.
|