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
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:
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.