Files
alchemist-accessibility/accessibility/docs/05-decisions.md
T

166 lines
8.5 KiB
Markdown
Raw Normal View History

# Decisions
Answers given, and their consequences. Supersedes the corresponding entries in `04-open-questions.md`.
---
## D1 (was Q1) — Development uses system LÖVE
Dev setup: `C:\Program Files\LOVE\love.exe` (11.5, matches `build.lua`'s `love = '11.5'`), running the source folder directly. `lovec.exe` in the same directory gives a console window and is what we'll use for debugging.
Tolk DLLs therefore go **next to `love.exe` in `C:\Program Files\LOVE\`** — that's the executable directory the OS loader searches. (Copies available at `D:\code\cafe`, `D:\code\dragonrpg-mapmaker`, `D:\code\DragonWarriorAccess`.) Needs an elevated copy, one time.
Distribution deferred until the mod works. Noted for later: whatever we ship must place the DLLs beside the executable, so a plain `.love` file can never be the whole delivery.
---
## D2 (was Q2) — Tracker hierarchy confirmed
Three levels, as proposed:
| Level | Key | Example |
|---|---|---|
| Category | `Ctrl+PgUp` / `Ctrl+PgDn` | "Creatures" |
| Item (kind) | `PgUp` / `PgDn` | "rat" |
| Instance | `Shift+PgUp` / `Shift+PgDn` | that specific rat at (12,7) |
| — | `Home` | announce distance + direction of current instance |
Categories accepted: **Creatures / Items / Exits / Interactables / Terrain features / Alchemy**, conditional on covering everything. See D4 for the coverage check.
---
## D3 (was Q7 + Q10) — Small patches now, upvalue probing kept as a fallback layer
Decision: use **small explicit patches** in game state files where they genuinely simplify things — but keep the option of switching to zero-edit operation, in case the game ships on Steam and editing files stops being practical for end users.
### Why Steam changes things
A Steam LÖVE release is normally a **fused executable** (the `.love` archive appended to `love.exe`). Editing game source then means unpack → patch → repack → replace the exe, which Steam's file integrity check may revert on next update. That's a bad end-user story, and it's exactly the scenario the upvalue technique was designed for.
### The design that keeps both doors open
Make the **state descriptor** the single abstraction, and let each descriptor declare *how* it gets focus — with two interchangeable backends:
```lua
-- accessibility/states/main_menu.lua
return {
title = "Main menu",
-- Backend A (preferred): the patched game file calls access.announce(...) directly.
-- Backend B (fallback): read the same values as upvalues, no game edit needed.
probe = { fn = "keypressed", focus = "selectedOption", list = "optionsList" },
render = function(entry, i, n)
return ("%s, %d of %d%s"):format(entry.title, i, n, entry.isEnabled and "" or ", unavailable")
end,
}
```
`render` — the part that carries all the per-screen knowledge — is **identical for both backends**. Switching modes is a global config flag, not a rewrite:
- `mode = "patched"` → the driver trusts `access.announce(entry, i, n)` calls injected into game files.
- `mode = "probe"` → the driver wraps `states.keypressed`, reads upvalues via `debug.getupvalue`, and calls the same `render`.
This costs us little now (we write `render` either way, plus a ~40-line probe driver) and means a Steam release is a config change plus fixing whichever probes drifted — not a redesign.
### Consequences
- Patches stay **mechanical and uniform**: a single `access.announce(...)` line per focus-change site, nothing else. Easy to read in a diff, easy to re-apply.
- The git baseline (`vanilla-0.4.2` tag) becomes load-bearing — it's how we rebase patches onto a new version. Do this in Phase 0.
- The **startup self-test** is still required, and now checks both backends: which patch sites are present, and which probes still resolve. This is the version-bump canary either way.
- Upstreaming to Spellweaver stays viable, since the patches are small and additive. Worth revisiting once there's something working to show.
---
## D4 — Category coverage check (the condition on D2)
I classified all tile templates statically to confirm the six categories don't leave gaps.
**Scale is much smaller than the line counts suggested:**
| Template set | Count |
|---|---|
| Tile templates (`modules/tile/templates.lua`, 6745 lines) | **231** |
| Actor templates (`models/actor/templates.lua`) | **40** |
| Item templates (`modules/item/templates.lua`) | **344** |
231 tile templates is very tractable — the line count is inflated by inline behaviour functions, not by breadth.
**Parent inheritance is already flattened for us.** `modules/tile/preprocess.lua` copies every missing field down from `template.parent` at load time. Since `main.lua` runs `preprocessTileTemplates()` during `love.load`, by the time our code runs, `require("modules.tile.templates")` gives fully-resolved templates — we read `template.name` / `template.description` / flags directly with no chain-walking.
*(Minor upstream quirk noticed: the flatten is a single pass over `pairs()`, so a grandchild template may inherit from a not-yet-flattened parent depending on hash order. Pre-existing game behaviour, not ours to fix — but if a template ever looks oddly under-specified, this is why.)*
**Only 6 templates are genuinely un-nameable** (no name, no description, no parent) — all invisible mechanics that *should* be excluded from tracking:
`blank`, `throw_blocker`, `movement_blocker`, `fire_detector`, `wall_features_position_dummy`, `ladder_down_hidden`.
Every other template resolves to a name/description and lands in a category. **The six categories cover the space.**
**The classifier flags exist and are clean:**
| Signal | Count | Maps to |
|---|---|---|
| `tryInteractFunction` | 139 | Interactables / Exits |
| `loot` | 68 | Items (+ Alchemy when the loot is an ingredient) |
| `blocksMovement` | 133 | Terrain (walls) |
| `blocksVision` | 127 | Terrain |
| `isThreatening` | 15 | Terrain features → hazards |
| `getPassability` | 11 | Terrain → water/special |
| `isLightSource` | 50 | (not a category; useful description detail) |
| `isPushable` / `isBreakableByHand` | 17 / 17 | Interactables |
| `isWallFeature` | 22 | Terrain |
2026-08-01 21:34:13 +02:00
`modules/tile.lua:234` defines the runtime flag as:
```lua
self.tryInteract = (self.loot or self.tryInteractFunction) and function(self,actor) ... end
```
2026-08-01 21:34:13 +02:00
> ### ⚠ Correction (found during Phase 3 implementation)
>
> I originally claimed here that **`tile.tryInteract` alone cleanly separates
> "the player can do something here" from scenery.** That is **wrong**, and the
> tracker was visibly worse for it.
>
> Plenty of templates carry a *flavour-only* `tryInteractFunction`. `wall` is the
> worst offender:
>
> ```lua
> wall = {
> name = "a stone wall",
> blocksMovement = true, blocksVision = true,
> tryInteractFunction = function(self, actor)
> console.print("You touch the wall. It's cold and rough.")
> return 1
> end,
> }
> ```
>
> So flag-first classification promoted **every wall segment** into
> Interactables — 13 of them in the starting house, burying the chest, cauldron
> and alchemy table under identical noise.
>
> `tryInteract` means *"something happens if you press interact"*, **not**
> *"this is worth walking to"*.
>
> **Fix:** exclude structural tiles — `blocksMovement and blocksVision` — before
> the `tryInteract` test. That is the solid shell (walls, rock); containers and
> furniture block movement but not sight, so they survive. Walls are already
> served by the wall tones and the surroundings scan.
>
> Result in the starting house: 13 kinds / 32 instances → **12 kinds / 19
> instances**, all of them things you would actually want to find.
>
> The flag-first *approach* still holds — it just needs this one structural
> filter ahead of it. The lesson is that flags encode what the game engine does,
> not what the player cares about.
**Residual hand-maintained list** is therefore small: the 6 exclusions above, the existing `exit` tag set (`way_out`, `building_door`, `horse`, `ladder_up`, `ladder_down`), and an ingredient-node list to split **Alchemy** out of **Items**. Dozens of entries, not hundreds — and new content in future versions falls through to a sensible default category rather than vanishing.
---
## Still open
From `04-open-questions.md`: **Q3** (verbosity: relay vs. augment), **Q4** (console auto-speak policy), **Q5** (wall tone triggers), **Q6** (keybinding integration), **Q8** (full keyboard audit as explicit deliverable?), **Q9** (which screen reader; do you want me running the game during dev).
None of these block starting Phase 0. Q5 and Q9 become relevant at Phase 2; Q4 at Phase 1.