# 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 bash accessibility/tests/run-testbed.sh # ~4 min — save-safe, see below 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`). The testbed scenario assumes a fresh save and will not navigate correctly without one — it drives a new game from the main menu through the prologue, which has already happened in any real save. **Do not clear that directory by hand.** It holds the player's actual playthrough, and `rm -rf` on it — which is what these docs used to say — destroys it. Use `accessibility/tests/run-testbed.sh`, which: 1. **Moves** the save to `Alchemist.a11y-backup.` (never deletes it), 2. runs the testbed against the empty directory LÖVE then creates, 3. discards the test run's directory and moves the real save back, from an `EXIT` trap so this happens on failure, on a crashed run and on Ctrl-C too. If the restore itself ever fails it says so loudly and leaves the backup on disk rather than pressing on. Why a wrapper and not something inside the mod: `love.load` calls `config.initialize`, `keybindings.initialize` and `user.initialize` long before `main.lua` reaches `require("accessibility").initialize()`. By the time any of our Lua runs, the save directory has already been read — the swap has to happen around the process. Override the interpreter path with `LOVE=/path/to/lovec.exe` if yours differs. 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.