viewwatch: announce objects entering and leaving view while walking

Walking blind, the hard part is not "what is next to me" -- that is `/` --
but "what changed when I moved". Rounding a corner reveals a room, stepping
back hides it, a rat walks into sight behind you, and none of it is spoken.

Shift+/ cycles three modes:

  settled (default)  changes accumulate while moving, spoken once you have
                     stood still for 0.4s
  always             every step reports its own change
  off

`settled` diffs against the state at the last ANNOUNCEMENT, not the last
step, so a long walk gives one combined summary that is a genuine net
change: something that came into view mid-walk and left again before you
stopped is not mentioned, and something visible throughout is mentioned
once. Summing per-step diffs would report both. A single step is the
degenerate case -- you are already standing still.

Arrivals and departures are deliberately asymmetric. Membership is current
FoV only (never rememberedFoV), but furniture drops out of FoV constantly
indoors as walls come between you and it, and "the bed is out of view"
every other step is noise. Arrivals are reported for every watched
category; departures only for creatures.

Terrain is unwatched by default -- water arrives dozens of tiles at a time.
Step detection polls playerActor.x/y as walltones does, so pathing, portals
and pushes are covered with no game function wrapped; the scan is a 14-tile
box and runs only on a step or a due settle, never per frame.

snapshot() and diffText() are pure and exposed so the testbed can assert
the wording and the FoV invariant directly instead of capturing speech.
34/34 testbed checks, 0 failures. Game footprint unchanged: main.lua | 1 +.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Accessibility Mod
2026-08-02 02:03:32 +02:00
co-authored by Claude Opus 5
parent 86c71fc001
commit 973e62c8fe
5 changed files with 546 additions and 5 deletions
+62 -5
View File
@@ -2,12 +2,12 @@
**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–5 COMPLETE. Handed off for
Last updated: 2026-08-02 · 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 —
verified by the testbed, **29/29 checks**.
verified by the testbed, **34/34 checks**.
### Run the testbed (do this after any change)
```
@@ -134,6 +134,7 @@ and by Talon at the keyboard (wall tones).
| `\` | Repeat current line |
| `Shift+\` | Copy current line to clipboard |
| `/` | Scan all 8 neighbours, clockwise from north (terse) |
| `Shift+/` | View watch: cycle off / announce when you stop / announce every step |
| `;` | Where am I — level, in-game time, coordinates |
| `Ctrl`+numpad 1–9 | Probe that direction without moving (`Ctrl+kp5` = standing on) |
| `Ctrl+PageUp` / `Ctrl+PageDown` | Tracker: previous / next **category** |
@@ -502,9 +503,65 @@ page with no title in front of it.
Verified live: `Journal. Quests, A simple commission, 1 of 1, Someone's banging
at my door. Whoever it is, this person is very persistent and doesn't know about
doorbells.` **29/29 testbed checks**, including a new one that asserts the spoken
doorbells.` **34/34 testbed checks**, including a new one that asserts the spoken
text actually contains the page body rather than merely being non-nil.
### Done — view watch: what comes in and out of view while walking (Talon request)
**`accessibility/viewwatch.lua`**, toggled with **`Shift+/`** — a natural pair
with `/` (scan around me now): `/` is "what is here", `Shift+/` sets how much of
that happens by itself.
Three modes, cycled by the key:
| Mode | Behaviour |
|---|---|
| `settled` (**default**) | Changes accumulate while moving; spoken once the player has stood still for `settleDelay` (0.4 s). |
| `always` | Every step reports its own change. |
| `off` | — |
**Why `settled` is the default and why it diffs against the last announcement,
not the last step.** Talon's question was "announce as they happen, or combine".
Combining is right for a long walk, but only if the baseline is the state at the
last *announcement* — then the summary is a genuine **net** change: something
that came into view mid-walk and left again before you stopped is correctly not
mentioned, and something that stayed in view throughout is mentioned once. A
per-step diff summed up would say both, which is worse than either mode. A
single step is the degenerate case: you are already standing still, so it
reports 0.4 s after the step lands.
**Arrivals and departures are deliberately asymmetric.** Membership is **current
`FoV` only** (never `rememberedFoV` — the question is what you can see from
where you stand). But furniture drops out of FoV constantly indoors as walls
come between you and it, and "the bed is out of view" every other step is pure
noise. So arrivals are reported for every watched category; departures only for
`config.announceGone`, which is **creatures only** by default.
Other decisions:
- Terrain is **not** watched by default (`config.watched.terrain = false`) —
water arrives dozens of tiles at a time and would drown everything else.
Classification is otherwise the tracker's (`categories.classifyTile`), so the
wall-exclusion correction to D4 applies here too.
- Scan is a **box of `radius` 14 tiles** around the player, not the whole grid.
Sight range is well inside that, and this runs on every step.
- Step detection **polls `playerActor.x/y`**, same as `walltones` — covers
walking, pathing, portals and pushes with no game function wrapped. The scan
runs only when the position changed or a settle is due, never per frame
(invariant 5).
- Entering a new level **re-baselines in silence** rather than reading the whole
first room; so does any mode change.
- Speech is **queued, not interrupting** — this is background awareness, so
anything the player asked for outranks it.
- Announces only in `ingame` / `ingame_observe`.
**Testability seam:** `viewwatch.snapshot()` and `viewwatch.diffText(prev, cur,
px, py)` are pure and exposed, so the testbed asserts the wording and the FoV
invariant directly instead of trying to capture speech. **34/34 testbed checks**
(5 new). Verified live during the run — the walk to the bed produced
`In view: a chest, 1 tile south-west. a torch on the wall, 3, nearest 2 tiles
south-west. …`.
### Known gaps (next up)
- **Phase 5**: player status key (HP, statuses, current action), popup
announcements (`popupStack.add`), damage alerts
@@ -569,7 +626,7 @@ utterance — speech is verifiable from the log, not only by ear. `!` after
## HANDOFF — read this if you are resuming
### State of play
Phases 0–5 complete. **23 state descriptors, 5 patches, 29/29 testbed checks**,
Phases 0–5 complete. **23 state descriptors, 5 patches, 34/34 testbed checks**,
history unit tests passing, **one line changed in the game** (`main.lua`).
Everything in the original brief is implemented: console speech with
@@ -580,7 +637,7 @@ exploration, wall tones, and keyboard+speech access to every screen.
```
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, 29 checks
"C:/Program Files/LOVE/lovec.exe" . --a11y-test # ~4 min, 34 checks
git diff --stat vanilla-0.4.2 -- . ':!accessibility' ':!.gitignore' ':!CLAUDE.md' # must stay: main.lua | 1 +
```