Merges the Godot port into the reverse-engineering repository, preserving both
histories -- 1019 commits of corpus plus the port's 31, brought in by subtree
merge and then moved into place so git can follow each file across the rename.
The reason is not tidiness. The two-repo split forced the exporter to depend on
the decoders by pinned revision, and that created a whole class of failure that
now disappears: a sha reachable only from a topic branch, orphaned by a
squash-merge, breaking a fresh checkout silently at build time. It also forced a
live read-only mount of one agent's working tree into another's container, which
is why a contract file could move mid-iteration. With a path dependency, a
decoder change and the exporter change it requires land in the same commit or
not at all.
Canary stays separate: it is a fork tracking upstream.
New structure for the long term:
docs/game/ how the game is NAVIGATED -- menus, modals, prompts, alerts,
and in-game flight. Written so nobody rediscovers it. Mostly
open questions on purpose; the in-game tutorials are the
resource for the flight half.
docs/port/MODDING.md
modding as a constraint on the exporter TODAY, not a later
feature: one logical asset in one file (the disc splits nearly
everything, and resolving that is the exporter's job), names a
person recognises, PNG/OGG/OGV/JSON only, base-and-overrides so
re-exporting is always safe, provenance in every file.
data/base + data/mods
generated tree and drop-in overrides, both gitignored
exchange/ transient inter-agent files, deliberately outside history
docs/agents/ the team protocol
Both the README and the navigation doc lead with the correction that cost the
most: the oracle is the real game under Xenia Canary. Reborn's renderer is a
hypothesis under test, it has been wrong, and treating it as ground truth
propagated into three documents and both agents before a human caught it.
Scripted modding stays possible without being built: no screen name is hardcoded
in GDScript and there is no native code in port/, which is what Godot Mod Loader
needs to be able to substitute behaviour later.
87 lines
3.8 KiB
Markdown
87 lines
3.8 KiB
Markdown
# Modding is a requirement, not a later feature
|
||
|
||
The port has two goals, and the second one constrains the first: **the exported
|
||
asset tree is a product**, not a build artefact. Someone who has never read this
|
||
repository should be able to open the tree, understand what they are looking at,
|
||
change something, and see it in the game.
|
||
|
||
That is a design constraint on the **exporter**, today — not a milestone to add
|
||
later. Custom *behaviour* (scripting) is a later milestone, but nothing built now
|
||
may make it harder.
|
||
|
||
## The five rules
|
||
|
||
**1. One logical asset, one file. Never split.**
|
||
|
||
A sprite is one PNG. A track is one OGG. A screen is one JSON. This is a real
|
||
constraint and not a platitude, because **the disc does the opposite everywhere**:
|
||
a `.pak` entry spans segment files, a bank holds several sub-waves, and a
|
||
cutscene voice is a byte region of a continuous stream chunked across entries
|
||
whose boundaries do not match the cues. All of that is the *exporter's* problem
|
||
to resolve. If a modder has to reassemble anything, the export is unfinished.
|
||
|
||
**2. Names a person recognises.**
|
||
|
||
`screens/title/main_menu.json`, not `0x90822a39.json`. Where the disc's own name
|
||
was never recovered — the six `*2D` archives, `GP_READY_ROOM` — emit a stable
|
||
synthetic name **and say in the file that the real one is unknown**, so a modder
|
||
can tell a recovered name from an invented one.
|
||
|
||
**3. Modern, editable formats only.**
|
||
|
||
| kind | format | why |
|
||
|---|---|---|
|
||
| data, layout, config | **JSON** | Godot parses it natively (`JSON.parse_string`); every tool speaks it |
|
||
| images | **PNG** | RGBA8, lossless, opens anywhere |
|
||
| audio | **Ogg Vorbis** | Godot-native, no licence trap |
|
||
| video | **Ogg Theora** | the only format Godot 4 plays natively |
|
||
| text | **UTF-8** | never UTF-16BE, whatever the disc did |
|
||
|
||
Not XML: Godot's `XMLParser` is a SAX-style API needing a hand-written binding
|
||
per schema, where JSON is one call. Not a custom binary container, ever — that
|
||
would rebuild the exact wall this port exists to remove.
|
||
|
||
**4. Base and overrides, never one merged pile.**
|
||
|
||
```
|
||
data/
|
||
base/ generated from your disc by the exporter. Gitignored. Rewritten
|
||
wholesale -- never hand-edit it, your changes will vanish.
|
||
mods/ drop-in overrides. Yours. The exporter never touches this.
|
||
```
|
||
|
||
A mod replaces a file by shadowing its path. A modder edits nothing under
|
||
`base/`, so re-exporting is always safe, and "did I break it?" is answered by
|
||
disabling a mod rather than by re-extracting the disc.
|
||
|
||
**5. Provenance in every generated file.**
|
||
|
||
Source archive, entry index, exporter version. It is what lets someone check a
|
||
file against the disc instead of trusting it — and what stops the export drifting
|
||
into an unverifiable fork of the original.
|
||
|
||
## Not blocking scripted mods later
|
||
|
||
Behaviour modding is a later milestone. Two decisions now keep the door open:
|
||
|
||
* **The loader is data-driven and screen-agnostic.** No screen name is hardcoded
|
||
in GDScript. A screen is *whatever the JSON describes*, so a mod that adds a
|
||
new screen needs no engine change.
|
||
* **No GDExtension, no native code in `port/`.** [Godot Mod Loader][gml] — the
|
||
established option, Godot 4.1–4.3, used by Brotato and Dome Keeper — works by
|
||
substituting **GDScript** at load time. A port whose logic lives in GDScript
|
||
stays moddable by it; one that hides logic in native code does not.
|
||
|
||
Adopting a mod loader is a decision for that milestone. Making it *possible* is a
|
||
constraint on this one.
|
||
|
||
[gml]: https://github.com/GodotModding/godot-mod-loader
|
||
|
||
## What this rules out, explicitly
|
||
|
||
* Atlases or packed archives of sprites — one file per sprite.
|
||
* Any format needing our code to read it.
|
||
* Hash-named files.
|
||
* Hand-edited files under `data/base/`.
|
||
* Splitting one playable thing across files to mirror how the disc stored it.
|