225 lines
8.5 KiB
Markdown
225 lines
8.5 KiB
Markdown
# 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.
|