MODDING.md calls base-and-overrides "a design constraint on the exporter today, not a milestone to add later". Nothing read `data/mods/` at all -- the directory has existed since the monorepo merge with a .gitkeep and no code path anywhere. Eight milestones shipped past it. ExportTree.resolve() now shadows by path, and every read goes through it: screens, sprites, cues, the music bed, movies. MenuAudio was reading tree.root directly and would otherwise have made audio the one asset kind a mod could not touch, for no reason a modder could have guessed. No manifest, no registration step -- the path IS the registration, which is the whole of the rule. One tree, not a stack: layering needs a load order and nobody has asked for one, so data/mods/README.md says that rather than inventing it. Every shadowed file is printed as it is read. The first version summarised in _ready, before any asset had been read, so it always said "nothing shadowed yet" -- a report structurally incapable of reporting anything, which is worse than none because it looks like an answer. data/mods/ was NOT gitignored, and that is a hole in a hard rule: a mod is usually an edited game asset, and this was the one directory a user is invited to put modified sprites in and git would have taken them. Now excluded except the README. Gate: a synthetic 203x43 magenta PNG (nothing disc-derived) at data/mods/sprites/title/main_menu/ptbtn01.png changes 8501 pixels in a bounding box of exactly 203x43 at the button's position, and `check` still passes. RAISED, NOT RESOLVED: MODDING.md says the tree is data/base/, PORT-MISSION.md §3 and the exporter and .gitignore say export/. Both are mission files and only the human changes a mission. REFUTATION on Q3's paint-order key: 2 of 16 screens did not match a stable sort by layer key -- but that was my test. pgloading_eff00.prm carries NO layer key (layer_source "none"): a primitive with no sprite header and no implied-name fallback. I sorted keyless first; the decoders put it last, which is right, since it is the full-screen black quad and HANDOFF's own sentence is that the fade quad paints last. Completing the rule to "keyless last" gives 16 of 16. SURVIVES. Recorded because the published claim does not say where a keyless element goes and there is one in the archive. Separately the tie-break's reach looks understated: 105 elements share a layer key across 12 of 16 screens, where HANDOFF characterises the cost as "one element's blend on one screen". WITHDRAWN, and it was mine: I filed "the runtime mix has no headroom" in red twice, off a peak reading. Measured properly it is 43 samples at full scale in 5.9 s and 24 in 98.5 s, longest run 0.25 ms -- the disc's own confirm cue on a transient, possibly only in the 16-bit save. Nothing changed, deliberately: attenuating would be an unmeasured level decision of the kind I refused for the loop point. A peak reading is not a clipping measurement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC
58 lines
2.1 KiB
Markdown
58 lines
2.1 KiB
Markdown
# Your mods go here
|
|
|
|
A mod **replaces a file by shadowing its path**. There is no manifest, no
|
|
registration and no load order: if a file exists here at the same relative path
|
|
it has in the export tree, the game reads yours instead.
|
|
|
|
```
|
|
export/sprites/title/main_menu/ptbtn01.png <- what the exporter wrote
|
|
data/mods/sprites/title/main_menu/ptbtn01.png <- what the game will use
|
|
```
|
|
|
|
That works for **every** asset kind the port reads — a screen's JSON, a sprite
|
|
PNG, a sound cue, the music bed, a movie — because every read goes through one
|
|
resolver (`port/scripts/export_tree.gd`, `ExportTree.resolve`).
|
|
|
|
Nothing under `export/` is ever touched, so **re-exporting from your disc is
|
|
always safe**, and *"did I break it?"* is answered by moving your file out of
|
|
this directory.
|
|
|
|
Point the game somewhere else with `SYLPHEED_MODS=/path/to/tree`.
|
|
|
|
## The game tells you what you changed
|
|
|
|
Every file a mod replaces is printed the first time it is read:
|
|
|
|
```
|
|
mod: sprites/title/main_menu/ptbtn01.png <- /work/data/mods/sprites/title/main_menu/ptbtn01.png
|
|
```
|
|
|
|
A modded run that looked identical to an unmodded one in the log would leave you
|
|
with exactly one debugging tool — delete the mod and try again.
|
|
|
|
## Try it in ten seconds
|
|
|
|
Replace the `NEW GAME` label with a magenta block. The size is the original's,
|
|
`203x43`, and nothing here is derived from the disc:
|
|
|
|
```bash
|
|
mkdir -p data/mods/sprites/title/main_menu
|
|
ffmpeg -f lavfi -i "color=c=0xff00c8:s=203x43" -frames:v 1 -pix_fmt rgba \
|
|
data/mods/sprites/title/main_menu/ptbtn01.png
|
|
godot --path port -- --menu
|
|
```
|
|
|
|
Delete the file to put it back.
|
|
|
|
## Nothing in here is committed
|
|
|
|
`.gitignore` excludes everything in this directory except this README. That is
|
|
deliberate: a mod is usually an *edited game asset*, and this repository never
|
|
holds game assets — not in `export/`, and not here either.
|
|
|
|
## One tree, not a stack
|
|
|
|
Several mods layering over each other would need a load order, and a load order
|
|
needs a rule nobody has asked for yet. Today there is one override tree. If you
|
|
want more, say so rather than assuming the port has an answer.
|