Commit Graph

11 Commits

Author SHA1 Message Date
sylph-decoder
f2a5392efb re: (A) on a settled boot title DOES reach the menu -- the withdrawal explained
Clearing my own debt: I withdrew navigation.md's "boot title accepts a single A"
counter-example as confounded by three concurrent emulators and never re-ran it,
which left the claim unsupported rather than settled.

Clean trial: exactly one emulator verified by count, gated on the plate pulse
(glyph in [500,2500] held 12 samples) so the press lands on the BOOT title rather
than the attract loop's, delivery confirmed at [file-pad] keystroke vk=5800
down/up. Glyph after the press is 0 at +2 s and +4 s -- the transition -- then 327
steady from +6 s through +39 s.

327 is a proxy and reading a proxy is the habit this corpus keeps cataloguing, so
the screen was checked with which_title_screen.py instead: main_menu at RMSE
19.91 and 20.08 with margin ~10, inside the 9.9-11.7 band its control establishes
on four known captures. The before frame gives the "neither" signature at margin
0.10, correctly, since the title is neither main_menu nor extras.

So the count is 3 of 3, the latency is 4-6 s -- which is why a script that
presses and looks 0.5 s later concludes the press was dropped -- and the two
earlier failures were the confound, not the game.

Refutation attempted: sylpheed-port's leaf segment rates. Derived independently
from the disc and they SURVIVE exactly -- pteff03 +4.0000 then +4.0000 then a
hold, pteff03a -4.0667 then -4.0625 then a hold. So their inversion stands: my
linearity gate fails on the leaf whose declared track is perfectly straight.

And records the third structural consequence of main being stale, which they
raised: HANDOFF.md is the delivery contract and it lives on an unmerged branch,
so their checkout contains none of this week's entries. Findings written into the
contract reach them only through messages -- the channel the protocol says does
not count as delivery. Writing it in the contract is necessary and not sufficient
when the contract lives where the other party cannot see it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 17:17:41 +00:00
sylph-decoder
1bb4963ff0 re: WITHDRAW the (A)-press finding -- three emulators were live at once
While chasing the draw-stream question I found THREE xenia instances running
simultaneously (started 15:39, 15:44, 16:12), which violates the "one emulator at
a time" hard rule and confounds the finding I recorded last iteration.

All three read the same /tmp/xenia_pad.txt and share display :98. A press written
to that file is delivered to EVERY instance, while `screenshot` grabs whichever
window is topmost -- not necessarily the one that acted on it. So "(A) was
delivered and the screen did not change" may simply be two different emulators,
and the keystroke-level confirmation proves only that SOME instance received it.
The navigation.md entry claiming the boot title's "2 of 2" is no longer 2 of 2 is
withdrawn as unsupported, pending a clean re-run.

The cause was mine. run-canary's lockfile is the IMPLEMENTATION of the one-at-a-
time rule; a kill -9 orphans it, and the obvious unblock -- rm -f the lock --
also disables the guard for every later launch. I did that more than once today.

METHOD gains two entries. A lockfile is the rule, not an obstacle to it: clear a
stale lock only after confirming zero live instances, and COUNT them rather than
trusting a kill landed, because a plain kill is asynchronous and a -9 on a stuck
process can take seconds. When a guard blocks you, the question is whether the
condition it guards against is present, not how to remove the guard.

And a third instance of pgrep -f matching the shell that runs it -- this time it
killed a cleanup command halfway through, leaving the emulators alive and the
lock in place. Already recorded for wait-loops; promoted to "reach for -C first".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 16:20:24 +00:00
sylph-decoder
b6d4242068 re: WITHDRAW "ptloop01/02 do not free-run" -- I measured a pivot, not an extent
sylpheed-port noted that build 5's ptloop parent can be static while the leaf
record animates, and asked me to check it against my table. My own corpus
refutes my claim outright.

ptloop-leaf-sweep-positions.txt -- written earlier in this same corpus -- records
ptloop01.rat's nested record at loop length 600, whose leaf pteff03.t32 sweeps a
400 px-wide quad with its centre running x~921->1041 over t=340..370. The
parent's declared rect is (441,270) 200x90. The leaf draws 300 px outside it: the
parent rect is a PIVOT ANCHOR, not the drawn extent.

Checked against the two JP captures: my measured rect differs by 0 px -- and so
does the whole dead region y 270..450 x 480..960 around it -- while the band the
sweep actually occupies (x 721..1241) differs by 44 025 px. The zero was measured
where nothing happens.

So the port's reading is right and now confirmed from the disc: parent static,
leaf animates, and the two nested records cycle at DIFFERENT lengths, 600 and
720. My "single static keyframe" described the parent only. The era adjudication
is unaffected -- its box overlaps the sweep band only at x 721..776, which shows
no between-session differences.

The menu-loop question is still unsettled after a second attempt, and the second
attempt's failure REFUTES my diagnosis of the first. menu_loop_probe.py gated on
the plate pulse (glyph in [500,2500] held 12 samples), fired at t=484.5 s with
glyph 1723 -- a verified settled BOOT title, not the attract one -- pressed A,
and the press was delivered ([file-pad] keystroke vk=5800 down/up, 8 RE-INPUT
lines). Twenty seconds later all five frames still classified as the title
(rmse ~67-70, margins 0.06-0.16, the "neither" signature; screen_id says title).
So "the attract title accepts nothing" does not explain attempt 1, and the
corpus's "the boot title accepts a single A, 2 of 2 runs" is no longer 2 of 2.

METHOD: a declared rect can be an anchor, not an extent -- confirm an element
draws in a region before diffing that region to ask whether it moves.
navigation.md: confirm the screen changed, do not infer it from a delivered
press.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 15:55:31 +00:00
sylph-decoder
36c00f1a5c re: ptloop01/02 do not free-run on the settled title -- and the menu is not settled
sylpheed-port found ptloop01/02 free-running in their renderer on the menu path,
pinned them, and was explicit that pinning picks one pose rather than the game's:
"a capture question, not a harness one". It is, and it lands in my lane.

On the title it is now answered. Those leaves rest at (441,270) 200x90, INSIDE
the box the ptlogo_eff3 era adjudication uses, and across my two JP captures from
different sessions they are byte-identical: 0 of 18 000 px, max |d| 0, against a
whole-frame contrast of 116 492 px differing. So they are static at rest, and the
in-box between-session noise of 0.32 is not theirs -- the 645 differing pixels
all lie in a 30-row band at y 99..128, nowhere near the loop rect. That also
closes the reach caveat on the EN->JP noise transfer.

The MENU is a different bundle and is not settled. Build 5 declares the same rect
with a single static keyframe, and that is where their row drifted.
menu_loop_rest.sh was written to capture five settled menu frames and diff the
rect; it did not complete. The run reached a title at t=146 s and (A) did not
take across six attempts -- the documented intermittency where the attract loop's
title accepts nothing, unlike the boot title. Recorded rather than re-rolled.

Two committed main-menu captures cannot substitute: they differ across 57 % of
the surface (different geometries and capture paths), so the 88 % differing on
the loop rect measures the mismatch, not the loops. The control fails and the
comparison is void.

navigation.md gains the trap that cost this iteration a run: kill -9 on xenia
orphans /tmp/xenia-canary.lock, the next run-canary refuses to STDERR where a
polling script never looks, and a probe then sampled a dead display for 484 s
reporting `other` every 4 s -- because screen_id.py on an empty screen returns
`other` and "not the title yet" is indistinguishable from "there is no emulator".
Kill plainly so it clears its own lock, and assert the emulator is alive before
entering any wait loop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 15:43:29 +00:00
sylph-decoder
c177eb47c6 re: the black gap between screens is NOT a load -- measured, three legs
sylpheed-port's BLOCKED ask #2. The gap was the one quantity in the transition
with no rule: measured at 0, 3 and 2 frames across three transitions, and I had
proposed it might be a load, which would make it emulator- and storage-dependent
and unauthorable.

Leg 1, bundle size runs the wrong way. If the gap were the incoming bundle
arriving, the biggest bundle would gap longest. Build 4 is 12 278 666 B and gaps
ZERO frames; build 5 is 6 977 437 B and gaps 3 and 2.

Leg 2, ran title->menu a second time from cold. The outgoing ramp is
byte-identical (63, 127, 191, 255) and the gap is 3 frames in BOTH runs.

Leg 3, and the two runs are not a null comparison -- which is the objection leg 2
invites. The captures refute it themselves: press-to-first-change differs by ~12
frames between them (~25 against ~10). Something in this transition really is
cache-sensitive and moved by 0.4 s, while the gap did not move at all. The
control comes from inside the measurement rather than from an assumption that
conditions differed.

So the gap is deterministic to the frame and not a load. It is also not constant
across transitions (0, 3, 2, 3) and not in the fade group -- the port reports 866
keyframes across 16 screens with 0 untimed. A deterministic game quantity with no
rule found; black_hold_units stays 0, and "not a load" must not become a reason
to author a constant.

The load proposal in screen-transitions.md is marked refuted rather than deleted.

Also fills in docs/game/navigation.md, which the standing brief asks me to keep
and which I had not touched while measuring four transitions: a player-side
section on what a screen change looks like, and three scripting traps -- that
`pkill -f xenia_canary` kills the shell that ran it (cost a launch today, and the
same trap is already in METHOD for pgrep), that screen_id.py reports `menu`
during the attract loop, and that it cannot tell EXTRAS from the main menu.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-30 14:28:00 +00:00
sylph-decoder
0fd21bd744 re: the boot shows the publisher first -- the TSV that says otherwise attached late
The port flagged docs/re/data/boot-timeline-2026-08-29.tsv, whose label column
runs splash_dev before splash_pub, as a possible boot-order bug in its tree.

Three cold boots, t=0 at launch, no pad input, and the frames looked at rather
than only correlated: SQUARE ENIX 3.05-7.34 / 1.18-5.78 / 1.19-5.56 s, then a
~0.25 s black hold, then GAME ARTS/SETA/studio anima. Publisher first, 3/3.

The TSV is not wrong about any frame; its t=0 is ~7.7 s into the guest's boot,
so the publisher splash had been and gone before the stream opened. The tell is
in the file: its first twelve rows are byte-identical to four decimals -- one
held frame sampled twelve times -- and those exact numbers reappear in my run 1
at 8.42-10.94 s.

Second trap, new: ADV.wmv opens with its own SQUARE ENIX card, bloomed and below
centre, scoring 0.59-0.75 against live-splash-publisher.png. The classifier
fires splash_pub twice per boot and the second one is a movie frame. The real
splash holds perfectly still and scores 0.93-0.94.

And the dwells are DECODED, not measured: the publisher declares 240 units
(4.000 s) and the developer 195 (3.250 s), against measured 4.30/4.60/4.37 and
3.51/3.50/3.37. Measured over declared is 1.085 on average across six spans --
a 30 Hz timeline at 27.6 fps, which is the presentation rate this corpus has
measured independently three times. The port authors nothing here.

Instrument control run first: 11/11 content, 4/4 plate, the two splash
references rejecting each other at 0.035.

docs/re/boot-order-and-splash-dwell.md

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nsxw1A9JseUw99Yw1ZRQzY
2026-08-29 14:15:28 +00:00
sylph-decoder
fb536df8b4 re: the PRESS-A plate arrives 2.13 s after the title settles, measured twice
The one number the port said decides a structural question on its side: whether
`ScreenView` has to draw two builds at once. It does. The boot title shows
build 4 alone, and 2.13 s after build 4 stops animating it composites build 2
over it. Two independent boots agree to 6 ms (2.138 / 2.132), which is under one
sample interval.

Measure from SETTLED, not from first pixels. "First drawn -> plate" is 3.78 s in
one run and 4.26 s in the other, because the build-in animation itself ran 1.64 s
and 2.13 s -- that spread is the emulator's frame pacing, and it is exactly the
kind of number that looks like a measurement.

Ruled out before believing it: that the plate was pulsing all along, too dim for
a thresholded glyph counter. The counter reads EXACTLY 154 -- the committed
no-plate title's own value -- for every frame of a plateau nearly one full pulse
period long, with zero variation, and the surface mean is flat to +/-0.03 across
it. A cycling overlay moves both.

Also settled, and also not:

  * the black hold between two screens is 0.14-0.30 s, which brackets the port's
    authored 0.17-0.23 s and the file's declared 12 units. Their constant stands.
  * the (A)->menu latency is STILL not available, and now the reason is known.
    Both runs freeze one frame for ~1.4 s at surface mean 26.626 -- agreeing
    between runs to six decimals, and reproduced in run 2 with stream restarts
    disabled, so it is not the capture path. It is a guest load stall: the (B)
    path, which loads nothing, has no freeze at all. Any figure from it would be
    an emulator load time.

Refutation attempt, recorded whether or not it survived: navigation.md's "the
title is not input-ready for about ten seconds, and even then (A) registers
roughly half the time". At 7.29 s and 7.28 s after the title settled, (A) was
accepted first press in both runs, as was (B) on the menu. n=2 only makes "half
the time" unlikely (p ~ 0.25); it contradicts the ten seconds outright.

And the standing red banner is withdrawn: the interactive title IS reachable in
this container, twice, with no pad input, in ~3.5 minutes. Why it changed is NOT
established -- this container came up with no Xenia storage root at all, so run 1
created a profile -- and that is written as a correlation for the next session to
test rather than as a cause.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
2026-08-29 12:15:22 +00:00
sylph-decoder
9a10258738 re: the main menu has no idle self-return -- and four of my own durations were a backlog
Two results and one retraction, all from the same session.

REFUTED: 'an ~8-10 s idle returns to the title' does not apply to the main menu.
Held untouched it stayed put for >= 60 s, correlation never leaving
0.9245-0.9249. That timer is real but belongs to the TITLE. It was the only
reason 'B leaves the main menu' was classed as authored, so Q5's B rule is
upgraded to measured-ordering: B is delivered (canary logs vk=5801) and is the
only input in >= 100 s before the return.

The PRESS (A) plate: the boot title presents build 4 WITHOUT the plate first --
green-glyph 154, against 159 on the committed no-plate capture and 753/977/1493
on plate titles -- and the plate arrives after. That is the port's third option.

RETRACTED: four durations taken the same day. classify_array costs 1503 ms per
frame; running it per frame against an 8 fps x11grab drained the pipe at
0.64 fps, so every classified frame was stale and increasingly so. It
manufactured 'plate 24.66 s after the title art', 'B->title 15.58 s', 'B->title
25.60 s' and 'A->menu 20.26 s'. The tell: a transition, a press and a fade do
not share a duration, and the two B figures GREW across a longer run.

A backlog preserves ordering and destroys durations, which is why the sequence
results above stand and every timing does not. The ring's period is unaffected
and that was checked, not assumed -- ring_period ran at 15.03 fps against a
requested 15.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KNR5Y79D1T4bBr6gJQaWFP
2026-08-29 11:50:46 +00:00
sylph-decoder
3a87a26235 game: fill in the player's-eye navigation map from the committed oracle frames
Sections 1 to 3 were almost entirely open questions. Everything a capture in
docs/re/captures/ actually shows is now written down from the chair: what is on
each screen, what the cursor does, and what each footer offers.

Boot: the publisher plate is SQUARE ENIX, the developer plate is GAME ARTS /
SETA / studio anima, both still pictures the game draws rather than video, then
the cinematic -- one A skips it, 57 s to the title against 193 s without.

Main menu: the five labels and where each goes, the wrap rule, and the caveat
that initial focus varied across four boots. Carries the footer warning from the
measurement in the same push.

Submenus: NEW GAME's DIFFICULTY and SELECT DATA; LOAD GAME's slot carousel,
Details panel and its five-button footer; the six tutorial lessons in two
groups; OPTIONS' four categories; EXTRAS' three items; and MISSION SELECT with
the locked-list explanation for the cursor that would not move.

What stays open is marked open, and it is now the residue no capture answers
rather than the residue nobody looked at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013UxPvE5cz7zekXBKi7Xw2r
2026-08-29 10:43:12 +00:00
MechaCat02
a8815f2826 agents: the team protocol, the share tool, and a player's-eye navigation doc
Some checks failed
CI / Native — macos-latest (push) Has been cancelled
CI / Native — windows-latest (push) Has been cancelled
CI / WASM — Web (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Native — ubuntu-latest (push) Has been cancelled
**navigation.md rewritten from the player's chair.** It was written from the
inside out -- GamePart ids, pak names, sprite names -- which is how WE find
things, not what the game shows anyone. Now it describes what is on screen, what
you press and what happens, with internals as footnotes. Most rows are open on
purpose: it exists to be filled in by playing, and the in-game tutorials are the
resource for the flight half.

**tools/share** gives transient files provenance without giving them history.
Three kinds of thing were travelling down one channel with opposite needs: code
and decoded knowledge want permanence, cited evidence wants permanence, and
"look at this PNG" wants no history at all. The third kind bloats a repository
forever; passing it by message is worse, because the receiver gets bytes with no
idea which build produced them. `share put` records who, when, what, the sender's
commit, and whether their tree was dirty -- because a capture taken from a
modified tree cannot be reproduced from the sha, and the receiver deserves to
know that before building an argument on it.

**docs/agents/PROTOCOL.md** is the contract. The parts that matter:

Dynamic RE stays with the Decoder -- most of what is open is behavioural and
cannot be answered from the file. What the planned Referee adds is different:
bias enters at what you CHOOSE to capture, so a corpus captured to a fixed
protocol by someone with no hypothesis is worth more than one captured to settle
an argument.

A message may point, ask, prioritise and challenge. It may not change scope,
redefine ground truth, or carry a finding instead of writing it down -- including
a message claiming to relay the human, because a relayed instruction has no
evidence attached and this project has watched a wrong belief travel further and
faster than its correction.

Adversarial duty is explicit: every iteration, try to refute one claim of another
agent and record the attempt either way. Run your own instrument through a
control first. Disagreements go to the human with both positions, not to
whoever is more certain.

And no agent may verify its way out of its own role: the Port has no oracle, the
Decoder builds nothing, the Referee interprets nothing.
2026-08-29 11:42:29 +02:00
MechaCat02
65cefa74c3 monorepo: one repository for the decoders, the port and the corpus
Some checks failed
CI / Native — macos-latest (push) Has been cancelled
CI / Native — windows-latest (push) Has been cancelled
CI / WASM — Web (push) Has been cancelled
CI / Formatting (push) Has been cancelled
CI / Native — ubuntu-latest (push) Has been cancelled
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.
2026-08-29 11:34:46 +02:00