78879dce06100302aa8959c90039113b971eafd6
11 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
af1d69ed4b |
port: the PRESS (A) plate pulses -- authored per element, because 82 of 212 share its shape
The human listed pulsation as first-class and the port drew nothing: the plate's focus record `ptbtn00f` was never reached, because press_start has no `buttons` and nothing is focused. That it LOOPS is measured -- the corpus timed the period four times (2.12 / 2.19 / 2.34 / 2.31 s) and you cannot measure a period unless the thing repeats. THE RULE I WAS GOING TO WRITE DIED IN THE CENSUS. The spinning ring is a rule in the renderer because it has a disc-wide check: 16 of 212 elements match its shape and all 16 are focus rings. The analogous shape for a pulse -- keyframes varying only in alpha, first alpha equal to last -- matches 82 OF 212, including ptcopyright, palogo_sqex, ptmsg and every _eff fade. A renderer rule on it would make the copyright notice pulse. Narrowed to focus records it matches exactly one distinct element, and a rule justified by n=1 is a special case wearing a rule's clothes. So it is a LOOKUP in authored/timing.json keyed <screen>/<element>, with the census recorded beside it so nobody widens it later. The period is 129 units -- the element's own group under the port's existing model: last timed keyframe t=105 plus the authored exit_ramp_units of 24. No new constant. 2.150 s at 60 units/s, 2.295 s at the ~28.1 fps the emulator presents, against measurements of 2.12-2.34. IT IS A CHOICE AND THE ALTERNATIVE IS STATED: restarting at the group's first keyframe (t=6) instead of 0 gives 123 units = 2.050 / 2.189 s, also inside the measured spread. Nothing separates them. t=0 is taken because it is where every other group starts -- consistency, not evidence. Verified the way the ring was, by bit-identity one period apart. 20 periods is 43.00 s = exactly 172 film frames: frames N and N+172 differ by 0-1/255, while the control a quarter-second off (43.25 s) differs by 58.7/255. On the held boot title the glow-box mean swings 26.0 <-> 37.7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF |
||
|
|
8fba7944d4 |
port: the game decodes all three voice streams at once, and two baseline rows were comparing blank frames
TWO FINDINGS, one mine and one handed to me, and the second retires a premise I built on twice. THE P1 BASELINE HAD ROWS THAT PROVED NOTHING. `build_12` and `build_15` render pure black in BOTH renderers -- mean 0, max 0 -- so the difference is zero and `verify-screen` scored them `max 0 over3 0 OK`, the strongest verdict it has. Two of sixteen rows were comparing nothing against nothing. Worse than a missing test, because a missing test is visible in the count. Cause isolated by a control, not by reading: `build_00`/`build_01` are the same loading screen minus three elements and render fine (mean 1.913, max 214.5). The dressed variants add `pgloading_eff00`, a 1280x720 primitive resting OPAQUE BLACK at t=38 inside its own opening black hold, with no layer key so paint order puts it last. The rule I was about to write -- "rest.t before the last timed keyframe is the pathology" -- was killed by running the census first: 152 of 212 elements in this export have rest.t earlier than their last timed keyframe. It is the norm. What is actually unusual is the CONTENT, and its reach is one: `pgloading_eff00` is the only element in the export whose resting pose is a fully opaque full-frame quad. One instance is not a rule, so the renderer is unchanged and the HARNESS is fixed: a blank pair now reports BLANK -- both renderers drew nothing; this row proves nothing. `status` is untouched, so an unrelated DIFFERS still fails. THE VOICE EXPORT IS KNOWN INCOMPLETE. The Decoder booted Canary with --xma_param_probe and the game decodes ALL THREE streams CONCURRENTLY, in three XMA contexts whose byte sizes match the disc payloads exactly. So "three presentations of one take, pick one" is refuted by the running game and the question I had been arguing -- WHICH presentation -- has no answer. This one no census could have caught. Every measurement was right: the streams are equal-duration, one is silence, one is 0.60x another with the residual 26.8 dB down. The frame around them was wrong, and the file says ChannelMask 0x0002 on all three. It took the running game -- which is the mission's own sentence arriving in practice. BEHAVIOUR HELD DELIBERATELY. An equal-gain 1/n sum of channel pairs is not a downmix either -- MISSION section 6 pins an explicit matrix for exactly that reason -- and summing cost S00A 6.02 dB when one stream was silence. Swapping one guess for another on a message is what produced this entry twice. What changed is that the wrongness is now LOUD, because this failure sounds like success: one stream is clean audible dialogue. A top-level manifest warning per movie, the console line, and the authored entry all say `1 of 3 streams`. "They are 5.1" is recorded as the Decoder's HYPOTHESIS with its own counter-evidence attached, and nothing builds on it. What settles it is asked: a recording of the game's own output over ADV through the null sink, which turns channel roles into a fit against an oracle. Refutation attempt, survived: the Decoder's loading-screen variant map. Entries 0/1 carry 7 elements and 12/15 carry those seven plus baseeff, eff00 and loop5 -- exact in count and identity, and it is what made build_00 a control. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF |
||
|
|
c43d44f57e |
port: withdraw the dual-mono generalisation -- the measurement stands, the rule does not
I argued `highest_rate` had no case because ADV's higher-rate presentation is dual-mono while its louder one is mono-in-stereo, so the extra bytes buy a duplicated channel rather than fidelity. The Decoder tested that disc-wide over the 28 three-stream cues: the stream-3/stream-2 size ratio runs min 0.0778, median 1.2565, max 2.9163, sd 0.5057, with only 12 of 28 within 15% of 1.0, and declared rates scatter with them. A 37x spread is not a duplicated channel. The CHANNEL MEASUREMENT STANDS -- ADV chunk 1 is mono-in-stereo and chunk 2 is dual-mono at -8.318574, this port's own decode, which the Decoder could not re-run and did not dispute. What fails is the step from one asset to the format. NOTHING IN THE EXPORT CHANGES. `loudest` is a per-asset content rule -- it reads the peak of the streams in front of it -- so a scattering structural ratio cannot undermine it. What changes is the REASON, in four places: authored/audio.json's presentation_why, the selector comment in audio.rs, BLOCKED.md's row, and DECISIONS.md. The honest statement is narrower: `highest_rate` was never refuted, it was never argued for, and neither is `loudest`. That is why the entry is marked CHOSEN rather than measured, and why one capture deletes it. Recorded on the pattern rather than just the instance: this is the third claim of mine in two iterations that generalised a single-asset observation, after "the chunks are two stems" and "everything the sequencer paces off rest.t is late". All three were true of the thing I looked at. The failure is reaching for the rule a measurement would imply if it held everywhere and writing that down in the same breath as the measurement. Also noted, not mine and not affecting export_voice: S12B's three streams are byte-size identical, and BIRD_224 is three-stream while not being a movie cue. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF |
||
|
|
2939c21b38 |
port: settle times measured -- the pacing was already right, and my own red flag was half wrong
THE TRANSCODE CACHE HAD NEVER HIT. `video::transcode` has carried one since P4 -- a `.cmd` sidecar with the command, the source size and the channel count -- and `main.rs` clears the output tree wholesale immediately before the check, deleting the sidecar and the file it stamps. Six exports in this session paid the full Theora encode and produced five byte-identical files, roughly 48 minutes. Nothing reported it, and nothing could: a cache is silent either way and the only symptom is a wall clock that looks like the job being slow. The wipe now spares `video/` and `prune_videos` deletes anything in it this run did not claim, so the wholesale guarantee is kept rather than traded. A re-export is 20 s. SETTLE TIMES, MEASURED, and they refute more of my row than they confirm. The principle holds -- the title's rest.t is 251 units = 4.183 s where its art finishes at ~2 s -- but "everything the sequencer paces off that landmark is therefore late" does not. Measured the port the way the game was measured, by VISIBLE SPAN rather than arrival-to-arrival: publisher wordmark port 4.25 s game 4.297 / 4.604 / 4.370 developer logos port 3.50 s game 3.508 / 3.503 / 3.366 black hold port ~0.25 s game 0.2 - 0.3 title -> plate port 2.000 s (declared 120 units) game 2.247 s Dead on. My earlier reading compared the port's transition timestamps against the game's visible spans, which differ by the exit ramp plus the black hold -- the whole of the discrepancy I was about to chase, and the same definitional trap that cost this corpus 0.48 s on the plate delay. Nothing in the sequencer is changed. `dwell_seconds` stays null, now for a measured reason rather than an absent one: `timing.json` said "if a capture ever times the real boot, this is where that number goes", and the answer is that nothing goes there. Not authored, deliberately: an (A)->menu dwell, which measures 3.763 s and contains a 1.53 s guest load stall on a cold cache; and the menu build-in and (B)->title, which rest on one run where the port is already within ~0.1 s. THE VOICE PRESENTATION MOVED TO authored/, because the recommendation behind it was withdrawn as self-contradictory and the choice is now unambiguously mine. `voice.presentation` = `loudest`, and the reason to switch is a measurement, not a preference: ADV chunk 1 is MONO-IN-STEREO and chunk 2 is DUAL-MONO, so chunk 2's extra bytes encode a duplicate channel rather than fidelity. That explains the byte-rate difference and removes the only argument for `highest_rate`. ADV's dialogue now exports at +0.3 dBFS instead of -8.7, which is the SE bound's documented decode overshoot on a wave mastered at full scale. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF |
||
|
|
7a6a027fc8 |
port: P7 -- the new-game intro plays, and the two screens it skips are named out loud
S00A has been exported since P4; what P7 needed was something to play it and a
defined place to land. Both are here, and the interesting part is the gap.
The real chain is NEW GAME -> DIFFICULTY -> SELECT DATA -> (A) on a save slot ->
~4.5 s -> S00A. DIFFICULTY and SELECT DATA are MEASURED destinations that are not
GP_TITLE builds, so no screen file exists to go to. The port jumps from NEW GAME
to the one thing in that chain it has -- and the whole design is about not
letting that read as a sequence:
* MenuFlow.accept returns a new kind, `video`, rather than folding this into
`blocked`, because the caller has to announce the skip and a distinct kind is
what forces it to;
* the runtime prints the skipped screens by name on every run;
* flow.json carries `skipped_chain` as DATA, so what is missing lives beside
the decision instead of inside a GDScript string.
After the movie the port returns to the title. Authored, and it has to be: the
game goes into mission 1 and gameplay is out of scope. The ~4.5 s before the
movie is left EMPTY on purpose -- GP_TITLE does carry a loading screen and 4.5 s
is about the right shape for one, which is exactly why that belongs in BLOCKED.md
and not in flow.json.
`--script`'s 20 s per-step timeout would have killed every movie run at step 1.
Raising the constant would have been wrong the other way: a movie stuck at frame
0 would then hang the job, and a job that waits is worse than one that fails. The
test is now LIVENESS -- while get_stream_position() advances the deadline moves
with it, and a stalled movie still trips the same 20 s.
Found while looking: GP_TITLE's four unnamed builds (entries 0, 1, 12, 15) are
LOADING screens -- every element in all four is pgloading_*, and LOADING is one of
the three names the decoder read out of the title part's state function. NOT
renamed here: which member of each pair is which locale is an inference, and a
name stops being questioned once written. Handed over.
One of them is a second casualty of the rest.t problem, and a worse one:
pgloading_eff00.prm rests OPAQUE BLACK at t=38, so anything drawing that screen
at its declared rest paints a black rectangle over all of it. The title's case
only dimmed a frame.
REFUTATION, attempted and SURVIVED: HANDOFF says "exactly the six screen builds
carry the black .prm quad while the six overlays do not". Counting bundles with a
full-screen black primitive gives 8 and 4 -- build_12/15 carry one too. But
theirs runs black -> held -> clear where the transition quad runs black -> clear
-> black, so read strictly as "the quad whose group is the transition" the claim
holds. Recorded anyway: there are two kinds, and the naive census over-counts.
Gate: NEW GAME -> S00A plays 93.75 s against a declared 93.9 -> title, with
98.453 s recorded off the Master bus. What that does NOT show is that S00A's own
audio is in the mix -- bed and movie were not separated in this run, and the
write-up says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC
|
||
|
|
c225852fb3 |
port: the ring spins, the plate needs no constant, and rest.t was never the settle
Two milestones' known-wrong bits, both now answered by the RE agent, both taken. P5 -- the focus ring. It was drawn at 0 with a comment saying so. The period is now measured (continuous spin, eight evenly spaced autocorrelation peaks over nine revolutions, no angle estimated anywhere) and it needs NO authored constant: the period is the element's own declared t=120, and what the measurement adds is only that the turn repeats rather than stopping -- which "groups hold" could not decide, because 0 and 360 are the same pose. `spin_period_units` is structural and narrow on purpose: two keyframes, differing in nothing but rotation_deg, by a full 360, first timed and second untimed. 16 of 212 elements in this export match and all 16 are focus rings, zero false positives. That check is the point -- the measurement was taken on ONE button of ONE screen, and a rule that caught anything else would be extrapolating it to elements nobody watched. Verified on the port's own render with the RE agent's own control: bit-identical one period apart across the whole frame, 3.6/255 inside the ring's box at quarter-period steps, and box luminance conserved to 0.027 % over eight phases -- which is the observable they used to separate rotation from a pulse. Not claimed: direction (no signed angle was ever measured) and phase across a focus change (their run held focus throughout). P3 -- the plate. Last iteration I refuted their authoring instruction and shipped it anyway rather than pick between two of their numbers. The refutation held and the answer came back better than either option I offered: AUTHOR NOTHING. Both builds run on one clock started together and the plate arrives at its own declared t=238. The 2.13 s constant is deleted. The premise that failed was mine: rest.t IS NOT WHEN A SCREEN SETTLES. It is the last hold keyframe before the exit. ptlogo1 stops MOVING at t=42 and then creeps 5 px and 31 alpha steps to t=251. Reading rest.t put build 4's arrival at 4.350 s instead of 1.967 s, and the "2.51 s, which is not a landmark of anything" I sent them is that error wearing a decimal point. 238 - 118 = 120 units = 2.000 s against a measured 2.135 s at 28.1 fps presentation. Checked against my own export before touching anything. `ScreenView.settle_time()` still uses rest.t, and so the boot sequencer paces every screen off the wrong landmark. NOT changed here: "visible arrival" is a heuristic and getting it wrong re-paces everything. Filed, and asked for a timed boot instead now that their oracle is live. REFUTATION: two of their pages measure the same declared 120 units of wall clock during a static hold and disagree by 2 % -- plate 2.135 s (28.10 fps implied), ring 2.177 s (27.56 fps). That is seven times the plate page's own 6 ms run-to-run agreement, and it lands on the argument that page uses to justify itself: "the build-in is where frames are dropped; the static hold is not". Also the ring page's band, 27.6-28.8 fps, does not contain its own measurement -- the mean needs 27.56 and four of seven spacings are outside. Filed, not worked around: my port uses the declared 120 units either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC |
||
|
|
9b30fb5933 |
port: P3 -- the boot title gets its PRESS (A) plate, and two of the RE agent's numbers do not agree
ScreenView now draws two builds at once, which it never had to before. It is a second ScreenView in the same SubViewport rather than a subordinate screen inside one: each build has its own timeline, its own textures and its own hold, which is the entire content of the finding, and Node2D siblings already paint in tree order. `paint_order` still means what it meant -- an ordering WITHIN a build. The delay is authored in flow.json on the BOOT STEP, not on the `title` screen. What was measured is the boot title; whether the plate is there when the title is reached again -- (B) from the menu, or after the attract movie -- is not, and hanging it on the screen would quietly claim that it is. REFUTATION, and it is the substance of this commit: the RE agent's authoring instruction does not reproduce the RE agent's own measurement, and the gap is 3.97 s. The instruction is "when build 4 has settled, wait 2.13 s, composite build 2". But build 2 has a group and this port plays groups -- ptbtn00 is alpha 0x00 at t=214, still 0x00 at t=236 while it slides 10 px up, and 0xff only at t=238, which is 3.967 s at 60 units/s. So the plate is first VISIBLE at settle+6.10 s, while what was measured -- the glyph counter leaving 154 -- is visibility at settle+2.13 s. Both groups starting together puts it 0.38 s BEFORE settle; build 2 starting at settle puts it at settle+3.97 s; landing on the measurement needs build 2's group to start 2.51 s after build 4's, which is not a landmark of anything. The measurement is untouched -- it is an observation of the running game and I have no standing to doubt it. What is refuted is the step that turns it into an authoring rule. So the port ships the instruction, prints the discrepancy on every boot, and files the row. Same call as the BGM sub-waves: a port that quietly picks the number that looks right destroys the evidence, because a corrected boot looks exactly like a correct one. Also refuted, and it was mine: BLOCKED.md has said since P2 that "no element's alpha reverses direction anywhere in this export, so nothing pulses". ptbtn00 reverses -- 0x00 -> 0xff -> 0x00 -- and it was in the export the whole time. The claim had been checked against the screens P2 happened to be animating. The port still draws no pulse, because no reading of this group yields the measured 2.24 s: the whole group is 4.47 s and from its first keyframe 0.90 s. Gate: `--boot --capture=` writes one frame of the composited end state, instead of the 600-PNG filmstrip that was previously the only boot artifact. `--screen=title --overlay=press_start` raises the same composite in two seconds for anyone who does not want to sit through 137 s of Theora. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC |
||
|
|
d6252481e1 |
port: P6 -- the menu has sound, and the BGM I "chose" was decoded all along
The three Static.slb cues and the menu bed now export to Ogg Vorbis and play.
`sylpheed_formats::media` does the assembly; nothing in port/ has heard of XMA.
Three things this milestone got wrong before it got right, all recorded in
docs/port/DECISIONS.md because the corrections are the useful part:
1. The cue offsets were a Rust `const` in the exporter. They are MEASURED, not
decoded -- a measured value compiled into the exporter is a measurement
wearing the costume of a decoded field, and nobody deletes it because nobody
can see it. They are authored/audio.json now.
2. I picked BGM_001 and wrote a careful `why` calling the choice arbitrary. The
menu's music is BGM_103, and it is in HANDOFF at
|
||
|
|
76979c799c |
port: P5 end to end -- and the port's title never says PRESS (A)
The P5 gate walk starts on a screen. This runs the whole objective instead, and
it is the only thing that would have found what it found:
xvfb-run -a godot --path port -- --boot --play \
--script=accept,down,down,down,down,accept,cancel,cancel --shots=/tmp/e2e
publisher wordmark -> developer logos -> ADV (151.9 s) -> title -> (A) -> main
menu -> navigate -> (A) -> EXTRAS -> (B) with focus restored to ptbtn05 -> (B)
-> title. 166.76 s, exit 0, nine frames. Shared as 1788003274-e68367e787d5.
THE PORT'S TITLE DOES NOT TELL THE PLAYER TO PRESS (A). The boot's last step is
`title` = GP_TITLE build 4, and build 4 has NO `PRESS (A) BUTTON` plate. P5 has
just made (A) the only way off that screen.
Not a guess about the art -- both states are captured off the running game and
differ by exactly that plate (live-title-build4-no-plate.png vs
live-title-press-a.png), and the plate is ALREADY EXPORTED as `press_start`,
build 2, sitting in export/screens/title/ unused by anything.
RECORDED, NOT FIXED, and the distinction is the point. This is P3's gate that
P5 exposed, and fixing it needs two things the port does not have:
* WHICH state an idle post-boot title shows -- build 4 alone, build 4 with the
plate over it, or build 4 THEN the plate after a delay -- is BEHAVIOURAL.
The game demonstrably has both states and nothing says which follows the
intro. The port has no oracle for a sequence; that is the Decoder's.
* showing it means DRAWING TWO BUILDS AT ONCE, which this port has never done
-- every mode loads exactly one screen. That is a change to ScreenView, not
a line in flow.json, and it is not being smuggled in under a navigation
milestone on the strength of "it looks more right".
Filed in BLOCKED.md. P5's gate is (A) into a submenu and (B) back; both work.
Two smaller things the same run found, both fixed:
* the boot step's `why` still said "nothing takes the title's place until P5
gives it somewhere to go". P5 has. Now says what is true: `--boot` STOPS on
the title (a boot that ends by fading to black looks like a crash) and
`--play` HANDS THE HELD TITLE OVER -- the stop is not a bug and the handover
is not another boot step.
* an empty focus printed as a line that trailed off, reading like a value had
gone missing rather than like there is none. The title is a screen with no
`buttons` that still takes (A), so it now prints
"(none -- this screen has no focusable item)".
Also confirmed: entering a submenu directly (`--menu=extras`) and pressing (B)
enters the parent at its AUTHORED initial focus, not a restored one. There is no
history to restore and MenuFlow.cancel only claims a restored focus when the
stack agrees about where it is going.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CtmUw5N5LJaMW1Njb8Ziey
|
||
|
|
1defbe0a32 |
port: P5 -- the menus navigate, and the focus ring is drawn wrong on purpose
P5's gate is "a human clicks through it". The artifact is a scripted walk that
proves the wiring rather than the intent -- up (wraps 01->05), five down, (A)
into EXTRAS, down, (B) back, landing on the main menu with focus RESTORED to
EXTRAS, ten PNGs one per settled step:
xvfb-run -a godot --path port -- --menu \
--script=up,down,down,down,down,down,accept,down,cancel --shots=/tmp/p5
--script posts InputEventAction through Input.parse_input_event so the presses
arrive at _unhandled_input exactly as a d-pad's would. Calling MenuFlow directly
would have been shorter and would have proved nothing: the wiring between a
press and the cursor is the part most likely to be broken, and a direct call is
exactly the part that skips it.
Derived vs authored, which P5 is the easiest place to blur:
* DERIVED -- the ORDER of the items, from each screen file's `buttons`, which
the exporter already fills from button-role elements sorted by resting Y.
* AUTHORED -- destinations, initial focus, what (B) does, and left/right being
a no-op. All measured off the running game (HANDOFF Q4/Q5) or chosen, none
on the disc, all in authored/flow.json with a why.
Four of five main-menu destinations are `goto: null` with a `blocked` note. That
is a MILESTONE BOUNDARY, not an unknown -- DIFFICULTY, the save list, the lesson
list and OPTIONS were all measured and live in archives this export does not
carry. `blocked` and `none` are kept apart so nobody later "discovers" the gap.
--headless CANNOT DRAW, and the port hung instead of saying so.
Measured, not assumed: under --headless Godot's dummy renderer never emits
RenderingServer.frame_post_draw, so every capture path awaited it forever --
--capture since P1, --film since P3, --shots as of now. With stdout block-
buffered the observable behaviour was SILENCE, FOREVER, which in a loop reads as
a job still working. Isolated by `--quit` (prints, exits 0) vs `--capture` (zero
bytes, killed at 40 s). Now those three flags refuse at STARTUP naming the
xvfb-run line that works, and --script no longer waits for a frame it is not
going to photograph -- so headless walks the menus in 4.5 s as a cheap
regression check needing no X server.
REFUTATION ATTEMPT, against the Decoder's
|
||
|
|
9fbb352ef0 |
monorepo: one repository for the decoders, the port and the corpus
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.
|