Both aiming modes announced their name and then went silent, so there was no way to know what the arrow keys were pointing at. Prompted by "it says targetting, melee, but when I press the arrow keys it does not tell me what I am currently selecting." They look like a pair and are not, which is the whole shape of the fix. Throw has a real free-roaming cursor on level.cursorX/cursorY, so its descriptor is ingame_observe plus one clause: whether the tile is a legal target. That clause is recomputed from level.FoV, level.interactableArea and level.canThrowOnCell rather than read off the state's targetAllowed upvalue, because ingame_targeting_throw.lua:228 sets targetAllowed false for the whole 0.3s cursor glide and only the animation's onFinish restores it -- and mid-glide is exactly when we speak, so it would have said "blocked" on every single move. The same class of trap as the examine-cursor popup lag, and the same answer: read the model, not the view. Melee has no cursor and no target state at all. ingame_targeting_melee.lua:184-195 takes one direction key and fires the callback in that same keypress, so nothing is ever "currently selected" and there is no focus for a read() to return. Its descriptor speaks only on entry, listing the eight adjacent tiles -- which are precisely the legal targets, the game's rule being Chebyshev <= 1. Arrows still commit immediately; the game plays as it did. A two-step confirm cursor was considered and rejected for that reason. That entry line is surroundings.scan()'s sentence, so scan() splits into describeNeighbours() returning the text and scan() speaking it. onOpen grows the ability to be a function rather than a fixed string, since the neighbours differ every time. menus.selfTest needed a branch for a descriptor with neither read nor probe -- melee is the first -- or it would index a nil probe and take the whole self test down with it, which is the one report that must never fail quietly. Neither mode is reachable by its key in the prologue: b is filtered out by the room's level.allowedActions, and t falls through to gotoInventory() when there is nothing throwable. Found that by writing the testbed block as keypresses and watching it land in the inventory. It now drives both states directly, borrowing levelWindow, hpbar and levelDescriptionWindow off the ingame state's upvalues the way gotoAction would pass them. 129 testbed checks, 0 failures. The throw block asserts the focus text CHANGED across a cursor move rather than merely being non-nil, which is the assertion that would have caught the targetAllowed mistake had it been made. Still unverified against real content: only "can throw" has been heard, not "blocked", "out of range" or "no line of sight". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
342 lines
14 KiB
Markdown
342 lines
14 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 |
|
||
| `Shift+/` | Cycle automatic announcements of what comes in and out of view |
|
||
| `;` | 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 |
|
||
| `End` | Unexplored ways — where there is still somewhere to go |
|
||
| `Shift+End` | The nearest way out of this level |
|
||
|
||
#### Getting your bearings on a whole floor (`End`)
|
||
|
||
`/` and the numpad probes tell you about the tiles beside you. `End` tells you
|
||
about the *floor*: it looks at everywhere you have already been, finds the places
|
||
where explored ground runs up against ground you have never seen, and reads them
|
||
out nearest first.
|
||
|
||
> `3 unexplored ways: 6 tiles north, 11 tiles south-west, 14 tiles east`
|
||
|
||
Each "way" is one opening, so a wide doorway counts once rather than three times.
|
||
When there is nowhere left to go you get `No unexplored ways from here`, which is
|
||
usually the moment to take `Shift+End` and leave.
|
||
|
||
This never tells you anything about ground you have not seen — only that there is
|
||
still a way through. It is the same thing a sighted player reads off the edge of
|
||
the explored area on screen.
|
||
|
||
`Shift+End` names the nearest exit you know about, how far away it is, and how
|
||
many steps it takes to walk there. Exits are also a tracker category (below), so
|
||
`Shift+End` is a shortcut rather than the only route — and it deliberately leaves
|
||
whatever you were tracking untouched.
|
||
|
||
#### Announcing things as they come into view (`Shift+/`)
|
||
|
||
`/` tells you what is around you *now*. `Shift+/` decides how much of that
|
||
happens by itself as you walk, cycling three settings:
|
||
|
||
- **View announcements when you stop** *(the default)* — changes pile up while
|
||
you are moving and are read out once you have stood still for a moment. A
|
||
single step reports as soon as the step lands. A ten-tile walk gives you **one
|
||
combined summary at the end** rather than ten reports on top of each other,
|
||
and it is a *net* change: something that came into view halfway along and went
|
||
out again before you stopped is not mentioned.
|
||
- **View announcements on every step** — each step reports its own change. Best
|
||
when you are creeping along carefully; too talkative for crossing a room.
|
||
- **View announcements off**.
|
||
|
||
You hear things arriving — `In view: a rat, 4 tiles north-east. barrel, 3,
|
||
nearest 2 tiles east` — and creatures leaving: `Out of view: a rat`. Only
|
||
creatures are reported when they leave. Furniture drops out of sight constantly
|
||
as walls come between you and it, and being told the bed is out of view every
|
||
other step would bury everything useful; a creature you can no longer see is
|
||
worth knowing about.
|
||
|
||
These announcements are queued rather than urgent, so anything you asked for
|
||
yourself is spoken first, and any key that acts in the game clears them.
|
||
|
||
Nothing outside your current field of view is ever reported.
|
||
|
||
#### Seeing inside containers (the game's own `s`)
|
||
|
||
The game's tutorial tells you to press **`s`** to see the contents of containers.
|
||
For a sighted player that draws the icon of whatever is inside every chest, sack
|
||
and pile you can see, straight onto the map. Pressing it now **speaks the same
|
||
thing**, nearest first:
|
||
|
||
```
|
||
A wooden chest, 2 tiles east, A bunch of ivy leaves, 3, A torch.
|
||
A burlap sack, 5 tiles north-west, Coarse salt
|
||
```
|
||
|
||
or `No loot in view` when there is nothing to find, so you can tell the key
|
||
worked rather than wondering whether it was dropped.
|
||
|
||
The icons can only show one item per container; you get the **whole list**. Each
|
||
container is also pushed into the message history, so `[`, `]` and `\` walk back
|
||
through a long list after it has been read out.
|
||
|
||
This is your own key — whatever you have `Highlight loot` bound to in the game's
|
||
controls, and however you have set *"highlight overlays require holding the
|
||
key"*. Clicking the toolbar icon works too. It also works in examine mode and
|
||
while aiming. Only containers you can currently see are reported.
|
||
|
||
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.
|
||
|
||
### Aiming
|
||
|
||
Both targeting modes now speak. No new keys — this is the game's own aiming.
|
||
|
||
**Ranged aiming** (throw, `t`) works like examine mode: the arrow keys move the
|
||
target cursor and each tile is read out as you land on it, followed by whether
|
||
you can actually throw there — *"can throw"*, *"blocked"*, *"out of range"* or
|
||
*"no line of sight"* — and how far away it is. `z` throws.
|
||
|
||
**Melee aiming** (hit `b`, interact `g`, push `y`) has no cursor: in this game a
|
||
single direction key both picks the tile and swings. So instead of reading a
|
||
selection that does not exist, the mod reads **all eight tiles around you the
|
||
moment you enter melee aiming** — clockwise from north — which are exactly the
|
||
tiles you can reach. Listen, then press the direction you want. `z` targets
|
||
yourself; `escape` backs out without attacking.
|
||
|
||
### 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 |
|
||
| `Shift+End` | The nearest exit, without changing your selection |
|
||
|
||
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.
|
||
|
||
### The inventory (`i`)
|
||
|
||
Moving the cursor reads the item under it, or `empty`, plus the slot number:
|
||
`a torch, slot 3`. `q` / `e` change tab, and the tab name is spoken when it
|
||
changes. An item you are currently wearing, wielding or have switched on is read
|
||
as `equipped`.
|
||
|
||
Pressing enter (or `z`) on an item opens a small menu of the things you can do
|
||
with *that* item right now — the list is different for every item, and changes
|
||
with its state. It is read as, for example:
|
||
|
||
```
|
||
a torch, actions, Switch on, 1 of 2
|
||
```
|
||
|
||
Up and Down move through the actions, enter performs the selected one, and any
|
||
other key closes the menu without doing anything. After the action runs you hear
|
||
the item again, so `Switch on` is followed by `a torch, equipped, slot 3` — that
|
||
is your confirmation it worked.
|
||
|
||
Some items have no actions at all (plain crafting reagents, for instance). For
|
||
those, enter does nothing whatsoever in the base game — not even a click — so the
|
||
mod says `a pinch of sulphur, no actions` rather than leaving you wondering
|
||
whether the key registered.
|
||
|
||
Common actions include Activate, Wield, Unwield, Wear, Take off, Switch on,
|
||
Switch off, Drink, Eat, Apply, Read, Open, and Prepare for throwing / Pack away.
|
||
**Equipping is done from here** — there is no separate equipment screen.
|
||
|
||
### Item details (`=`)
|
||
|
||
| Key | Action |
|
||
|---|---|
|
||
| `=` | Read the full details of the item under the cursor |
|
||
| `Shift+=` | The same, including the item's name |
|
||
|
||
Reading the cursor only tells you *which* item you are on. The details tell you
|
||
what it is: what kind of thing it is, its condition if it can wear out, its
|
||
description, and — the useful part for alchemy — **what it turns into**, listed
|
||
per refinement method:
|
||
|
||
```
|
||
It is a raw material. Some fresh green leaves from ivy. They contain weak
|
||
poison. Yields: x1 when ground in a mortar. x0.2 when distilled.
|
||
```
|
||
|
||
On the crafting, refining and trading screens the details also read the panel
|
||
beside the grid, which is where the game keeps the things you most need before
|
||
committing: a recipe's components and **whether you can afford them**, what a
|
||
refinement produces and how much of it you already have, and — in a shop — the
|
||
**price**, which appears nowhere else.
|
||
|
||
The same key works on the **research tree** at the writing desk, where the focus
|
||
line only tells you the node's name and whether you can afford it. The details
|
||
read the rest of what is on screen: what it costs in knowledge and how much you
|
||
are short by, which recipes it unlocks, and its description. `Shift+=` adds the
|
||
node's name and your full knowledge stock across all four types.
|
||
|
||
```
|
||
Costs 5 practical, 2 academic, 2 occult knowledge, short 2 occult. Unlocks
|
||
Sinister gauntlets, Verdant sandals. Clothing with supernatural attributes can
|
||
be created by infusing the weave with magical powers.
|
||
```
|
||
|
||
You do not have to press `=` every time. **Stop on an item for about half a
|
||
second and the details are read to you automatically**, which is what the game
|
||
does for a sighted player: its own detail popup only appears after you have
|
||
hovered a slot for a moment. Arrow through a tab quickly and you hear only the
|
||
names, exactly as a sighted player sees only the icons.
|
||
|
||
Details also go into the message history, so `[` and `]` walk back through a
|
||
long description a piece at a time and `\` re-reads a line.
|
||
|
||
### The travel map
|
||
|
||
The map of Rimesia you get when you leave an area is a picture: icons scattered
|
||
across a painting, with a panel beside them. Arrowing through the list now reads
|
||
what the picture says.
|
||
|
||
```
|
||
Blackreed Mire, swamp, 2 of 9, north-east, Travel will take 1 day and 6 hours,
|
||
Visited 3 days ago
|
||
```
|
||
|
||
- **what kind of place it is** — town, crypt, mine, shipwreck, witchwood — which
|
||
on screen is only the shape of the icon;
|
||
- **which way it lies** from where you are standing now;
|
||
- **how long the journey takes**, which is the number the decision actually turns
|
||
on and which was previously only in the side panel;
|
||
- **when you were last there**, which on a map full of similar-sounding names is
|
||
often the only way to tell them apart.
|
||
|
||
The area's description is longer, so it is not read every time you move the
|
||
cursor. `=` reads it, and stopping on an area for half a second reads it by
|
||
itself — the same way item details work.
|
||
|
||
### 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.
|