docs: how to set up, run and publish this tree

The setup is unusual enough to need its own page, and the facts were
scattered across scratch.md, CLAUDE.md and publish.sh: the mod is developed
inside a copy of a commercial game, so the dev tree deliberately has no
remote and publishing means rebuilding a filtered repo and force-pushing
that instead.

docs/00-dev-setup.md covers the two-tree arrangement and which one you are
in, prerequisites and why the game must be an extracted folder rather than
a .love (ffi.load goes through the OS loader), building a dev tree from
scratch including the vanilla-0.4.2 baseline tag and the one main.lua line,
the everyday commands, the save location, reading the [speech] console and
the startup self-test, and the publish flow with its leak check.

Linked from CLAUDE.md and the docs index. No code change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Accessibility Mod
2026-08-02 02:16:21 +02:00
co-authored by Claude Opus 5
parent 973e62c8fe
commit 7ce429c2fb
4 changed files with 234 additions and 1 deletions
+224
View File
@@ -0,0 +1,224 @@
# Development setup
How to get from nothing to a tree you can run, test and publish from. The
arrangement is a little unusual, because the mod lives inside a copy of a
commercial game that must never be published.
**If you are already set up**, the only things you need daily are
[Everyday commands](#everyday-commands) and [Publishing](#publishing).
---
## The two trees
This repository exists in two forms, and the same `CLAUDE.md` ships in both, so
check which one you are in:
| | **Development tree** | **Standalone mod repo** |
|---|---|---|
| Contents | The whole extracted game (1838 files) **plus** `accessibility/` | `accessibility/` and the docs only |
| Beside `accessibility/` | `main.lua`, `states/`, `models/`, … | nothing |
| Can you run the game? | Yes | **No** |
| Can you run the testbed? | Yes | **No** |
| Remote | **None, deliberately** — see [Publishing](#publishing) | `code.iamtalon.me/talon/alchemist-accessibility` |
| Where it comes from | You build it, below | Generated from the dev tree by `publish.sh` |
The standalone repo is **derived, never developed in directly**. Every publish
rewrites its history from scratch, so any commit made there is destroyed on the
next publish. Always work in the development tree.
Everything outside `accessibility/` in the dev tree is the vanilla game: read-only
reference material. You read it to find out how the game works; you patch it only
where the docs sanction, which currently means exactly one line.
---
## What you need
| | Why | Notes |
|---|---|---|
| **The game, 0.4.2, as an extracted source folder** | The thing being modded | A directory containing `main.lua` and `conf.lua`. **Not** a packed `.love`, **not** the Steam build — see below. |
| **LÖVE 11.5** | Runs the game | `conf.lua` pins `t.version = "11.5"`. Installed here at `C:\Program Files\LOVE\`. Use **`lovec.exe`**, not `love.exe`. |
| **Standalone Lua 5.4** | Unit tests and syntax checks without launching the game | `lua` and `luac` on PATH. |
| **A screen reader, or nothing** | To hear the output | Tolk drives NVDA / JAWS / System Access and falls back to SAPI 5. Dev here uses **NVDA**. Speech is also mirrored to the console, so you can verify from a log with no reader running at all. |
| **`uv` (for `uvx`)** | `git-filter-repo` at publish time | Only needed when publishing. |
Optional: `luacheck`. `.luacheckrc` is configured (`std = "min+alch"` with an
explicit globals list) but the linter is not currently installed — `luac -p` is
what actually gets run.
### Why the game must be an extracted folder
`ffi.load("Tolk")` goes through the **OS** loader, not LÖVE's virtual filesystem,
so the TTS DLLs must exist as real files on disk. They can never live inside a
`.love` archive. This is also why the mod loads them by absolute path via
`love.filesystem.getSource()`. See `accessibility/vendor/love2talk/VENDORED.md`.
---
## Building the development tree from scratch
**1. Extract the game** into a working directory. This tree lives at
`D:\code\Sulphur Memories Alchemist 0.4.2 sanctified`.
**2. Commit the pristine game and tag it**, *before* adding any mod code, so the
baseline is genuinely clean:
```
git init
git add .
git commit -m "Vanilla Sulphur Memories: Alchemist 0.4.2"
git tag vanilla-0.4.2
```
The tag holds 1838 files and **no `accessibility/` directory**. That is the whole
point of it: it is what makes the footprint check below trustworthy.
**3. Add `accessibility/`** — copy it in, or clone the standalone repo and move
its `accessibility/` folder into place.
**4. Add the one line to `main.lua`.** In `love.load()`, immediately after
`console.prepare()`:
```lua
require("accessibility").initialize() --ACCESSIBILITY MOD: sole entry point
```
`accessibility/publish/install.patch` applies exactly this. The placement is not
arbitrary: 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()`.
**5. Check the TTS DLLs survived the copy.** `accessibility/lib/` must contain
`Tolk.dll`, `nvdaControllerClient64.dll` and `SAAPI64.dll`.
⚠ `.gitignore` has a blanket `*.dll` with a deliberate `!accessibility/lib/*.dll`
un-ignore. If you ever restructure that folder, the DLLs will silently stop being
tracked and the mod will be mute for anyone who clones it.
**6. Verify.** Run the [everyday commands](#everyday-commands). A working setup
prints, at startup:
```
[accessibility] TTS: available
[accessibility] speech channel: yes
[speech]! Main menu
```
`speech channel: NO` means the DLLs loaded but nothing is listening — start a
screen reader, or expect SAPI.
---
## Everyday commands
Run all of these after any change. The testbed in particular: it has caught
essentially every bug in this project, and a change that looks obviously correct
is exactly the kind that fails it.
```
cd "D:/code/Sulphur Memories Alchemist 0.4.2 sanctified"
lua accessibility/tests/test_history.lua # pure Lua, instant
luac -p accessibility/*.lua # syntax only, no LOVE needed
rm -rf ~/AppData/Roaming/LOVE/Alchemist # clean save
"C:/Program Files/LOVE/lovec.exe" . --a11y-test # ~4 min
git diff --stat vanilla-0.4.2 -- . ':!accessibility' ':!.gitignore' ':!CLAUDE.md'
```
The last one **must** print `main.lua | 1 +`. That is the invariant that keeps the
mod cheap to lift onto a new game version.
**What matters in the testbed run is `0 failures`, not the check count**, which
varies between runs because several blocks are conditional.
To just play it:
```
"C:/Program Files/LOVE/lovec.exe" .
```
### Save data
LÖVE writes to `~/AppData/Roaming/LOVE/Alchemist` (`t.identity = "Alchemist"` in
`conf.lua`). Deleting it is how you get a clean prologue; the testbed scenario
assumes a fresh save and will not navigate correctly without one.
Runtime files the game also drops next to the source during dev (`config.json`,
`user.json`, `keybindings.json`, savegames) are already in `.gitignore`.
### Reading the console
`lovec.exe` gives a console window, and every utterance is mirrored there as a
`[speech]` line — so speech is verifiable from a log rather than only by ear:
| Prefix | Meaning |
|---|---|
| `[speech]!` | interrupted whatever was being said |
| `[speech]*` | urgent, spoken immediately (bypasses the frame queue) |
| `[speech] ` | queued normally |
### If something mysteriously does not speak
Read the **startup self-test**. It reports every patch and every descriptor probe.
Most menu narration reads the game's file-local variables via `debug.getupvalue`,
so a renamed local in a future game version shows up there as
`PROBE BROKEN: no upvalue 'x'` rather than as silence three screens in.
`ok (unset until opened)` is normal.
---
## Publishing
**The development tree has no git remote, and must not be given one.** It holds
all 1838 files of a commercial title. Pushing it anywhere would distribute the
game.
Publishing therefore means: rebuild a separate repository that contains only the
mod, and force-push *that*.
```
sh accessibility/publish/publish.sh
```
This clones the dev tree to `../alchemist-accessibility-publish`, runs
`git-filter-repo` to keep only `accessibility/` and `CLAUDE.md`, lifts
`accessibility/publish/README.md` and `install.patch` up to the root (so a visitor
to the mod repo finds them where they expect), and drops the publish machinery
itself.
Then **the check that matters**: it lists every path in the rewritten history and
aborts if anything that is not the mod or its docs survived. A clean run says:
```
clean: history contains only the mod and its docs
```
If it says `ABORT: non-mod files present in history`, **do not push** — a game
file leaked through the filter.
The script prints the push commands and stops rather than pushing for you:
```
cd ../alchemist-accessibility-publish
git remote add origin https://code.iamtalon.me/talon/alchemist-accessibility.git
git push --force -u origin master
```
`--force` is not a mistake and not a recovery step. `git-filter-repo` rewrites the
history from scratch every time, so the published commits are new objects on every
publish. The mod repo is derived output; force-pushing it is the normal path.
⚠ Paste those as **separate lines**. If a shell wraps the `remote add` line, the
URL ends up on its own and gets run as a command.
---
## Rebasing onto a new game version
Commit the new pristine tree on top of the `vanilla-0.4.2` baseline, then replay
the mod commits. The only game-file conflict possible is the single `main.lua`
line. Then run the startup self-test and read it: it tells you immediately which
patches and probes the new version broke.