The late start was a second condition on the start filter, end - s < 1_500_000,
only within one bank. ADV's predecessor trailer sits 3618816 B before end, so it
was rejected and start fell back to anchor, a TOC offset rather than a stream
boundary -- which is why it hit regions over 1.5 MB and never single-stream ones.
ADV region 3114352 -> 3618816 B, streams 2 of 3 -> 3 of 3, complete, dropping
zero chunks. S00A 1 of 3 -> 2 of 3 with the third digitally silent. The runtime
no longer prints an incomplete line for either.
Also fixes the warning crying wolf: S00A still read KNOWN INCOMPLETE over a
93.694 s stream of exact zeroes. Exported gains content_waves and the warning,
console line and manifest field all key on kept < content rather than kept <
present. Second time in two iterations this warning was wrong in the cautious
direction -- over-warning is what makes the next real warning unreadable.
S00A is a second movie in the predicted direction: kept went 1 -> 2 because a
chunk that was a different duration now matches at 93.694 s. Not independent
ground truth, but a different asset and the outcome was predicted first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
FOUR THINGS, and the first is what MISSION section 3 calls the measure of
progress.
DELETED `exit_ramp_units` AND `exit_ramp_seconds`. They were authored because the
disc had no time slot on a group's final keyframe, so the ramp into it was the
one unknown duration per screen. Under the corrected record layout that keyframe
does not exist -- a group is an 8-byte header then frames x {u32 time; 36-byte
pose} and every pose is timed. VERIFIED DEAD BEFORE DELETING: setting it to 9999
(166 s) moved the boot's transitions by 0.04 s, which is wall-clock jitter, and
both uses in ScreenView are gated on a condition that no longer fires on any of
the export's 866 keyframes.
INVERTED THE FORMAT'S OWN RULE. `check.rs` enforced "the final keyframe has no
`t`; the disc has no time slot there" and FORMAT.md stated it. Both are now
backwards, and the validator fired 150 times on a re-export. I had not run
`check` between pinning the tag and measuring against the oracle -- the pixel
harness was green while the format validator was failing on every screen with a
multi-keyframe group. A correctness harness does not replace a format one; they
fail at different layers.
GUARDED A SCALE-0 LEAF, which the Decoder hit in its own renderer: its leaf
branch marked the element drawn unconditionally while the blit returned early on
zero scale, so a scale-0 leaf suppressed its parent and blanked the element --
live on all four loading screens. This port did not have the bug only because
authored/rendering.json happens not to list pgloading_loop5. That is an accident
of a gate written for another reason, not a defence, so `_draw_leaf` now reports
whether it drew and `_draw` falls back to the parent.
ISOLATED THE PACING QUESTION rather than leaving it as a suspected regression.
Legacy association: publisher 4.70 agrees, developer 3.92 DIFFERS. Corrected:
publisher 4.26 DIFFERS, developer 3.62 agrees. Both misses are ~0.03 s outside a
composite bound. The association traded which screen is marginally out; it did
not regress the pacing.
Bumped the pin c -> d for the parser and audio changes. Its headline renderer
change does not reach this port: sylpheed-cli builds from the workspace crate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
I tested the wrong switch. SYLPHEED_KF_TIME_SHIFT is a superseded partial fix: it
got the association right but LEFT POSE 0 UNTIMED, which is exactly why the
untimed keyframe appeared to move from last to first. The real correction is the
DEFAULT in the tagged crate, with the old reading behind SYLPHEED_KF_TIME_LEGACY.
So last iteration's five rows measured a mismatch against a knob nobody should
use -- I suspected they were not decisive, I did not suspect the knob was retired.
THE CONSEQUENCE IS MUCH SMALLER THAN I BUDGETED. A placement group is an 8-byte
header then frames x {u32 time; 36-byte pose}, so pose 0's time is the group's
lead-in word and every pose is timed. Measured on the re-export: 866 keyframes,
0 untimed. `pose_at`'s "the final keyframe carries no t, so give it a synthetic
time" premise does not invert, it DISAPPEARS -- dead code rather than wrong code,
which is why nothing needed re-deriving. And the leaf now reads t=0 x=-639,
t=150 x=-39, t=540 x=1521, giving x=781 at t=355: the Decoder's predicted
top-left, and the 1300 px discrepancy is gone.
Pinned by tag, which is what MISSION section 2's tagging rule is for. BLOCKED was
wrong in both directions -- "cannot be taken yet" AND "only when that branch lands
on main". It arrives when the tag is pinned.
COST STATED: sylpheed-cli builds from the workspace crate, so until this reaches
main the exporter and the reference renderer read different decoders and
verify-screen compares two eras. verify-capture is unaffected -- it compares
against oracle captures and never touches the CLI. Revert to the path dependency
when the tag is an ancestor of main.
Oracle: publisher_logo 1.00% -> 0.75%, developer_logos 0.39% -> 0.33%, and
extras' differing region COLLAPSING from 736x525 to 398x295 at the sweep position
-- the residual localised onto the one element still in question. title unchanged
at 1.82%, now posed at t=355, the Decoder's FITTED sweep time. t=390 measures
1.65% and picking it would be fitting the pose to the score.
REFUTED, MINE: "ptlogo_eff2 is the single drawn element at a scale that is not a
whole multiple of 100%". That census was parents-only; the 45 leaves hold
thirteen distinct non-whole-multiple scales and 125% is among the rarest at two.
The claim's real content was "the only one the port draws" -- about my element
set, not the disc.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
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.