cae2f7596e79f48d80893f7b04cca969f16ece2b
1211 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
159778faf1 |
formats: test backdrop coverage per instant, and record where the .tbm hunt reached
Two things, neither of which moves a verdict. The port pointed out that rejecting on DECLARED size replaces one error with its mirror: an element scaled ABOVE 100% could cover the screen from a smaller declared size, and my guard would silently exclude it. Checked against the disc first: across 921 keyless elements, ZERO cover the screen only via scale, so the mirror case does not occur here. Adopted anyway, because the construction does not need that to stay true -- coverage is now tested per instant against the scaled size, alongside the opacity test, since both animate on the same ramp. 80 forced instances before and after, split 42 .prm / 38 .tbm, unchanged. Second: an attempt to upgrade the 38 .tbm verdicts from inferred to decoded by finding the texture and measuring its alpha coverage. It cannot be located. Not in its bundle (no RATC record, no sprite-table entry, for any of the 13 names); not a file (no .tbm anywhere on the disc); not a pak entry (its archive's hashed TOC contains none of the name, its uppercase form, its stem, .t32/.tga/.xpr variants, or ui\\ and tex\\ prefixes, across four archives); and not visible in our composite, since compose skips an element with no resolvable sprite, so we draw no pixels for a .tbm at all -- and no committed capture covers a screen that has one. So a second reading survives and is recorded rather than excluded: a .tbm may contribute no pixels, in which case its paint position is INERT rather than correct. That leaves the 38 harmless instead of right -- a different claim with the same consequence. Distinguishing them needs a capture of GP_SAVE_LOAD, GP_BUNK or GP_DEBRIEFING_PILOTLOG, all behind the A fault. One upgrade: pfbase.tbm's first position is MEASURED, not inferred -- it is element 0 of the save/load frame and the order read off the running game starts [0, 1, 2, ...]. Twelve of the thirteen .tbm names still rest on the rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
29c99e1b3e |
re: primitive blend mode -- undecodable with reach, and no longer a risk
Looked in four places. The bundle has no field: a primitive has no RATC child at all and the declaration words are constant across every element of three measured screens -- the two grounds ui-prm-primitives.md already used to refute a bundle-side LAYER key, and they apply identically to blend. The colour census: every full-screen *eff00* primitive on the disc carries pure black at its various alphas, and the only non-black primitive anywhere is pbafc.prm, RGB 00e8e0 cyan. The occlusion constraint cannot reach that one. pbafc.prm looked alarming at a declared 844x600 and alpha ff; it is a small moving glint. It strobes between alpha 255 and 124 every 2 units, travels from x=178 to x=291, and is scaled 2%x3%, so it draws about 17x18 pixels. At that size it occludes essentially nothing. The oracle is unavailable: GP_READY_ROOM is a recorded no-go and gameplay needs the A press that faults the guest in this container. But the consequence closes even though the question does not. For a BLACK quad -- which is every primitive forced_backdrop touches -- the hypotheses differ only in whether it hides what is beneath. Drawn first it is correct under both; drawn last it is correct only under additive. So the rule's verdict is robust to the open question, and the port's original "layerless sorts last" was wrong under alpha-over and merely pointless under additive. This is explicitly NOT evidence for alpha-over. The investigation also found forced_backdrop judging coverage from the pivot alone, ignoring scale -- pbafc.prm is the disc's own proof that a nominally 844x600 element can draw at 2%. Checked before changing anything: all 80 forced instances are at scale 100% on every opaque instant, so no verdict moved. The guard now requires scale >= 100 at the instants it counts as opaque. Defensive, not a fix. 4 + 13 disc tests green either side. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
1f6e07598e |
re: the primitive colour census -- and it refutes 38 of my own 80 forced verdicts
A disc-wide census of the ARGB that keyless elements carry. Every full-screen *eff00* PRIMITIVE is pure black at its various alphas (ff000000, 7f000000, 40000000, b2000000, cc000000, d4000000, 00000000). Black at alpha a over content is exactly an alpha-over dim or fade, and an additive black quad would be a no-op nobody would author -- so this narrows the open blend question a long way. The only non-black primitive on the disc is pbafc.prm, RGB 00e8e0 cyan at alphas up to ff, and it is 844x600, NOT full-screen, so it sits outside forced_backdrop's geometry guard. It is now the sole additive candidate. The census also refutes my own argument for nearly half its verdicts. Of the 80 forced-first instances only 42 are .prm; 38 are .tbm carrying fade ffffffff. A SOLID white quad at alpha 255 painted first would make the screen white, and no screen is white -- so a .tbm is a white modulation on a texture, and element alpha does not establish its coverage. That is the .t32 error one file extension further out. I guarded that with el.sprite.is_some(), which fixed the symptom and not the cause: an element's alpha is not its texture's opacity, and only an untextured primitive makes the two the same fact. So 42 verdicts stay decoded and 38 drop to inferred -- still almost certainly right, since all are named *base*, all are full-screen, and pfbase.tbm's first position is measured in the running game, but that is a name-and-role argument which this page elsewhere calls the weaker kind. The code is deliberately unchanged. Restricting forced_backdrop to .prm would send eleven screens' backgrounds back to u32::MAX -- last -- which is the blank-screen bug the rule was written to fix. Downgrading the status is honest; reverting the position would be wrong. The 42/38 split is pinned by a test so anyone tightening the rule sees what it costs. Separately, on the port's black_hold_units ask: four more no-input boots yielded one usable log, which armed late and missed the publisher splash, so the sample is still two runs spanning 3 and 4 frames. Their 6.5-9.2 range stands. And a reason it may not be resolvable this way: the draw log DROPS frame numbers -- in the 3-frame run, frames 121 and 124 are absent entirely, so "frames with no sprite" and "span of frame numbers" are different quantities. Their statistical correction is taken: at n=3 the sample SD (3.893) is the estimator, not the population SD (3.179), making my run 1.88 sigma from the corpus mean rather than 2.31. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
7a5f7b886a |
re: downgrade -- the drift explains the 4.1% in sign, not magnitude
The port refuted the stronger half of the last claim and was right. I wrote that the units/frame drift explained the publisher splash's 4.1% error against its declared dwell. It explains the sign only. Their test verified exactly here: the publisher/developer dwell ratio is 1.2143 declared, 1.2784 as the corpus's three cold boots measure it, and 1.3678 as this container's drift predicts -- so the drift's direction is right and real evidence, but its magnitude is about 2.4x too strong. One refinement, because the means are being compared more finely than n=3 supports: the corpus's three boots individually give excesses of +0.89%, +8.24% and +6.79%, a spread of 7.3 percentage points -- WIDER than the 5.30 pp gap under test -- and boot 1's ratio (1.2251) is essentially the declared 1.2143. This run sits 2.3 sigma above their mean: suggestive, not established. Not closable without a frame log from the corpus's instrument, which was screenshot timing and has none. An attempt to give this side an n of 3 failed on tooling and is recorded: ARM=early loses its F10 about 40% of the time -- two of five runs logged "ARMED EARLY" and produced no draw log at all, with nothing in the session log distinguishing them. Also fences the 33% drift against a misreading the port flagged: it is PRESENTATION pacing and cannot reach keyframe_units_per_second = 60, which is the game's logical rate, decoded under Q1, and which a renderer converts through at its own frame rate. And records a cross-check neither side went looking for: the batch counts are 1 and 2 on the publisher against 3 and 6 on the developer, and the port reports a count restricted to SPRITE-BEARING elements reproduces that exactly from the export -- so palogo_eff0, the layerless forced backdrop, is not in the batched draw, confirmed from the file. Two instruments that disagreed about that element in every previous iteration now agree on which one it is. New tool splash_boundaries.py carries the corrected counting method. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
6c8ab5db94 |
re: withdraw the splash frame boundaries -- count the batch, not the logged quads
The port refuted the boundaries in boot-splash-dwells-are-declared.md by arithmetic: the two splash spans gave 2.237 and 2.414 units/frame, 7.9% apart on one boot of one guest, which must be one number. They proposed the spans were anchored on different elements. The log says the cause is worse. The developer splash batches SIX quads into one draw (indices=24) and the log dumps only the first two. While the three glows are alive they occupy that prefix, so the three wordmarks are invisible to the log until the glows stop being submitted at t=45. "Developer wordmarks first drawn at frame 140" was the logging prefix shifting, not the game -- and the same defect explains why palogo_anima never appeared at all. The fix costs nothing: indices/4 is how many quads the draw actually holds, and the 8-vertex cap cannot touch it. Its transitions land exactly where the declared count of elements with alpha>0 changes, giving free calibration points: publisher 1->2 quads at frame 5.5 (t=15), 2->1 at 22.5 (t=45), ends 119.5 (t=255) developer 3->6 quads at frame 126.5 (t=15), 6->3 at 139.5 (t=45), ends 209.5 (t=210) That yields 1.765 and 2.165 units/frame on the publisher, 2.308 and 2.357 on the developer -- the developer's two independent segments agreeing to 2%, and the rate rising 33% across the run. One cause for both the port's 7.9% inconsistency and this page's open 4.1% publisher error, exactly as they predicted: the publisher splash runs during the first seconds, where the rate is furthest from its later value. It also means no single units-per-frame figure describes a run here, which is the dwell-is-emulator-paced conclusion from a third direction. The declared dwells (255 and 210 units) and the corpus's three-cold-boot confirmation are untouched -- neither uses this draw log. METHOD.md gains the general form: when an instrument truncates, the surviving sample is not random, it is the first N, and what falls in the first N is itself a moving function of the thing being measured. A truncated view looks like a complete view of a smaller set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
acaacc36e0 |
re: the boot splash dwells are declared -- and wall clock is the wrong unit
The port asked for two wall-clock timestamps across the boot splashes. Measured, and the measurement's own result is that timestamps are not the invariant. The dwells are the bundles' own declared timelines: publisher t=0..255 = 4.250 s at 60 units/s, developer t=0..210 = 3.500 s. The corpus's independent screenshot timing over three cold boots gives 4.30/4.60/4.37 and 3.51/3.50/3.37 -- the developer agreeing to 1.1%, two of its three runs to 0.3%. A fresh no-input boot with a frame->wall-clock map puts the same two dwells at 5.10-5.61 s and 3.83-4.30 s, 15-20% longer than both the declared values and the corpus's runs, on the same disc and the same declared timeline. So the wall-clock dwell is an emulator-pacing artefact that varies run to run, and a port authoring seconds is authoring one run's pacing. Boundaries from the draw stream, read per quad: publisher glow frame 1, wordmark 6-119, three frames with NO sprite drawn, developer glows 123, wordmarks 140-209, intro video 216. The 3-frame gap replicates the earlier 4-frame measurement within the +-1 both are quantised to. New tool frame_clock.sh, and its limitation found by its own control: it resolves to one BUFFER FLUSH, not one frame. The capture writes through a C++ ofstream, so tail sees the log in bursts -- 69 of 125 samples showed no advance and the rest jumped 7-15 frames. Naive interpolation inside a burst made the apparent rate swing between 0.0164 and 0.0316 s/frame, which is the flush and not the guest. Frames 119 and 123 fall in one burst, so the inter-splash gap is not separable by this clock at all. Everything is quoted as brackets and the point estimates were withdrawn before being reported. palogo_anima never appears in the log and is NOT reported as undrawn: the developer bundle batches 7 elements into one draw and only the first two quads are logged. That is the trap that produced the eff3 false negative, so it is named rather than claimed. Also records the port's correction: ptcopyright has 105 instants with alpha >= 1 (t=139..243) against 105.89 units of span; I had quoted the rounded span. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
78328c5022 |
re: the top-level clock freezes at the settle point -- closing the 114-vs-120 gap
Measured in the title draw capture, re-read with the per-quad parser. GP_TITLE build 4 declares t = 0..269, about 120 presented frames at this run's pacing. The title dwell lasted ~1100. ptcopyright declares alpha >= 1 for 106 units (t=138..244) and is drawn for 1050 frames; ptlogo1 declares an exit at t=264 and is drawn for 1095. Both vanish within three frames of the dwell ending. So the top-level clock advances through the build-in, stops inside the settle window [160,236], and holds. The exit ramp is not played on a timer -- it plays when something makes the screen leave. That is ui-settle-time.md's decode seen from the other side and observed in the running game rather than inferred from the file. A nested record keeps looping on its own clock throughout. This closes the 114-vs-120 gap, and it was my arithmetic rather than a discrepancy in the decode. The 2.231 units/frame was regressed over BUILD-IN events -- the only stretch in which the top-level clock advances -- and applied to a period measured over the settled dwell, where that clock is frozen and only the plate's own record is running. Two different clocks. The declared 120 was never in doubt from the calibration-free dark-fraction test. The 51.158-frame period is now confirmed by a second independent estimator: autocorrelation returns lag 51 with clean harmonics at 102 and 154. Its FIRST version failed its control, returning 48 for a period known to be 51.158, because it indexed by sample position where the log's frame numbers have gaps. Recorded, because the failure is the reason the second version can be trusted. Not settled: the sweeps' period. The same validated estimator disagrees between two dwells of one screen -- 515 vs 452 frames for the same family -- and a 14% disagreement within one screen is not a period. The +0x08 field cannot settle it either, since ptloop01/ptloop02 have zero slack. Blocker recorded in CONTAINER-NOTES: a single A press on the title faults the guest. Three menu-capture attempts, two ending in register dumps of 223 MB and 519 MB, against three no-input runs in the same session that all completed. And a guest fault writes an UNBOUNDED register dump to stdout on a filesystem at 91%, so any scripted button press needs a size guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
cf91ad1dcc |
re: the forced-backdrop span -- 256 vs 211 is a bundle mismatch, and the hold
decides 55% of verdicts The port implemented the forced-backdrop rule and reported a discrepancy: palogo_eff0.prm at 256 opaque instants against this corpus's 211. There is no discrepancy. palogo_eff0.prm appears on BOTH splashes -- the publisher (entries 10, 13) runs to t=255, giving 256 instants; the developer (11, 14) runs to t=210, giving 211. Same definition, different bundle. The page now names the entries so it cannot recur. The definition, stated: the span is 0..=max keyframe time over every element in the build, and an element HOLDS its final pose past its own last keyframe -- which is what pose_at does, and which is decoded rather than assumed (a group holds at its last keyframe rather than looping; the declared +0x08 never falls short of the last keyframe, the slack being that hold). The port's instinct that the hold was load-bearing was right. Over the 130 keyless full-screen primitives with an opaque interval: * span = the header's declared +0x08 -> 0 verdicts change * span = the primitive's own last keyframe -> 72 change * elements GONE after their last keyframe -> 72 change So the hold decides 55% of verdicts -- and dropping it is REFUTED by a measured order. palogo_eff0.prm is a single keyframe at t=0: without the hold it is opaque for one instant, no other element is up yet, and the rule calls it free, against a game measured painting it first. Pinned by a new test that spells out the counterfactual rather than importing it. The verdicts that matter are convention-independent: pgloading_eff00.prm is FIRST under all four conventions and pteff00.prm FREE under all four. And the header length is interchangeable with the elements' maximum -- zero disagreements disc-wide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
cfcda5501c |
formats: a keyless primitive that would hide the screen is forced to paint first
Partly closes ui-prm-primitives.md's standing blocker, "where an UNMEASURED
primitive paints". Raised by the port: build_12/build_15 composite to solid
black at every instant of their declared life, because pgloading_eff00.prm -- a
full-screen opaque quad -- sorts last.
The rule is a constraint read off the file, not a preference: an element that
covers the screen and is fully opaque at some instant cannot paint above
anything visible at that instant. Where the elements visible during its opaque
span are ALL of them, its position is forced to first.
pgloading_eff00.prm is opaque for 39 instants and all 9 other elements are
visible inside that span -> forced first, 4/4 instances.
Two controls, both measured orders from the running game, and the rule has to
survive both:
* palogo_eff0.prm is measured painting FIRST -- opaque 211 instants, forced
below 6 of 6. It is NAMED like an overlay, so a name-based rule sorts it
wrong against a measured order. Occlusion gets it right.
* pteff00.prm is measured painting LAST -- opaque for 2 instants at its
screen's entry and exit, forced below only 3 of 23, so the constraint
permits it on top where it belongs.
Disc-wide: 80 instances forced first, 50 constrained but not forced, 0
unconstrained. The split runs almost exactly along the names -- every *base* is
forced, every *eff00* is not -- with three families crossing it, which is
exactly why the name is not the rule.
It also explains 36 builds the corpus had recorded as "coming out one colour"
with no cause: pzeff00.prm is forced first in 32 of 32 instances, so they were
wiped by our own sort rather than by the game.
The rule's real limit was found by its own disc-wide test failing. Applied to
any element it claimed 22 .t32 SPRITES must sort first against their own layer
keys -- pneff01.t32 (key 0xd850, #8 of 13) and pbfriendly.t32 (0x9230, #17 of
49). A sprite's ELEMENT alpha says nothing about whether its TEXTURE covers the
screen, so forced_backdrop is now restricted to untextured primitives, which is
also the only case derived_paint_order consults it for.
Reach stated: assumes straight alpha-over (blend mode is still open, and an
additive quad at alpha 255 would not occlude); it is a lower bound, not an
ordering; and there is no new oracle measurement -- both controls are prior
ones, and a loading screen is not reachable from the title path.
3 new disc tests; the 13 paint-order tests are green, including
the_derived_order_matches_the_measured_ones_up_to_ties.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
|
||
|
|
47faeaa7a9 |
re: RETRACT "the game never draws eff3" -- a batched draw merged two quads
The console draws all five title flashes. My claim that ptlogo_back2eff3 is never drawn was an instrument artefact, and I had reported it to the port with three alternative explanations "ruled out". A GPU draw can batch several quads -- indices=4 is one, indices=8 two, indices=24 six -- and the UI draw log dumps only the first 8 vertices. Taking min/max over a line's whole vertex list merges quads into one box. eff3 is batched with eff4, and because the wipe family is right-aligned, eff3 (788..1196) lies ENTIRELY INSIDE eff4 (447..1196). The union is exactly eff4's own extent, so the merged box matched eff4 to 1 px, eff3 vanished, and nothing looked wrong. Parsed per quad, all five fire in both title entries in the declared stagger: eff1 130-131, eff2 133, eff3 133-134, eff4 133-135, eff/eff5 134+, back2 136+; and 5953-5955 / 5955-5957 / 5957-5958 / 5957-5959 / 5958+ / 5962+ in entry 2. Frames 133 and 134 are t=60.1 and 62.3, inside eff3's declared t in (58,64). Also retracts "the developer splash is one composited quad" -- the same bug, which the port refuted by arithmetic first (a 259-tall box cannot contain three logos spanning y 164..585). It draws three logos and three glows as separate quads in one indices=24 call; the 525x259 was gamearts_eff merged with seta_eff. The 9-unit black hold is unaffected: those glows are the developer splash's first draw. The three "ruled out" explanations were all aimed at the wrong failure. In particular the invisible-draw check counted draws with NO geometry line, when the hiding place was draws with PARTIAL geometry. Refuting three wrong hypotheses is not evidence for a fourth, and a list of failure modes written by whoever built the instrument is the least likely to contain its blind spot. Recorded in METHOD.md, along with the tell that was present and explained away: a merged box carries the first quad's colour, which made one element's alpha read 255/127/254 on consecutive frames. New tool: tools/re-capture/quads_per_frame.py parses vertices in groups of four and warns when the logged quad count falls short of indices/4. Also guards a double-A-tap in ui_draw_capture.sh: the movie branch ignored that TARGET=menu had already tapped, so a run tapped A on the title at t=23s and again at t=27s on the transition; the guest faulted and Xenia dumped registers to stdout until the file reached 519 MB. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
1a2f4fdc2d |
re: the boot splash black gap is ~9 units, measured in draws not luminance
The port found its boot had no black frame between the publisher and developer splashes and authored 12 units by analogy with the menus' transition quad. On the boot path that analogy has nothing behind it -- palogo_eff0.prm is a single static keyframe, so the splash bundles declare no fade quad. I had agreed with the dismissal that hid the defect: told the residual was 0.03 s against a bound built from two measured ranges plus jitter slack, I said it said more about the bound than the game. The real gap was 0.2 s. Measured in the draw stream, which separates true black from a fade tail where luminance cannot: palogo_sqex is drawn to frame 125 at alpha 7, frames 126-129 submit NO sprite quad at all, and the developer fades in at frame 130 from alpha 34. Four presented frames, the only such run in the sequence. Converted with the disc as its own clock rather than a frame rate -- this run presented at 13.1 fps against 28 elsewhere -- palogo_sqex declares alpha >= 1 for 239.8 units and is drawn in 105 frames, giving 2.284 units per presented frame, which the title capture independently corroborates at 2.231. So the gap is ~9.1 units (0.152 s), against the 12 authored; +-1 frame is 6.9-11.4. And the true black is SHORTER, since both boundary frames still carry picture. Second finding: the developer splash is ONE composited 525x259 quad at the bounding box of its three declared logos, none of whose individual sizes is ever submitted. That is why an earlier pass reported "developer splash: 0 frames". Declaration sites ruled out: the splash bundles (no fade quad) and the top-level +0x08 (a family constant, 300/60, slack 12-226 units). The executable is NOT looked at and is named as the next place rather than claimed. Also answers the port's sweep question: +0x08 canNOT settle it, because ptloop01/ptloop02 have zero slack and a zero-slack record cannot distinguish "loops" from "runs once and stops". The oracle settles it for the TITLE -- the sweep oscillates over its whole range and resets hard to the same start, once in dwell 1 and twice in dwell 2, so it does not park. The MENU is unmeasured and stays open. Tooling: GRACE and NOTAP knobs for ui_draw_capture.sh. The script taps A on "the screen changed a lot", which is also true of a fading splash -- a first run tapped through the publisher and the developer never appeared. The instrument was perturbing what it measured. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
69bc4cd1cd |
re: the game never draws ptlogo_back2eff3 -- reproduced, with three explanations
ruled out
Last iteration I recorded eff3's absence as unexplained after withdrawing a bad
explanation for it. The previous capture survived on disk with 6907 frames, and
the attract loop returns to the title, so it contains a SECOND build-in at
frames 5942..7025. eff3 is absent there too.
Three alternative explanations tested and failed:
* sampling phase -- eff3 is non-zero for t in (58,64), SIX units, against a
2.23-unit step. A window wider than the step cannot be missed; frames 133
(t=60.1) and 134 (t=62.3) sit inside it and draw eff2 and eff4 instead.
* a draw the log cannot see -- exactly 2 draws per frame carry no geometry,
on all 932 settled title frames, always the same full-screen-triangle
shader, and present on frames where no wipe element is active.
* a bad position guess -- dropping position entirely, ZERO quads anywhere on
screen in either build-in window have a width within +-30 of 408. The width
spectrum jumps straight from 262 to 748.
Draw counts across both entries: eff1 4, eff2 3, eff3 0, eff4 6, against ~5
expected each. The four are a right-aligned wipe (938+258, 788+408, 447+749,
64+1133, all ending at x~1196) -- a left-growing reveal in four widths, of
which the game draws three.
Why is NOT established: nothing in eff3's element record differs from its
neighbours. Classified measured, not decoded, and the port is told that
dropping eff3 means authoring a behaviour I cannot derive from the file.
Two further corrections, both to my own earlier claims:
* "frame 107 is the title composited once" was an over-read. It binds NO
texture and only 4 of its 27 draws log geometry. The second title entry
has no such frame.
* the two build-ins are NOT frame-identical. I had that from a coincidentally
aligned pair of rows; aligned properly only 4 of 46 frames match. They are
the same animation at different sampling phases -- which is precisely what
makes the eff3 result robust.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
|
||
|
|
311bd16ad8 |
re: withdraw the eff3 explanation and the flash advice; add a calibration-free
test that refutes 105 Two claims shipped this morning are withdrawn, and the port had already acted on one of them. WITHDRAWN 1: "eff3 was never drawn because a 2-unit flash peak is sub-frame." eff3's alpha is non-zero for t in (58,64), and the capture's frames 133 and 134 sit at t = 60.0 and 62.2 -- squarely inside that window, with eff2 and eff4 both drawn in the same frames. It should have been submitted and was not. The absence is real and UNEXPLAINED; it is not sampling phase. WITHDRAWN 2: "a port drawing all five flashes shows more sweep than the console". No evidence behind it. The port checked against its own renderer and found it draws them sequentially at their declared times, never more than two at once -- which is exactly what frames 131-135 show the game doing. The pile-up worth warning about was the rest() bug, now fixed. Kept, at the port's request: a frame-by-frame comparison of the build-in WILL disagree about which flash lands in which frame -- 2 units per submitted frame against this run's 2.231 units per presented frame -- and neither side is wrong. Without that stated, the discrepancy reads as a port defect. Added, and stronger than the argument it replaces: a calibration-free test of 105 vs 120. The glow's draw is omitted when its alpha reaches zero, and the smallest alpha actually submitted across 807 drawn frames is 1, so the culling threshold is read off the data rather than assumed. Measured dark fraction 17.7% (173 of 980 settled frames); a 120-unit cycle with its declared 15-unit hold predicts 14.4%; a 105-unit cycle predicts 2.2%. 105 is out by 8x and would need a threshold of alpha 11 out of a peak of 80, while the capture contains submitted draws at alpha 1..12. No frame rate, no pacing factor, no wall clock. Also recorded: a regression of five build-in events against their declared times (residuals <=0.9 frames) recovers t=0 at frame 106.1 when the composite spike, not in the fit, is frame 107 -- and that same slope makes the glow's period imply a 114-unit cycle against a declared 120, which is unexplained. And the vertex-alpha identity holds for the glow but does NOT generalise: eff4 reads 255/127/254 on consecutive frames. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
d98c8214cc |
re: the title's build-in measured in the guest's draw stream -- the flashes are real
The settle-time decode was confirmed only against a SETTLED frame, which shows the end state is right and says nothing about whether the five flashes ever happen. This runs the oracle: a draw capture armed before the title exists, so the window contains the frames in which the screen is built. The flashes fire in a six-frame window and are absent from all 155 other sampled frames. `ptlogo_back2eff1` is drawn in exactly two frames at t = 54.0 against a decoded peak of t54-56; `ptlogo1` first appears at t = 42.2 against a decoded t42. Units-per-frame was taken from the GLOW's period alone, a different element, so the timings are not circular. The two holders are continuous from frame 134. The plate glow's quad carries a per-vertex colour whose alpha IS the element's fade alpha, so the ramp is read straight out of the guest: observed range 0..80 against a decoded peak of 80, exact and unfitted; period 51.158 presented frames over 20 cycle starts. Fitting the decoded ramp gives RMS 13.16 alpha levels against 38.18 for the same ramp REVERSED -- if the shape carried no information those would be equal, so the asymmetry is real and correctly directed. Further controls: symmetric triangle 15.73, flat 31.13. `ptlogo_back2eff3` was never drawn, and that is expected rather than a miss: a 2-unit flash peak is 0.85 of a presented frame, so catching one is a matter of phase. A port drawing all five every time shows more sweep than the console. METHOD.md gains the trap this cost: a 2D draw's identity is its vertex geometry, not its bound texture. These sprites sample shared pages, and matching texture dimensions produced a false negative (no flash is ever drawn) and a false positive (the intro movie's 640x360 YUV planes read as `ptbase2`) in the same pass. Also records the top-level restriction on the settle window, which the port raised and which is verified here: top-level [160,236] width 76, including the `ptloop` leaves [269,540] width 271 -- an instant past the end of every top-level element's timeline. Evidence committed as a derived per-frame series, not the 7 MB raw log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
4c74e579a0 |
re: 210 focus records pulse, not 2 -- and 8 of them fail in a way that looks right
The port censused focus-record alpha over its own export -- 34 elements, 2 varying, both `ptbtn00f` -- and concluded there is nothing to fix. That is correct and correctly scoped. This asks the same question of the whole disc. 1 130 focus records, 2 664 timed elements, 210 with a varying alpha. 202 have `rest()` returning the PEAK, the `ui-settle-time` pathology. By pak: PILOTLOG 116, MOVIE_THEATER 54, HANGAR_ARSENAL 30, LEADERBOARD 8, GP_TITLE 2. So the port's 2 is right because GP_TITLE has 2. The scope was load-bearing and was not stated as a limit -- "only 2 have a varying alpha" reads as a fact about the format and is a fact about one pak. The pathology is concentrated in exactly the screens a wider port reaches next. The 8 LEADERBOARD ones are the worse mode. `py_ranking_btn01f` swings 255->127->255 with no two adjacent keyframes equal, so `rest()` falls through to its longest-dwell rule and returns 244 -- neither the peak nor the trough. A glow stuck at its peak is visibly wrong; one stuck at 244 of a 127..255 range looks entirely plausible and nothing reports it. Verified rather than asserted: two hits dumped keyframe by keyframe, and a control on `ptbtn01f`, which is genuinely constant across its cycle and is correctly NOT flagged. `py_ranking_btn01f` also confirms the loop-length decode independently -- its ramp ends at t=90 inside a declared 120-unit cycle, holding bright for 30 units. Reach stated: 210 is a floor. Focus records are matched by the `Xf.rat` name rule, and elements with constant alpha but varying scale, rotation or position have the same problem and are not counted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
d4a08ad194 |
re: a nested record's +0x08 is its loop length -- the plate's period is 120, not 105
Answers the question the port agent asked: does the `PRESS A` plate's pulse group loop from its start, or hold at alpha 0 between cycles? It holds. A nested record is itself a RATC bundle with its own header, and that header's `+0x08` is the loop length -- the same field ui_header_time_disc already tests as an animation length at the top level. Its keyframes need not fill it, and the slack is a hold at the final pose. `ptbtn00f` is 105 units of ramp inside a 120-unit cycle, so the glow rests dark for 15 units between pulses. The five main-menu focus records fill their 120 exactly, which is what shows the slack belongs to this record rather than to the format. Disc-wide over 1 781 timed nested records: 92.3% declare exactly their last keyframe time, 7.7% declare more, and 0 declare less. That last row is the falsifier -- a cycle cannot restart before its own last pose -- and it never fires; the 7.7% is what keeps the reading from being an unfalsifiable relabelling of the keyframes. Falsification against the running game, using a pacing factor measured INDEPENDENTLY on the main menu's focus ring (declared 120 units, measured 2.177 s, factor 1.0885): to reach the corpus's four measurements of the plate pulse (2.12/2.19/2.34/2.31 s), a 105-unit period needs a factor of 1.211-1.337, which EXCLUDES the ring's; a 120-unit period needs 1.060-1.170, which CONTAINS it. Predicted 2.177 s against a measured 2.12-2.34. The two elements are in different bundles and were measured in separate runs; the only thing tying them together is that both declare 120. So the port should stop shipping 105. Its 123-vs-129 ambiguity straddled the right answer without containing it, and 129 only fitted because it was 105 + the exit_ramp_units constant it has since correctly deleted. Reach is stated: this says where a cycle ends, not which records cycle, and the TOP-level +0x08 is a different field left untouched -- every GP_TITLE entry declares 300 while its elements end at 244-269. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
7b5a4aa041 |
re: the paint-order tie-break costs one pixel, on one screen we do not ship
Closes the open half of Q3. `ui-paint-order-derived-check.md` bounded WHERE a wrong tie-break could show -- overlapping same-key pairs -- and said outright that nobody had measured how many change a pixel. At the instant the player sees, the answer is: at most 1 px at max channel difference 1, on the JAPANESE title only (`ptlogo2` x `ptlogo_tm`, 5 px of shared ink). Exactly 0 px on all five port screens. The earlier 24-pair bound was counted at `rest()`, and 10 of the title's 11 overlapping tied pairs are between `ptlogo_back2eff1`..`eff5` -- the five transient flashes from the settle-time finding, transparent on the settled screen. A tie between two invisible elements cannot cost a pixel. Not a knife-edge. Sweeping every keyframe time and every midpoint between keyframe times, the live-pair count is flat across the ENTIRE settle window: 1 on the EN title, 2 on the JP title, 0 on all four loading bundles -- whose tie is live only at t17..t33, during the build-in, which matters because their settle windows are narrow enough to deserve little trust otherwise. Controls: every entry reporting zero also swaps an overlapping DIFFERENT-key pair, which must and does move pixels (25 310 / 268 698 / ~765 000 px). Zeros are explained by shared-ink counts rather than asserted -- the `ptframe` pairs overlap by bounding box and share 0 px of ink. Entries 0/1/12/15 have NO live control and their zeros rest on keyframe data rather than a render; recorded as the weaker claim it is. Refutation attempt on the corpus's "24 overlapping pairs": it SURVIVES as a rest-pose count -- an independent recount reproduces entry 7's 16 exactly. What is overturned is its interpretation as the risk surface. `tie_break_pixel_cost` gains a settle-time case and an alpha/scale filter on its rect test; `tie_cost_over_time` is new. Also strips 611 bytes of captured cargo warnings from the head of the committed tie census. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
38b80302b7 |
re: the title's light arc is five transient flashes, not a tone or rotation error
Records the settle-time decode, and corrects two claims it overturns.
docs/re/structures/ui-settle-time.md is the finding: a settled screen is one
instant every element is posed at, the disc names it (the midpoint of the
longest keyframe-free interval), and the title's arc closes from 33.22 to
11.79 with the clipped-pixel count landing on the console's 1459 within 0.5%.
Includes the disc-wide reach -- only 30% of bundles have a window wide enough
to trust -- and a three-way figure.
Withdrawn in ui-rotation-implemented.md:
* "Flat. No minimum." was not a property of rotation. `at` posed LEAVES ONLY,
so the scan moved the light sweeps and never touched the top-level flashes.
* "our renderer does not draw ptlogo1/ptlogo2 at all" is wrong. Both are
drawn; the four elements the diagnostic named are kind-0x4 ghosts sharing
their template's name. Hiding the real ones makes the error WORSE by
+5.20 and +7.47.
* Its 10.92 baseline is not reproducible -- 14.07 at its own pre-change tag
and 14.07 today -- so the "1.7% better" verdict rests on an unrecorded
recipe. Flagged in title-residual-tone-vs-geometry.md too.
METHOD.md gains two traps: a shared CARGO_TARGET_DIR makes a `git worktree`
build silently replace the binary you run next (it cost three renders here,
caught only because a missing flag was a hard error rather than a wrong
number); and an aggregate computed per-element is not a state of the system.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
|
||
|
|
d0735d2c25 |
formats: a settled screen is one instant, not one hold per element
`Element::rest()` picks each element's last hold keyframe independently of every other element, so a composite built from it is not the screen at any moment in time -- it is a per-element maximum. For a transient that is exactly wrong: a two-frame flash's last hold IS the flash peak, so it burns forever. GP_TITLE build 4 is the case. `ptlogo_back2eff1`..`eff5` are five staggered two-frame flashes -- one light sweep drawn as five frames, all extinguished by t110 -- that `rest()` draws simultaneously and permanently. Five stacked white glows saturate the light arc behind the logo. The disc names the right instant: the midpoint of the longest interval containing no keyframe of any element. `UiBuild::settle_time()` and `settle_window()`; `screen render --settle` applies it and prints the window, whose width is how much the midpoint is worth. Predicted t=198 from [160,236] BEFORE scoring. Against the console capture, the arc band goes 33.22 -> 11.79 and pixels at the clipping level 8581 -> 1452, where the console has 1459 -- an unfitted statistic. Whole frame 14.07 -> 12.06. Controls at t=100 and t=358 are far worse, and a hand-picked visibility list reaches the identical numbers. `ComposeOptions::at` now poses every element rather than leaves only, which is why the earlier rotation pose scan was flat: it moved the sweeps and never touched the top-level flashes. `at = None` is byte-identical (cmp), the pre-rotation tag renders identically at rest, and the 13 paint-order tests plus the keyframe/focus/opt-link disc tests are green. Also fixes the diagnostic that caused a wrong finding to be sent to the port agent: `not drawn` listed bare names, and a kind-0x4 ghost carries its template's name, so four ghosts printed as `ptlogo1.t32`/`ptlogo2.t32` and read as "the logo is missing". It now prints index, name and reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
37af2055e3 |
formats: a scale-0 leaf must not claim the draw and blank its parent
Found by inspection while the disc tests ran. The leaf branch set its something-was-drawn flag unconditionally after calling blit, but blit returns early on a zero scale -- collapsed to nothing, not unset. So a scale-0 leaf would have been counted as drawn, its parent skipped, and the element blanked outright. pgloading_loop5 s leaf is scale (0,0), so this was live on all four loading screens, and scale-0 is one of the failures this corpus is already named for. Fixed by skipping a zero-scale leaf pose before it can claim the draw; the loading builds render afterwards at 4.0 percent non-black. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pdformats-pin-2026-08-29d |
||
|
|
81f42f9041 |
formats: teach the renderer to rotate (Option A) -- and report that it does not close the title
The human chose Option A: teach sylpheed-formats own renderer to draw rotation_deg so it and the port stay comparable and verify-screen keeps meaning someone is wrong. Three pieces, because rotation alone does nothing on the title. blit gains a rotated path that draws by inverse mapping over the rotated bounding box, turning about the pivot, whose absolute position is invariant under scale; zero rotation keeps the original forward-mapped path byte for byte so non-rotating screens cannot regress. compose draws a nested .rat leaf when the leaf carries geometry the parent does not, which is the sweeps case, but not as a blanket rule since a button s leaf duplicates its parent. And --at poses leaves at a keyframe time, because the sweeps hold off-screen at x=1521 so a resting composite omits them. A trap found the hard way: posing EVERYTHING at one global time is wrong, because a top-level group s final keyframes are its exit ramp and rest() deliberately stops before them. Posing the title at t=358 walked every parent into its exit and drove the disagreement from 10.92 to 61.74. So at poses leaves only. Controls: 0 and 360 degrees byte-identical to the unrotated path, 90 degrees swaps a 10x4 to 4x10, area conserved within 15 percent, centroid stays on the pivot. 116 lib tests pass, main_menu unchanged at 9.26. And the verification did not show what it was meant to, which is reported rather than buried: scanning the pose time against the title capture gives 10.73 to 11.17 against a 10.92 baseline -- flat, no minimum, best 1.7 percent. The whole-frame mean is dominated by the tone curve, and the renderer still does not draw ptlogo1/ptlogo2 at all, which is a far larger spatial gap than two translucent sweeps. So rotation is correct in isolation and no screen regressed, but whether it closes the port s 1.81 percent is not established here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
21a2c649e6 |
notes: build-reborn points at a source root that does not exist either
Found while checking what "Reborn" names. build-reborn line 15 is
SRC="${PROJECT_DIR:-/work}/Syplheed-Reborn" -- transposed letters -- and no such
directory exists; the workspace is at /work itself. It fails immediately with
cd: /work/Syplheed-Reborn: No such file or directory, so the documented way to
run the disc-gated tests is broken in this container.
Records the direct alternative, setting SYLPHEED_DISC by hand, which is what this
session has actually been doing.
This is the second wrapper here pointing at a source root that does not exist --
build-canary has the same defect and blocks the audio tap. Worth checking a
wrapper s SRC before trusting that a green or a failure came from your own code.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
|
||
|
|
ce5505872b |
handoff: deliver the tone-versus-geometry split on the title
The rotation decision needs a size, not just a direction. A per-level LUT fitted on a screen is the most general tone model possible, so whatever it cannot close is by construction spatial. Self-fitted, it closes 70.3 percent on the main menu -- the positive control, where the port measures 0.06 percent so geometry is right -- and only 32.0 percent on the title. At most a third of the title s disagreement is tone; at least two thirds is geometry. Also warns the port off a global tone correction: the curve does not transfer. Fitted on the title and applied to the menu it closes 29.7 percent; the other way round it makes the title 24 percent worse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
f2a74efe2d |
re: at least two thirds of the title s disagreement is geometry, not tone
Separates two confounded effects so the pending rotation decision has a number: how much would drawing the rotation actually buy. A per-level lookup table fitted on a screen is the most general tone model there is -- every render level mapped to whatever capture level minimises the error, no functional form assumed -- so whatever such a LUT cannot close is by construction not a per-level effect. Fitting on the screen itself therefore gives an upper bound on the tone share and a lower bound on the geometry share. Positive control: on the main menu, where the port measures 0.06 percent of pixels differing so geometry is essentially right, a self-fitted LUT closes 70.3 percent, from 9.26 to 2.75. The instrument can collapse a tone-dominated residual. Result: on the title the same self-fitted LUT closes only 32.0 percent, from 10.92 to 7.42. So at most a third of the title s disagreement is tone and at least two thirds is spatial -- content in the wrong place, which is where the rotation lives. The fitted LUT is generous to tone, so the geometry share is if anything larger. Also refutes the idea of a single transferable tone curve. Fitted on the title and applied to the menu it closes 29.7 percent; fitted on the menu and applied to the title it makes things 24 percent WORSE. A curve fitted on a dark flat screen is unconstrained at the bright end -- the menu s populated range is levels 5 to 204 with few bright pixels -- and extrapolating it onto the title s planet and wordmark actively harms. That extends the existing refutation of the single exponent: even a full per-level LUT fails to transfer, so a consumer must not carry a global tone correction. Reach: two screens, one capture each, and the title pairing is the same screen but not the same instant, so the ratio is what is claimed rather than the absolute level. Our render draws the sweeps parent record only, so the geometry share includes both the missing rotation and the missing leaf placement -- both closed by the same decision. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
1f9609c37f |
re: the keyframe block s +4 and +8 are angles used almost always as a 180 flip
Narrows a standing unexplained pair without claiming to decode it, and states precisely why it cannot be closed in this container. Census over every UI pak on the disc -- 2859 builds, 90347 keyframes, parents and nested leaves. +4 has 12 distinct values and +8 has 11, against 157 for the decoded rotation at +12. Per sprite-instance across 14241 of them, with +12 as a control because it is known to hold a real angle: +4 takes more than two distinct values on 7 instances, +8 on 99, and +12 on 396. So +4 is in practice a two-state field whose state is 180 -- and for a screen-plane sprite a 180 degree rotation about an in-plane axis is a mirror. But they are not booleans. GP_TITLE entry 7 s ptlogo3a runs +4 = -72, -18, -4, -1 against +12 = -14, -4, -1, 0: the two decay to zero together with +4 roughly four to five times +12 at each keyframe. That is a coupled two-axis settle and the strongest support the disc offers for the three-axis reading. So the readings reconcile -- the field is an angle whose overwhelmingly common use is the 180 degree special case. The reach is the important half. All six non-zero +4/+8 keyframes in GP_TITLE are in entry 7, the Japanese title, which has no oracle capture and which MISSION scopes out as localisation beyond English. The five English screens that do have captures carry +4 = +8 = 0 on every keyframe, so they never exercise the fields. The paks that use them heavily, GP_READY_ROOM at 4686 and GP_DIALOG at 1058, are also out of scope and GP_READY_ROOM is a recorded no-go. So this is untestable against every oracle the project holds rather than merely unfinished. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
258e1e63f7 |
method: an insensitive observable fails twice, and the second way sends you hunting
The port checked my pivot claim rather than taking it and found the nuance: the sprite is odd-width, 399, so its true centre is 199.5 against a declared pivot of 200. Half a pixel, far inside the 0.70 and 0.48 px agreement, so it changes nothing -- but "the pivot IS the centre" is the kind of sentence somebody leans on for a sub-pixel claim later, so the page now says it is the centre rounded up. And records the general form of the two bugs this exchange produced, which share one cause. First, a leaf-composition rule was checked against alpha, which moves about 0.3 levels per keyframe unit, so a one-keyframe association error barely shifted it and the rule looked confirmed -- while the same span moved x by 1560 px. Second, fitting t from that same alpha manufactured an 11.5 px position residual that did not exist and sent the consumer hunting a pivot rule to explain it, because one byte of alpha quantisation is worth 1.5 to 1.9 keyframe units. Solve on the fastest-moving field and check the slow one, never the reverse. The second failure is the more expensive: failing to falsify leaves you falsely reassured, but inventing a residual sends you looking for a mechanism. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
f15c3f516d |
re: the 11.5 px residual was the alpha-only fit s resolution, and it closes
Left open earlier as do-not-fit. Closed by adding observables rather than tuning a parameter. The draw s vertex buffer carries positions and colours at the same instant, so all four quantities must agree on one t. Solving independently: quad A x gives 357.88, quad B x 357.58, quad A alpha 355.75, quad B alpha 354.09. The alphas are about 50x less precise per unit of time because alpha is a byte changing by only 0.27 to 0.33 levels per keyframe unit, so a single level of quantisation is worth 1.5 to 1.9 units, which at 4 px per unit is 6 to 8 px of sweep. The 11.5 px was that. At the position-derived t = 357.7 every observable lands: centres within 0.70 and 0.48 px, both alphas inside one level, and the parent alpha is 0 throughout. And there is no pivot correction to find -- the leaf pivot is (200,90) against a 399x180 sprite, so the pivot is the sprite centre and rotation displaces it by nothing. The methodological point is the one this exchange started with, inverted. Earlier a rule looked confirmed because it was checked against alpha, the insensitive field. Here the same insensitivity manufactured an apparent 11.5 px error. The insensitive quantity does not merely fail to falsify, it invents residuals. Solve on the fastest-moving field and check the slow one, never the reverse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
a1adb17d35 |
re: 125 percent is not the only odd scale, and ptlogo_eff2 is a pop not a steady state
Refutes a DECISIONS claim and decodes the element the port withheld on my say-so. The claim that title_jp s ptlogo_eff2 at 125 percent is the single drawn element in the whole export at a scale that is not a whole multiple of 100 percent rested on a census of PARENTS only. Opening the 45 leaves as well finds thirteen distinct non-whole-multiple scales -- 75, 96, 99, 101, 103, 112, 125, 150, 204x208, 210x220, 250, and the 75x100 / 96x100 / 99x100 pairs -- with 125 among the rarest at two occurrences. ptlogo1 and ptlogo2 carry 101/103/112 on the ENGLISH title too, so it is not a Japanese-build peculiarity. The claim s real content was "the only one the port draws", which is about the export s element set rather than the disc. And ptlogo_eff2 is decoded. The 125 percent lasts 57 units, about 0.95 s -- a scale-0 to 125 to scale-0 flash between t=50 and t=107, a transient rather than a steady state, which is why it looked anomalous in a census of resting poses. The leaf draws at 100 percent as two superimposed copies of the same sprite at alpha 160 and 80, each rotating a full 360 degrees over 960 units: a slow double-layered spin, 16 s per revolution. This is exactly the case the ptloop rule could not separate. There the parent had expired so leaf-wins and parent-ignored were indistinguishable; here the parent carries real geometry including a scale that reaches zero twice. If parent scale gates the leaf the spin is a 0.95 s flash; if the leaf runs on its own timeline it spins for 16 s. Nothing on the disc chooses between them, and title_jp has no oracle capture, so it is undecodable in this container -- the port is right to withhold it, and the Japanese-locale capture MISSION has parked would settle it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
5da1a837fa |
re: the port s leaf x = -324 is the OLD keyframe association, not a geometry gap
They implemented the leaf rule and reported the leaf top-left at x about -324 at t=355, off-screen left, against 781 here, and asked how a rotated 600-percent quad s declared pos relates to its drawn centre. It is not a geometry question. Their stated pairing is t=150 at x=-639, t=540 at x=-39. On the disc the poses -639 / -39 / 1521 carry times 0 / 150 / 540. Their pairing gives each pose the NEXT pose s time -- exactly the association ui-keyframe-record-layout.md refuted and HANDOFF carries a red banner about. Feeding their pairing into the same interpolation reproduces -324 to the digit. With the corrected association t=355 gives top-left 781 and centre 980.5 for the 399-wide sprite, against 992.0 measured off the capture. So the leaf path still carries the pre-fix association although the top-level parser was corrected: a leaf is parse_build on a sub-slice, so anything reading leaves through a separate path can still be shifted. And the reason it looked confirmed is worth keeping: alpha at t=355 sits inside a long segment where a one-keyframe shift barely moves it, while x sweeps 1560 px over the same span. The rule matched on the insensitive quantity and was wrong on the sensitive one -- check a new interpretation against the fastest-moving field, not the one that happens to agree. The residual 11.5 px between 980.5 and 992.0 is left open rather than fitted; a rotation about a declared pivot rather than the centre would displace by roughly that much and nothing here measures it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
14a7897c8f |
handoff: deliver the leaf-versus-parent alpha rule
The port asked for the composition rule and declined to guess it, which was the right call. Delivered on the page they read: draw the leaf on its own timeline, do not multiply the parent s alpha in, with the refutation stated -- multiplying predicts zero at the observed time and the sweeps would be invisible. Keeps the two limits that matter to a consumer. It is not a universal precedence rule: here the parent is a container with no sprite, while for a button the leaf duplicates the parent and the parent wins, so the discriminator is which record carries the geometry. And because every observation has parent alpha zero, leaf wins is not separated from parent ignored because it draws nothing. Also flags their title_jp ptlogo_eff2 lead as untested by me, with the reason it is worth checking: if its two-element leaf carries the geometry the same way, the 125 percent scale may be the parent s. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
2f1c561d76 |
re: a nested .rat leaf animates on its own timeline; parent alpha does not multiply
Answers the port s question, which they refused to guess at: they emit both a parent record and its nested leaf, each with an alpha ramp over a different span, and would not draw the leaf without the composition rule. The per-draw capture records vertex colours, and on the title s ptloop draw they are C3FFFFFF and B6FFFFFF -- alpha 195 and 182, not 255 -- so the composed alpha is observable. Fitting ONLY the two alphas against the two leaf ramps gives one consistent time, t = 355, where leaf A is 194.8 against an observed 195 and leaf B is 182.2 against 182. The parent has expired there: it returns to 0 at t=250 and a group holds at its last keyframe. So leaf times parent over 255 predicts zero for both quads and the sweeps would be invisible. They are drawn. The position check was predicted rather than fitted: nothing about x entered the fit, and the same t=355 places the quad centres at 981 and 478 against 992.0 and 467.2 measured off the capture -- within about 11 px on 400-px quads travelling 1560 and 1950 px. Four quantities from two differently-shaped ramps agree on one time. The rule: a leaf carrying geometry animates on its own timeline and the parent s alpha does not gate it. For these records the parent is a container with no sprite. Reach stated: one draw, one capture, one element pair, and specifically the case where the parent carries no geometry. The opposite case is already recorded -- for a button a base record s leaf duplicates the parent and the parent wins -- so the discriminator is which record carries the geometry, not a fixed precedence. And because every observation here has parent alpha 0, "the leaf wins" is not separated from "the parent is ignored because it draws nothing"; a capture during t=100 to 238 would separate them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
5a4428ec67 |
handoff: the port s title residual is the rotated ptloop quads, not a blend mode
The port asked whether a blend field exists and whether _eff layers draw additively, naming it as their largest remaining oracle gap. Both answers are no and the cause is already decoded. Additive blending is refuted specifically: T8aD +0x04 bit 0x02 as an additive selector was tested and every measure worsens against the capture. The export carries no blend field because none has been found -- the per-draw capture records primitive type, index count, shader hashes, texture bindings and vertex attribute 0, but no RB_BLENDCONTROL, and reading real blend state needs a Canary change that is blocked here. One of their three eliminations is overturned. They ruled out the ptloop sweeps as 399x180 at (441,270) with keyframes holding position constant -- that is the unscaled, unrotated geometry. Measured off a GPU draw capture, the live title submits two rotated quads: ptloop01 at 400x1076 rotated +30.26 degrees centred (992,359), and ptloop02 at 400x1444 rotated -45.28 degrees centred (467,360), the scales being 600 and 800 percent. Two quads at x about 467 and 992, leaning opposite ways, is exactly their signature of darker centre-left and brighter right nearly cancelling. Our own renderer shows the same residual from the same cause, tiles running -38.6 then +33.8 across the band, so it is a shared decode gap rather than a defect in their compositor. The rotation itself is decoded: keyframe block +12, degrees, clockwise-positive, confirmed against a framebuffer capture. So their biggest oracle gap and the rotation question they raised for the human are the same item. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
3bd1a61ea9 |
re: S00A is unreachable here -- the new-game path crashes on a cache it cannot build
Drive 4 worked. With focus detected and the detector validated live against a known transition (NEW GAME, down, LOAD GAME -- CONTROL PASSED), the drive navigated and pressed through, and every step confirms against a committed capture: main menu +0.999, newgame-difficulty +0.999, newgame-selectdata-crash +0.997. Then the guest throws, PC 0x82307128 times 349, and no S00A voice stream ever decodes. It is on the new-game path rather than the boot: the log order is ADV attract, then BGM_103 menu music, then the throw. So it is not the ~100 s boot throw that title-crash-stl-tree.md documents. That page attributes the throw to an incomplete on-disc cache and Q4 s note cites it for this crash. The attribution survives, but not through the container the page names. aab216c3 is complete here at 7 files. The line immediately before the exception resolves \1b556564\9\00c8dcd, and 1b556564 holds exactly one file plus a stray 1b556564900c8dcd.tmp -- precisely the page s run C, partially rebuilt, which throws. So the new-game path builds a different cache container from the title path and that one is incomplete. The page s remedy does not transfer. It restores a previously complete cache, and no complete 1b556564 has ever existed here: the game crashes while building it, leaving the .tmp, so the cache cannot complete and re-running does not escape it. Its own run B shows a fully cold cache throws as well, so deleting does not help. Consequence recorded: the centre-channel voice result stays resting on ADV alone, because the corroborating asset is behind a crash that is outside menu-port scope. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
c5fe6a460c |
re: initial focus is TUTORIAL x3 / NEW GAME x3, and never anything else
Two more data points for the Q5 instability, from today s drives. A run that pressed A with no d-pad movement ended in a tutorial mission, correlating +0.960 with the committed capture, so that boot opened on TUTORIAL. A later boot read NEW GAME from a focus detector on the first menu frame. Six boots on the same harness now: TUTORIAL three times, NEW GAME three times, and no other item ever observed. The distribution is not uniform over the five buttons -- only these two occur -- which is a real constraint on whatever selects initial focus and something an explanation will have to account for. Also records in METHOD a bug that cost a seven-minute driven boot: a value was clamped for readability BEFORE the comparison that used it. A focus detector printed a degenerate margin, so it was capped at 999; the cap ran before the vote-sorting step, two different votes compared equal, the stable sort kept the wrong one, and a correct NEW GAME became an out-of-range index and a refusal. The measurement was right throughout -- a cosmetic fix changed a decision. Clamp at the point of display, never upstream of a comparison. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
8b4dcccb67 |
handoff: BGM_103 as the menu music now has a runtime leg
The port asked for this to have its own line rather than sitting inside a drive report, and they are right -- it is a third independent confirmation of a claim that the port authors from. The claim rested on GamePart_Title s sub_821C5580 playing cue 1103 (static code) and on the bank s two declared wave sizes matching what an XMA probe saw (disc census). On a driven boot, BGM_103 s two waves were handed to the XMA decoder at the moment the main menu appeared -- observed being decoded on arrival at the screen, rather than inferred from a table or matched by size afterwards. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
b7930e6616 |
re: the S00A drive failed -- initial menu focus was TUTORIAL, not NEW GAME
Reported as a route finding rather than retried silently, because the cause is a defect in shared harness tooling that will bite the next drive. Drive 1 sat 396 s reporting other with two spurious menu hits while the guest was healthy and decoding audio throughout. screen_id.py thresholds on green and only returns title once the PRESS A plate has faded in, so it cannot see the plate-less build-4 title this corpus documents. It also calls difficulty-screen a menu. Both reproduce on committed frames, and newgame_path.sh, nav_probe.sh and boot_menu.sh all gate on it. Drive 2, with a classifier controlled 6/6, reached title_plate at t=398 s and drove on -- into a TUTORIAL MISSION. The screen 60 s after the last A correlates +0.960 with the committed tutorial-mission-reached-then-crash.png, and no S00A voice stream ever decoded. newgame_path.sh s header assumes NEW GAME is the first item so no d-pad movement is needed; that contradicts this corpus s own Q5 result, where four boots gave TUTORIAL, TUTORIAL, NEW GAME, NEW GAME. Incidental corroboration: BGM_103 s two waves decoded on reaching the menu, an independent runtime confirmation of the menu-music claim that HANDOFF rests on static code and a disc census. What is needed is a focus DETECTOR, and wrap-around means counting presses cannot substitute -- up from the first item goes to the last. I do not have one: a per-row brightness statistic failed its control, picking NEW GAME on the capture whose filename says OPTIONS. Also records a refutation attempt on the port s focus identification that FAILED. Differencing the two captures and binning by row appeared to show NEW GAME and EXTRAS changing, contradicting them. That was my error -- I placed row bands as rest_y plus or minus 24, treating the resting position as a band centre. The offset-independent check settles it: the changed bands are 254.9 design-y apart against a button pitch of 80, so the two focused buttons are 3 apart and not 4 -- NEW GAME to OPTIONS. Their identification stands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
2a7ac9f859 |
re: the tone curve s single exponent is refuted above render 40
Raised by the port and independently reproduced here before being adopted, since adopting their claims unchecked has misfired twice this session. Binning matched pixels by render level rather than fitting a scalar, the implied exponent falls monotonically and crosses 1.0: render 16 23 31 39 47 64 port (all) 1.26 1.18 1.10 1.03 0.93 0.85 mine (flat) 1.303 1.347 1.128 0.912 0.935 1.003 Below the crossing the capture is darker than the render, which is what the page measured; above it the capture is brighter. A single exponent cannot express a curve that crosses unity, so the model is valid only in the darks -- which is exactly the reach the page already stated. The reach line was not a hedge, it was the finding. Where the two disagree is recorded and not resolved: the crossing is about 44 by their binning and 35 to 40 by mine, and the darks read 1.18-1.26 theirs, 1.30-1.35 mine, 1.49 for the page s original patch fit. Three estimators on three populations, all agreeing on direction and on gamma above 1 in the darks. A confound in my own reproduction is stated rather than left implicit: whole-image correlation is only 0.594 because the committed capture and the default render differ in focus state, which the port measured as 74.1 percent of differing pixels. My bins include that mismatch, so they are not a clean second opinion. And a 1280x720 render against a 1279x675 capture needs a resample, which is why the fit is restricted to flat-neighbourhood pixels. METHOD gains the general form: a stated reach is a boundary rather than a hedge, and the fix was printing the curve instead of a scalar, because a scalar hides its own domain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
fbe4ba5526 |
method: screen_id.py cannot see a plate-less title, and calls DIFFICULTY a menu
Both reproduce on committed reference frames, so this is a defect in a shared harness tool rather than a one-run anomaly: live-title-build4-no-plate.png -> other (should be title) live-title-press-a.png -> title difficulty-screen.png -> menu (is not the main menu) It thresholds on green -- 0.0009 with the PRESS A plate against 0.0002 without -- so it only recognises a title once the plate has faded in. This corpus s own finding is that the boot title shows build 4 FIRST, plate-less, for about 2.25 s, which means any harness waiting for `title` from it can sit through a visible title and report nothing. That is what happened on an S00A drive here: 396 s of `other` with two spurious `menu` hits, on a run whose audio proved the guest was healthy throughout. newgame_path.sh, nav_probe.sh and boot_menu.sh all gate on it. The zncc-against-committed-frames classifier used for the settle-time screen log has neither defect, controlling 6/6 with both movie frames and difficulty-screen as negatives -- but only at a 0.85 threshold. At 0.60 it also called difficulty-screen a menu at 0.632, so the threshold is doing real work and has to be controlled rather than chosen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
a13dd42c2d |
re: stripping additive padding is exact -- controlled, and only one side needs it
The page recorded the substituted-versus-additive distinction as unverified, with the port controlling it. It passes. A real music and SFX bed of 137.37 s, itself carrying 454 genuine zero runs -- which is what makes it an honest control -- had 1149 holes inserted at 8.37 gaps/s to +9.9 percent length, matching the measured ALSA profile, then stripped: original vs itself (ceiling) r 1.000 lag 0.0 s margin +0.141 padded vs original r 0.436 lag -12.2 margin +0.006 stripped vs original r 1.000 lag 0.0 margin +0.142 Two things beyond the yes. It runs the inference forwards: padding at this profile puts correlation squarely in the known-absent regime on a file whose contents are controlled, so the earlier captures were unusable for the reason claimed rather than for some other one -- until now that was reasoning backwards from a failure to a cause. And only one side needs stripping, since the stripped capture matches the UNSTRIPPED source at the ceiling, so a capture needs no preprocessing before being handed over and there is no shared step to get out of sync on. The danger is recorded as the part to repeat: stripping removes genuine silence too and cannot tell the two apart, so it is exact on additive ALSA padding and vandalism on a PulseAudio monitor capture where the silence replaced real audio. Running it on the wrong artefact would look like it worked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
9e66d6a82c |
handoff: deliver the centre-channel result and the S00A capture that is not taken
Records the port s measurement on the page they read, because it closes the last open question and my own pages carried the hypothesis it settles. Keeps all three limits as they stated them, including that streams 2 and 3 are indistinguishable to the instrument so no selection rule is vindicated, and that the one-of-three-streams warning stands with its character changed rather than its colour. Also names the capture that would strengthen it most and says plainly that I have not taken it: S00A rather than a longer ADV, why it is structurally different, and what it costs -- a driven rendered run, so no --gpu=null and the additive padding comes with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
5fb64f3d31 |
re: the voice dialogue is in the CENTRE channel -- measured, not inferred
The 5.1 reading of a voice cue s three concurrent streams was recorded here as a hypothesis with counter-evidence attached. It is now answered, by the port fitting the disc s decoded streams against a clean capture of the game s own 6-channel output, with the instrument controlled first (known-present margin +0.248, known-absent +0.005). Speech-band margins put streams 2 and 3 at +0.305 and +0.307 on FC, r = 0.989, above the known-present control, while stream 1 sits in the noise on every channel. The low band mirrors it exactly: the movie bed at 0.76 to 0.84 on the four corners against 0.32 on FC. Dialogue in the centre, bed in the corners. The hypothesis was right for a reason the file could never have supplied. ChannelMask reads 0x0002 on all three streams, so the header is not merely unhelpful, it is actively misleading -- refusing to call it 5.1 from the header was correct, and the oracle answered what the header could not. Three limits recorded as the measurer stated them: streams 2 and 3 are indistinguishable to this instrument, so no rule for choosing between them is vindicated; the one-of-three-streams warning stands, since nothing says what streams 1 and 3 contribute; and the reach is 59.7 s of a 137 s movie, one run, one asset. Also records the capture that would strengthen it most and why it is not taken: S00A rather than a longer ADV, because its second full-length stream is digital silence where ADV s is a 0.60x copy, so a structurally different movie would agree. Reaching it needs a driven rendered run -- S00A starts about 4.5 s after A on the save slot -- so it cannot use --gpu=null and will carry the additive padding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
6f25a6a0d0 |
re: substituted silence destroys information, additive padding does not
The distinction is the port s and it is sharper than the framing this page had. PulseAudio s monitor SUBSTITUTES: audio that existed is replaced by silence to keep the wall clock, so information is destroyed and deleting the holes only compresses time unevenly. Xenia s padding is ADDITIVE: the silence is inserted between samples the guest emitted, so nothing is lost and every real sample is present and in order. So stripping all-channel-zero runs from an ALSA-tee capture is exact rather than a repair, which means even the 0.70x rendered capture at 9.98 percent padding is usable for correlation, where none of the PulseAudio-monitor captures ever were however they were tuned. Recorded as unverified: the port is controlling it by padding a known source to match and checking the stripped result correlates back. Consequence for check-capture recorded too: its silence and gap-rate rule was built when only damage existed and cannot distinguish genuine emulator padding from capture damage, so a FAIL on an ALSA-tee capture is a statement about the recording path rather than the file s usability. Also promotes the runaway guard to a first-class CONTAINER-NOTES entry at the port s request -- 7.34 GB in 50 seconds at about 250x real time is not a footnote -- and adds --gpu=null there, which is what takes the guest from 0.70x to 0.96x and stops the padding, with its two caveats: no video for provenance, and runs die at about 70 s with PM4_DRAW_INDX failures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
4f77ab26d7 |
re: --gpu=null gives a clean audio capture -- 0.31 percent silence, 0.01 gaps/s
The residual padding in the ALSA tee was the guest running at 0.70x real time, and the dominant load is llvmpipe software rendering -- which an audio capture does not need at all. PulseAudio monitor, xenia default 39.3 percent silence 30.5 gaps/s ALSA tee to paced slave, rendered 9.98 percent silence 8.37 gaps/s ALSA tee to paced slave, --gpu=null 0.31 percent silence 0.01 gaps/s One gap in 67.7 s. Six distinct channels, no duplicates, sensible peaks. For scale the port s genuine music bed control measures 1.1 percent silence at 3.3 gaps/s, so this capture is cleaner than their known-good reference. Control that the run is still comparable: ADV s three XMA contexts appear in the --gpu=null log, so the movie s voice is decoding exactly as in a rendered boot. That is also better provenance for an audio question than screenshots were, since it evidences the thing being recorded rather than what was on screen. Records the full working recipe. Caveat: --gpu=null means no video, so screen-based provenance is unavailable and it is only appropriate when the question is about audio. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
eaa578421f |
notes: point the container page at the working audio-capture route
The entry already warned that run-canary is silent twice over, but stopped there -- and fixing only the mutes still yields an unfaithful capture, because a PulseAudio null sink s monitor is wall-clocked and invents silence when the client is late. Adds the pointer to the ALSA file-tee page, with the two things a reader needs before starting: the slave must pace or the tee free-runs at about 250x real time, and a size guard is not optional. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
371cd0d610 |
re: the ALSA tee measured on Canary -- faithful now, but the guest still pads
150 s boot with --apu=alsa --mute=false, tee in front of the paced pulse slave. PulseAudio monitor, default 5.3 ms 39.3 percent silence 30.5 gaps/s PulseAudio monitor, 200 ms 15.6 percent silence 3.5 gaps/s PulseAudio monitor, 500 ms 50.1 percent silence 1.3 gaps/s ALSA tee to paced pulse slave 9.98 percent silence 8.37 gaps/s 106.2 s captured over about 151 s of wall clock, i.e. 0.70x real time: the file is SHORT rather than gap-riddled, which is the intended trade. Six distinct channels, no duplicates, sensible peaks. A format trap worth recording: xenia s ALSA driver is SND_PCM_FORMAT_FLOAT_LE and its log confirms 6 channels, so the raw tee is float32 and not s16. Reading it as s16 yields a plausible-looking file whose giveaway is peaks alternating exactly -0.00 / -4.82 across channels -- the two halves of each float landing in alternate channels. I measured it wrongly that way first. The residual 10 percent silence is not removed, but its meaning has changed. It is no longer invented by PulseAudio s monitor; the tee records exactly what Xenia wrote, and Xenia wrote silence, because its writer thread pads whenever the guest has not filled the ring. So the capture is faithful -- every sample in it is a sample the emulator emitted -- while the emulator is still padding, because the guest runs at about 0.7x real time here. No capture method can remove that. So this is a 3.9x improvement in silence and a change of attribution, not a clean capture. At 9.98 percent and 8.37 gaps/s it sits right on the port s fail bar, and should not be treated as an oracle without saying which side of the line it fell on. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
3d5b5bae13 |
re: capture audio through an ALSA file tee, not a PulseAudio monitor
The human identified that both agents were fighting the wrong subsystem, and
testing it here confirms the diagnosis and finds the limit.
A PulseAudio null sink s MONITOR is sampled on a wall clock: when the client is
late PulseAudio does not wait, it emits silence to keep its own timeline. So the
39.3 percent silence in the take-2 capture was never audio that went missing, it
was silence PulseAudio invented -- which is why PULSE_LATENCY_MSEC gave a
non-monotonic curve and never won. The instrument was wrong, not mistuned.
ALSA s file plugin has no clock; it tees exactly what the client writes, so a
slow producer yields a shorter file rather than a gap-riddled one. Control with
six distinct tones: 12.000 s against a 12.000 s source, 0.00 percent silence,
zero gaps, no duplicate channels. Channel order comes out as ALSA s
FL FR BL BR FC LFE rather than WAV s FL FR FC LFE BL BR -- deterministic and
invertible, not data loss.
Three configuration traps recorded in the order they bite: ALSA_CONFIG_PATH
replaces the whole config so the stock one must be included; but WITH that
include a pcm.!default override silently does not take, in either the inline or
the alias form, so the slave must be declared with an inline plugin type and no
include; and a pipe to head SIGPIPEs the producer before it writes, which looks
exactly like a broken config.
And the limit the proposer honestly flagged, now measured: a bare file tee is not
enough for Xenia, because its ALSA writer thread pads silence whenever the ring
buffer is empty (alsa_audio_driver.cc:359). Against a device that never blocks it
free-ran at about 250x real time -- 7.34 GB, 12746 s of nominal audio, in 50 s of
wall clock, nearly all driver-generated silence. Killed and deleted; it would
have filled the disk.
The configuration that satisfies both constraints is a tee in FRONT of a paced
slave: type file with slave.pcm { type pulse }. The file plugin captures what the
client writes and the slave supplies the clock, so the wall-clock silence
insertion happens downstream of the capture point. Control through that exact
config: 12.000 s, 0.00 percent silence, zero gaps.
Consequences for verification: short file becomes the failure mode, so a capture
check needs an expected-duration test alongside silence and gap rate, and a
runaway guard is not optional.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
|
||
|
|
e8afbc8632 |
re: the buffer/dropout relationship is not monotonic, and a gap-rate bar misses it
Extends the latency finding with a third point, and it changes the advice. xenia default (~5.3 ms) 347.5 s 39.3 percent silence 30.5 gaps/s 3.94 ms median PULSE_LATENCY_MSEC=200 88.0 s 15.6 percent silence 3.5 gaps/s 37.33 ms median PULSE_LATENCY_MSEC=500 87.9 s 50.1 percent silence 1.3 gaps/s 346.67 ms median 200 ms is 2.5x better than the default; 500 ms is worse than either. Raising the buffer keeps cutting the gap RATE while total silence bottoms out at 200 ms and then doubles, because an over-large buffer starves in a few enormous holes rather than many small ones. That is also a warning about the metric. The port s check-capture bar is 20 gaps/s, derived from sound controls -- starved 32.9, genuine music bed 3.3, voice track 0.03. The 500 ms file scores 1.3 gaps/s, better than a real music bed, while being 50 percent silence: a gap-rate test alone would pass the worst capture of the three. It needs a total-silence companion. Same shape as the defect that made a per-channel level table useless -- one number that cannot see the failure mode next door. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
b56d0e1fb6 |
re: the capture dropouts are largely a client-buffer size, not the guest running slow
Withdraws my conclusion that the monitor-sink capture route cannot be fixed by configuration, and with it the claim that only an in-emulator tap would work. The container has no audio hardware -- no /proc/asound/cards, no /dev/snd, no asound.conf -- so PulseAudio s stock default.pa module-always-sink supplies a null sink, whose whole purpose is to exist when there is no device. A null sink has no hardware clock: it is timer-driven, and anything the client fails to write in time becomes silence in the monitor. That much was right. What was wrong was inferring from it that the holes mean the guest runs below real time. The alternative was never tested: xenia asks SDL for channel_samples_ = 256, which is 5.33 ms at 6 channels, and daemon.conf here is stock with no fragment tuning. PULSE_LATENCY_MSEC overrides what SDL s PulseAudio backend requests. Measured, same title and sink and parec invocation: xenia default (~5.3 ms) 347.5 s 39.3 percent silence 30.5 gaps/s PULSE_LATENCY_MSEC=200 88.0 s 15.6 percent silence 3.5 gaps/s An 8.7x reduction from one environment variable. Against the port s controls -- starved 32.9 gaps/s, genuine music bed 3.3, voice track 0.03, bar at 20 -- the default is squarely starved and 200 ms lands at the level of real content. Not yet a clean bill of health: the runs are not like-for-like at 88 s against 347 s, and the short one covers the splash logos where silence is real. What is established is direction and scale. The consequence that matters is that the capture route should be retried at raised latency before anyone spends a session on a Canary rebuild. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
55cdcac61b |
handoff: tell the port the settle-time numbers carry an unmeasured real-time factor
The pulse-period argument for trusting the settle-time run is withdrawn, and the port authors from those numbers so it belongs on the page they read. The plate delay survives because it agrees with three independent prior readings; the menu build-in and B-to-title are anchored by nothing, so a few per cent of emulator slowdown sits inside them undetected. That is a second, independent reason to treat those two as provisional beyond their being one-run figures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
02a99bc3e9 |
re: withdraw the pulse-period argument, and the shared WAV declares itself empty
Two corrections to my own recent work, both prompted by the port checking it. First, the settle-time page argued the plate delay discrepancy was an instrument artefact on two legs, and one of them is withdrawn. It said the plate pulse period acts as an internal clock for presentation rate and measured 2.369 s against the corpus s 2.3. That estimate rests on ONE interval between two distinct troughs at a 125 ms sample interval -- uncertainty 0.177 s or 6.7 percent -- and trough-picking on a noisy plateau is fragile enough that re-running it gives 2.628 s, because an adjacent local minimum had been counted as a separate trough. Against the corpus s 2.24 that is +17.3 percent, about two sigma. So the pulse period does not show the run at normal speed; it is too weak to show anything, and cannot resolve a real-time factor below about 7 percent. The conclusion survives on the other leg, which is the sound one: the content-measured 2.247 s agrees with three independent prior readings (2.13 / 2.132 / 2.138), and both its landmarks are sharp content transitions rather than a trough on a plateau. A 17 percent slowdown would have put it at 2.49 s. What that leaves open matters because the port authors from these numbers: the run carries an unmeasured real-time factor under about 7 percent. The plate delay is anchored by agreement with prior runs; the menu build-in and B-to-title are anchored by nothing, so that is a second reason to treat them as provisional. Second, the shared capture is worse than truncated: parec writes the WAV header with zero sizes and patches them on clean exit, so the mid-write copy has RIFF size 8 and data size 0 against 183 MB of actual bytes. Python s wave module refuses to open it; ffmpeg and ffprobe recover by scanning and report a plausible duration, which is exactly why it went unnoticed -- the lenient reader hid it. Also corrects the attribution of the starvation numbers: 39.3 percent and 16680453 frames were measured on the finished local recording, not on the shared artefact. The port measured the shared copy and got 35.6 percent and 15289876 frames, with burst and gap medians agreeing to 0.1 ms. The diagnosis is unaffected but a number must say which artefact it came from. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |