54a719c2860d902a4ead2ca7ccd8ea7804ab9335
147 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0e03e7496e |
re: the three streams of a voice cue are decoded CONCURRENTLY, not alternatives
Refutes a framing of mine that two documents and the port s exporter were built on, so it is a new page rather than an edit. voice-region-leading-chunk.md read a long cue s three streams as three presentations of one take, and from that came the instruction "take one stream, do not sum", which the port implemented. The open question was which presentation the game plays. It has no answer. Booted with --xma_param_probe=true, the cvar whose own comment says it is keyed to reveal which sub-wave of a movie s .slb the game actually decodes. The guest opens three XMA contexts and decodes all three concurrently: ctx=0 packets=632 byte_size=1294336 ch=2 48000 ADV stream 1 ctx=1 packets=546 byte_size=1118208 ch=2 48000 ADV stream 2 ctx=2 packets=572 byte_size=1171456 ch=2 48000 ADV stream 3 Byte-exact against the three streams payloads taken independently off the disc (RIFF size minus 60). Only these three contexts appear in the run. So a consumer that picks one discards two thirds of what the game mixes. Both "three presentations of one take" and "take one stream" are withdrawn -- and the previous behaviour is not thereby right either, because an equal-gain 1/n sum of channel pairs is not a downmix and the port s measured 6.02 dB loss was real. Neither rule is established; a consumer is authoring. Three concurrent stereo streams is six channels and N stereo streams is how XMA carries multichannel on the 360, which would also explain the 1-or-3-never-2 span census. Recorded as a hypothesis, not a result: all three fmt chunks declare ChannelMask 0x0002 identically, which is not what distinct channel roles should look like. Everything byte-level survives: the leading chunk being stream 1 clipped by our own guard, the 70 + 8 + 17 decomposition, the bank-header discriminator. Reach: one cue, one boot. That 28 cues are 3-stream is decoded; that all three decode concurrently is measured on ADV alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
7a0e18889f |
re: the dual-mono explanation for the extra stream bytes does not generalise
The port chose a voice presentation on the argument that ADV chunk 1 is mono-in-stereo and chunk 2 is dual-mono, so chunk 2 s extra bytes encode a duplicated channel rather than fidelity -- which would explain its higher declared PsuedoBytesPerSec without appealing to encode quality. Their ADV channel measurement stands. The generalisation does not. If stream 3 were systematically the same take with its channel duplicated, its size ratio to stream 2 would be tight across the 28 three-stream cues. Measured: min 0.0778 (S00A, the silent one) median 1.2565 max 2.9163 (S06A) sd 0.5057 within 15 percent of 1.0: 12 of 28 A 37x spread is not a duplicated channel, and the declared rates scatter with them -- S06A is 5661 against 16513 B/s. Whatever distinguishes the three streams varies per cue rather than being a fixed channel-configuration triple. This does not touch the port s decision, which is to take the loudest presentation: that is a per-asset content measurement, not a structural rule, so a scattering ratio cannot undermine it. It touches the explanation, which should not harden into a fact about the format. Two curiosities recorded: S12B s three streams are byte-size identical at 14396 each, and BIRD_224 is 3-stream while being a non-movie cue, so the shape is not exclusive to cutscenes. Also narrows the settle-time page s own generalisation. The port measured its boot the way this corpus measured the game and found the sequencer NOT late -- its 0.6 s discrepancy was arrival-to-arrival timestamps compared against visible spans, the plate-delay trap in a second place. So what is supported is that rest.t is the wrong landmark for the TITLE, not that everything paced off it is late. And the offered re-take of the one-run menu figures is recorded as declined, with the reason, rather than left looking unfinished. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
4bd4779f8b |
re: settle_time measured -- the title is drawn at 2 s, not at rest.t s 4.18
The port s boot sequencer paces every screen off rest.t, which is the last hold keyframe rather than when a screen arrives. Measured on one cold boot: the container had no Xenia storage root at all, so this is a fresh profile with no shader cache, the slowest case. title build-in (first ink -> art fully drawn) 0.23 s title settled -> PRESS A plate on 2.247 s (disc declares 120 units) plate pulse period ~2.37 s main menu build-in 0.531 s B -> title 0.482 s A -> menu 3.763 s DO NOT AUTHOR, see below The title s rest.t is 251 units = 4.183 s and its art is finished at about 2 s, so a sequencer pacing off rest.t holds it roughly twice as long as the game does. Instrument controlled before the run: 9/9 on the content classifier including the movie-frame and difficulty-screen negatives, 4/4 on the plate detector; the run sampled 7.99 fps against a requested 8 with an independent one-shot grab cross-checking every 20 s. Records a refutation attempt of mine that FAILED. The probe s own marks gave a plate delay of 3.203 s against the corpus s 2.13 s, which on a cold-cache boot looked like a real effect. It was the instrument: the plate pulse period is an internal clock for presentation rate and measures 2.369 s here against the corpus s 2.3, so the run is not slowed, and re-measuring from content gives 2.247 s. The probe s title_static mark fires during the crossfade out of the attract movie, before the wordmark has drawn -- glyph was still 0 when it fired. Also a third independent reproduction of the A-path load stall: 13 frames, 1.53 s, surface mean 26.631 against the earlier 14/1.53 and 12/1.39 at 26.626. This boot had no shader cache, so it is not a warm-cache artefact. Noted that the earlier pair agreed to six decimals and mine agrees to three. Reach: one run. The menu build-in and B->title rest on it alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
801062c933 |
re: a long voice cue is three presentations of one take, and our guard clips the first
Closes the last open question on the voice regions: why one cue s byte span decodes to ~2.6x the movie s length. The port measured, with controls including a cross-movie negative, that a region s leading chunk is the TAIL of the full-length chunk that follows it -- r = 0.998 at a lag that puts it flush against that chunk s end, residual 16.7 dB down over 84.5 s. They withdrew their own earlier 0.768, which came from a search that scored best on the boundary of its own lag range. Checked it here by an independent route that needs no decoder. If the leading chunk is the tail of a full-length first stream, the whole leading stream should be one complete take of chunk 1 s duration. For ADV: 504464 + 808304 = 1312768 B at chunk 0 s byte rate of 9559.7 B/s is 137.323 s, against chunk 1 s measured 137.324 s. One millisecond over 137 seconds, from byte rates rather than from envelope correlation. And the byte structure settles the shape disc-wide. Counting stream starts inside every inter-descriptor span: 258 hold exactly 1 stream, 28 hold exactly 3, and nothing holds 2 or any other number. All 20 spans over 1.5 MB are 3-stream. The 95 movie regions decompose 70 + 8 + 17, and the 8 are independently the same 8 the first census found as bank-header-with-3-chunks. So 359 s = 84.55 + 137.32 + 137.32: three presentations of one take, the first clipped by resolve_movie_voice_region s own 1.5 MB guard. Consequences recorded for the port: dropping the leading chunk is removing a duplicate rather than truncating, so the hedge is lifted; but summing chunk 1 and chunk 2 is wrong, because they are the same take at different gain, not two stems. Take one stream. Also flags a coincidence I nearly built on: the 504464 B constant is structural, not proportional -- ADV s proportional prediction lands within 8 bytes of it and S00A s is 4305 B out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
06a1779b19 |
re: the voice region s leading chunk is the movie s OWN dialogue, and a guard puts it there
My own leading hypothesis -- that the leading chunk is an in-mission VOICE_D_* line -- is refuted, on the route the port suggested: widen the enumeration past the 95 manifest-bound movies and the byte-span test settles it without anyone listening. Scanning the stream for every trailer descriptor (the (id, 0x11) pair whose id repeats at +0x800) gives the complete cue partition, mission lines included: 287 descriptors in a 116.2 MB window, all 287 carrying an id the 4280-name registry names. Every one of the 17 leading spans is bracketed by desc(N-1)..desc(N) where desc(N) is that movie s OWN cue id. Zero mission lines. The mechanism is a guard in our own resolver. resolve_movie_voice_region takes the predecessor trailer as the region start, guards it with end - start < 1_500_000, and falls back to the .slb TOC anchor when that fails. Cues with a true span over the guard: 17, of which 17 are stream-opening. Cues under it: 78, of which 0. Perfect discrimination both ways. The anchor sits a constant 504464 B after the true predecessor trailer on all 17, which is unexplained. Not established, and stated as such: this does NOT mean the export truncates N seconds. The port s decode already has ADV s region at 359 s against a 137 s movie, so it over-covers and the byte-to-time mapping is not linear. No XMA1 decoder in this container to check. Also withdraws a claim this page had adopted from the port -- that chunks 1 and 2 are two stems of one performance. The port refuted its own claim by decoding: S00A chunk 2 is digital silence, ADV chunk 2 is 0.60x chunk 1 with the residual 26.8 dB down. Equal duration was a shape match and Q10 s music census should not have been carried across to voice on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
7e12a3b1f9 |
re: the voice-region third chunk is a different structure from the BGM one
The port hit a 2+1 chunk signature on a resolved movie-voice region and asked
whether the bank-header explanation that closed HANDOFF Q10 also covers it,
rather than assuming it. It does not, and the discriminator is mechanical.
Disc-wide over the 95 English movie-voice regions the manifest binds:
78 open with a bank header -- bank_header_len fires, 10240 B = 5 packets
exactly, every time. That is the BGM case.
17 open with a leading headerless stream -- bank_header_len is None, and all
17 have length congruent to 1392 mod 2048, the disc s own derived data
offset. No other residue occurs.
0 begin at a RIFF.
Counting chunks does not discriminate: 8 bank-header regions also yield three
chunks. slb.rs already predicted this in its own doc comment -- the header
signature has "zero false positives on the 7993 mid-bank windows, where the
leading region IS real" -- and a voice region is a mid-bank window by
construction.
Also tested the obvious defence of dropping the leading chunk, that it is the
predecessor cue s audio: 0 of 17 leading spans lie inside any other resolved
region, 0.0 percent on every one. The test finds overlaps where they exist (16
overlapping pairs among the regions, 60 exactly-adjacent boundaries, 73 of 78
bank-header regions starting where another ends), so the zero is not the
instrument.
Left open, with reach: the census covers movie-voice regions only, and the same
stream carries the in-mission VOICE_D_* cues, which are not enumerated -- the
leading bytes plausibly belong to one of those. Could not be settled by
listening: no XMA1 decoder in this container, and sylpheed-cli audio info
reports these chunks as 16 channels / 4310 Hz / 2-bit, which is visibly wrong.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd
|
||
|
|
ae42f7f850 |
re: the paint-order tie-break costs the port zero pixels, not 24 pairs
The census bounded WHERE a wrong tie-break could show and said outright that nobody had measured how many of those pairs change a pixel. Measured. `compose_with_order` renders a bundle in a caller-supplied order; the new example renders each screen twice, once derived and once with one tied pair swapped, and diffs. Same-key elements are contiguous under a stable sort on (key, i), so a swap paints nothing else in between. Controlled per entry: swapping an OVERLAPPING pair with DIFFERENT keys moves 36 305 to 771 479 px (max delta 254). Where no such pair is drawn the output says so rather than reporting an uninterpretable zero. * EXTRAS (entries 6/9) and the main menu (5/8): 0 px. The tied ptframe pairs ink ~3 600 px each and share NONE of them -- the 102x132 rect overlap was an artefact of approximating an element as pivot x 2. Blend-independent. * Across all 31 drawable overlapping tied pairs in GP_TITLE, the largest change any of them makes to any channel is 3/255. * Withdrawn: "a wrong tie-break can be wrong by a whole layer". That rested on ptlogo_back2eff5 geometrically containing two other glows. Rendered, the swap moves 6 390 px by max delta 2. Containment is not occlusion when the container is a near-transparent glow, and nobody had rendered it before asserting it. Reach: this measures our compositor's sensitivity to order, not the game's. The zero-shared-ink results hold under any per-pixel blend; the delta<=3 figures assume ours. 13 disc-gated ui_paint_order_disc tests and 114 lib tests pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QsEPXWVaEpyfudtR6re1Pd |
||
|
|
c5b815c5aa |
re: the paint-order tie-break costs 24 overlapping pairs, not one element
The port challenged HANDOFF's "costs one element's blend on one screen" with a census of 105 elements sharing a layer key across 12 of 16 screens. The two numbers count different things -- elements vs overlapping pairs -- so it is not the contradiction it looked like, but the objection stands and the line was wrong. paint_order_audit already reports overlapping ties per entry, and over all 16 GP_TITLE entries: 5 use a measured order and carry no tie risk; of the 11 that fall back to the derived order, 7 have overlapping ties, 24 pairs in total. The Japanese title (entry 7) alone has 16, because it is the twin of the one build whose measured order exists and has none of its own. Overlap bounds where a wrong tie-break COULD show, not what it costs; nobody has measured how many of the 24 change a pixel, and the port is right about that too. The rule itself is unaffected: the layer key is still decoded and the derived order still reproduces every measured order exactly except the title's eight tied pairs. Census committed at docs/re/data/paint-order-ties-gp_title.txt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nsxw1A9JseUw99Yw1ZRQzY |
||
|
|
a9755170ee |
formats: a keyframe's time comes before its pose, and none of them was missing
The placement region is `frames` records of `{u32 time; 36-byte pose}` after an
8-byte header, so the time word PRECEDES the pose it belongs to. Our parser's
40-byte window opened at the pose, four bytes into the record, and then read the
word at its `+36` as that pose's time -- which is the NEXT pose's. Every pose
field was right; only the time association slipped by one.
Two things the corpus has carried for weeks are that off-by-one and nothing
else: "a group's data stops 4 bytes short of its final block's time slot", and
"the last keyframe carries no time". The group is not short (8 + frames*40 is
exact) and no time is missing -- the first pose's time is the lead-in word at
`header + 8` that `parse_placements` skipped without asking what it was.
Disc-wide, 33 archives, 13 991 groups, each test with a control:
A lead-in prepended to the shifted times is non-decreasing 13991/13991
B a non-zero lead-in is strictly below the next time 5058/5058
control (another group's lead-in, same bundle) 70.9%
C multi-segment alpha ramp at a constant rate, corrected 857/1540
the same, under the old reading 0/1042
C is the one that cannot be argued with: interpolation between keyframes is
linear, and under the old reading not one multi-keyframe ramp on the disc comes
out at a constant rate.
Adoption is free on every static composite, which is what the corpus previously
declined it over. `SYLPHEED_KF_TIME_SHIFT=1` moved GP_TITLE build 7 by 13.1% of
its pixels because it left pose 0 untimed; with the lead-in restored, all 12
GP_TITLE builds render byte-identically, and across 217 builds in six archives
only two elements pick a different rest pose -- both times between two poses
that are equally invisible.
`SYLPHEED_KF_TIME_SHIFT` is gone; `SYLPHEED_KF_TIME_LEGACY=1` restores the old
reading for A/B work.
ui_header_time_disc needed one line: 546 bundles whose every group is a single
static pose now report max_time = 0 where they previously reported no time at
all. Excluding them, the result it guards strengthened -- the bound holds over
2 859 bundles instead of 2 313, still with zero violations.
Not established: the executable's own parser. Reach is written down.
docs/re/ui-keyframe-record-layout.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nsxw1A9JseUw99Yw1ZRQzY
|
||
|
|
e07c1de1b5 |
re: I cannot measure this emulator's clock -- and that answers the port's 2%
The port put two of my pages against each other: settle->plate 2.135 s and one
focus-ring revolution 2.177 s, both a declared 120 units during a static hold,
2% apart against a 6 ms run-to-run agreement. Fair challenge.
The resolution is that the question assumes a stable wall clock. Same interval,
same container, same day: 2.138, 2.132, and 2.549 s -- a 19% swing, caused by
adding --log_ui_draws=true. The 2% is a fifth of that. The two pages were never
in conflict about the game; they are three readings of one declared quantity
through a clock that moves. What settles the quantity is the disc.
Wall clock cannot separate the hypotheses, so I tried to measure frames instead.
Both instruments are recorded as failures rather than published as numbers:
* Canary's own [UI-CAP] counter -- the one that produced the corpus's 28.5 fps
-- costs a third of the frame rate. 300 frames in 16.567 s = 18.11 fps on a
screen that gives ~28 without it. That reclassifies 28.5 as a load-dependent
lower bound; it does not overturn it.
* A distinct-frame counter over the spinning ring FAILED its decisive control:
15.88 fps against the game's own 17.59 in the same window, 10% low, so the
ring does not change on every presented frame. Its static control also read
2.63 instead of ~0. Dead, not tuneable, per METHOD.md.
The rule that follows, and it applies to everything I hand the port: a measured
interval landing near a round number of declared units almost certainly IS that
number of units. Ship the units.
Also recovered here, because the same question needed it: the static PPC route.
Four tools open /work/xenia-rs/sylpheed.db and nothing in this repository builds
it -- no disassembler, no PPC decoder, and default.xex is encrypted (zero
plaintext "GamePart"). Xenia decompresses the image at load, so dump_image.py
reads it out of guest memory and validates it against the corpus's own landmarks:
the 29-entry GamePart id table at 0x820A1630 and the Xbox 360 D3D runtime
strings. String search and table dumps work again; instruction-level work does
not, and the present interval I wanted is an immediate, not a string.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
|
||
|
|
fb536df8b4 |
re: the PRESS-A plate arrives 2.13 s after the title settles, measured twice
The one number the port said decides a structural question on its side: whether
`ScreenView` has to draw two builds at once. It does. The boot title shows
build 4 alone, and 2.13 s after build 4 stops animating it composites build 2
over it. Two independent boots agree to 6 ms (2.138 / 2.132), which is under one
sample interval.
Measure from SETTLED, not from first pixels. "First drawn -> plate" is 3.78 s in
one run and 4.26 s in the other, because the build-in animation itself ran 1.64 s
and 2.13 s -- that spread is the emulator's frame pacing, and it is exactly the
kind of number that looks like a measurement.
Ruled out before believing it: that the plate was pulsing all along, too dim for
a thresholded glyph counter. The counter reads EXACTLY 154 -- the committed
no-plate title's own value -- for every frame of a plateau nearly one full pulse
period long, with zero variation, and the surface mean is flat to +/-0.03 across
it. A cycling overlay moves both.
Also settled, and also not:
* the black hold between two screens is 0.14-0.30 s, which brackets the port's
authored 0.17-0.23 s and the file's declared 12 units. Their constant stands.
* the (A)->menu latency is STILL not available, and now the reason is known.
Both runs freeze one frame for ~1.4 s at surface mean 26.626 -- agreeing
between runs to six decimals, and reproduced in run 2 with stream restarts
disabled, so it is not the capture path. It is a guest load stall: the (B)
path, which loads nothing, has no freeze at all. Any figure from it would be
an emulator load time.
Refutation attempt, recorded whether or not it survived: navigation.md's "the
title is not input-ready for about ten seconds, and even then (A) registers
roughly half the time". At 7.29 s and 7.28 s after the title settled, (A) was
accepted first press in both runs, as was (B) on the menu. n=2 only makes "half
the time" unlikely (p ~ 0.25); it contradicts the ten seconds outright.
And the standing red banner is withdrawn: the interactive title IS reachable in
this container, twice, with no pad input, in ~3.5 minutes. Why it changed is NOT
established -- this container came up with no Xenia storage root at all, so run 1
created a profile -- and that is written as a correlation for the next session to
test rather than as a cause.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014voBspJ6kFncNErZJuZcLw
|
||
|
|
4fa3099249 |
re: the main menu's focus ring spins continuously -- period 2.18 s, measured
Answers the port's ask: ptbtneff01 is ANIMATED while a button is focused, not drawn once and held. The existing page said 'the ring SPINS' from one frame at a large angle, which is equally consistent with a static draw at a fixed angle. No angle is quoted anywhere. The 360-bin angular estimator written for this FAILED its own control -- a synthetic 30 deg came back as 0 deg (peak 0.596) while 90/180/270 came back exactly -- so it was not used. What settles it needs no angle: total annulus brightness is conserved to 0.4 % while individual angular bins swing by 24, i.e. brightness moving AROUND the ring, which excludes a pulse. The temporal-std map is a clean annulus, falling to ~1 both inside and outside the stroke, which excludes positional jitter. Period from the profile's autocorrelation: eight evenly spaced peaks, mean 2.177 s over nine revolutions. Even spacing is the internal check a drifting instrument cannot pass. That is 120 units = 60 frames = 2.00 s at a true 30 Hz. Also measured, same run: the ring is the ONLY moving thing on the settled main menu -- temporal std is exactly 0.000 on every unfocused button, the labels and the footer. And the ring's centre, located from the std map at game (520.7, 339.7), matches the declared leaf offset's prediction of (521, 340). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KNR5Y79D1T4bBr6gJQaWFP |
||
|
|
3491d30066 |
re(media): the disc ships movies in TWO audio profiles, and 28 of them are 5.1
Probed all 97 movies. 28 are wmapro 48 kHz 6-channel 5.1 -- ADV.wmv and every S*.wmv story cutscene; the other 69 are wmav2 48 kHz stereo, every RT*.wmv and hokyu_*.wmv. The split is cinematics vs in-mission radio chatter. Both movies the menu milestone needs, ADV.wmv (the boot/attract intro) and S00A.wmv (the new-game intro), are in the SURROUND group. Why it matters: one ffmpeg command over dat/movie/ produces two different kinds of result and records neither. The 69 stereo files pass through unchanged; the 28 surround files get downmixed 5.1 -> stereo by ffmpeg's DEFAULT matrix, folding centre-channel dialogue into L/R at a weighting nobody chose and which is not stable across ffmpeg versions. That is a content decision inherited by accident, so it should be stated explicitly and recorded beside the command. Credit where due: found by the human while checking a transcode, verified independently here and widened from one file to the whole disc. Two METHOD entries from the same episode, both about measurement rather than format: don't probe a file another process is still writing (a half-written transcode reported 33 s against a 137 s source, no error, nearly a filed bug), and a difference-signal RMS is meaningless before cross-correlation alignment (-34.2 dB against a -25.3 dB source looks like failure and is inconclusive). |
||
|
|
d110cf38c7 |
media: expose se_wave_riff -- the menu's SE cues, assembled where the format lives
The port is forbidden from reimplementing media assembly and Static.slb is exactly that case: no RIFF, no seek chunk, no XACT container, just a packed run of whole 2048-byte XMA1 packets, so a wave is defined only by (offset, packet count) and the header has to be synthesized. That step now happens once, in the crate that owns the format, instead of in each consumer. `slb::xma1_wave_riff` wraps raw packets; `media::se_wave_riff` looks the bank up and reads just the packets asked for. Both reuse the existing synth_xma1_fmt / build_riff, which are already byte-identical to what tools/re-capture/ slb_extract_wave.py writes -- so this is exposure, not a second implementation. It reads a TARGETED range rather than the whole bank, and that is load-bearing: Static.slb is the ONE entry of sound.pak's 9 519 whose declared extent runs past the end of the extracted segments -- by exactly 616 768 B -- so reading it whole fails outright on this extraction. Every cue we need is in the first few hundred KB. Recorded rather than worked around silently. Verified as an artifact, not a compile: all three cues decode through ffmpeg to mono 48 kHz PCM at 0.533 / 0.344 / 1.016 s, non-silent (rms 2085 / 2985 / 4327, peaks 29813 / 16973 / 32767). The refusal path is exercised in the same run -- an impossible packet count is rejected rather than returning a short stream, because a truncated XMA decodes to plausible-sounding garbage. Also adds docs/re/captures/ORACLE-CAPTURES.md: an index of the nine canary framebuffer captures already in this repo, and a plain statement that THEY are the reference and `screen render` is not. |
||
|
|
f817dd5939 |
re(ui): the 60 nameless RATC children are frames, not children -- .tan decoded
Closes the reach caveat the `opt ` name fix left behind: 60 of 18 002 RATC children carry no `opt ` block, and it was not established whether they lack one or sit past our 128-byte window. Neither. They are not children. `examples/ratc_optless_children.rs` re-runs `ratc::parse`'s own guards over the disc and reports which one fired: all 60 are "tag beyond the window", none is rejected by length, gap or charset, none is child #0, and all 60 live in six bundles of one archive. Within a bundle the distances back to the nearest tag are an exact arithmetic progression, step 60 600 -- ten different records finding the SAME tag, because there is only one. Reading a bundle directly: children 1..10 are equal-size T8aD blocks under a single `opt ` name, `pb_f15_eg_anm.tan`. `.tan` is a FRAME SEQUENCE. One block declares the resource; its payload is a run of T8aD frames. Disc-wide, over all 18 718 `opt ` names in all 33 paks: a RATC bundle names exactly six kinds of resource -- `.t32` 14 756, `.rat` 3 311, `.prm` 367, `.tbm` 224, `.sbo` 54, `.tan` 6. Six `.tan`, ten frames each = 60, the entire population with nothing left over. The negative is closed, not narrowed. Consequence recorded but deliberately not fixed: `ratc::parse` over-reports there, listing a `.tan`'s frames as anonymous children. Nothing in the menu milestone reads a `.tan` -- it occurs only in GP_READY_ROOM, which S1 ruled out -- so no screen the port draws changes. Also a METHOD entry for this container OOM-killing `slb_leading_segment_disc` under default test parallelism (SIGKILL, no assertion; 8/8 pass with --test-threads=1). |
||
|
|
56cc7acfc3 |
re(ui): a RATC child's name is stated, not inferred -- and it was hiding every menu background
`ratc::parse` named each child by scanning backwards for the last printable run of bytes before its magic. The format states the name explicitly instead, in an `opt ` block: `"opt " | BE32 len | name | NUL | 3 bytes | magic` -- the same block `ui_layout::opt_link` already read for a button's focus link. The scan agrees with it 17 918 times out of 17 942 and is wrong 24 times, every one the same failure: the 3 trailing payload bytes are themselves printable and beat the real name. For `pteff05.t32` those bytes are `38 41 58` = `8AX`, so the full-resolution background of all five menu screens registered under a name no element declares, resolved to no sprite, and `compose` dropped it through an early `continue` that -- unlike the two arms above it -- records nothing. The screen lost its background and `screen render` still reported "all resolved". `8AX` was never a name. Docs that treated it as one are corrected here. Disc-wide, and the control is the 17 918 the scan already got right: the `opt ` reading reproduces every one of them. Effect on the five screens is the signature of the same art at twice the resolution -- mean brightness unmoved, high-frequency detail x1.15..x1.30 -- which is what the separately-measured `ui-8ax-fullres-background` result said the game draws. Also closes a long-standing dangling reference: `pmbase.t32`, recorded as "on the disc nowhere", is the `GP_STAGE_CLEAR` child the scan called `8AX`. RATC sibling references now resolve 10 148 of 10 148. Verified: 114/114 sylpheed-formats unit tests (including two new ones pinning the `8AX` case byte for byte and the no-block fallback), and every disc-gated integration suite in sylpheed-formats/sylpheed-cli. |
||
|
|
7a4e4333f4 |
docs: withdraw yesterday's "paint order is a sequence" -- wrong source
Last iteration I claimed the splash's measured_paint_order [0,2,4,6,1,3,5]
records, between its glow and logo halves, the temporal order they were
seen in rather than depth -- because the halves never share a frame.
The no-overlap measurement is right (glows f94-115, logos f116-211). The
inference is wrong, on two independent grounds:
* Wrong source. That vector is not a read of the draw capture. It is a
read of the live screen object's CHILD ARRAY -- ui-screen-runtime.md
records it literally as "paint order (child slots)". A child list has
a definite order whether or not its children are ever drawn together,
so co-occurrence does not bear on it. The capture was the CHECK.
* The order is in the file anyway. paint_order_audit on GP_TITLE entry
11: derived == measured, 0 inverted pairs, 0 same-layer-key ties. The
glows and logos carry distinct T8aD keys (0xa100 < 0xa110), so the
file orders the halves statically, no capture involved.
I asked the question that started this iteration -- do the title and menu
orders have the same problem -- and the answer is that none of the three
does, for the same reason.
What survives is narrower and now recorded with numbers: how much of each
order its capture actually cross-checks. The title capture is stable (8
draws / 12 quads / 5 textures, identical in all five captured frames
across two logs) and confirms 7 of 24 positions; the menu capture is not
(texture 0x11C30000 present in frame 0, gone by frame 3); the splash
capture cannot cross-check its middle at all.
A counting trap worth the tool: count QUADS, not draws. The menu's draw 9
is indices=24 -- six quads batched from one texture. Counting draws reads
9 where 16 are on screen, and an earlier pass of this analysis briefly
"found" three quads for six declarations that way and concluded elements
were missing. They were batched.
METHOD: check what a "measured" value was measured FROM before reasoning
about its limits. The co-occurrence rule is real, and it is specific to
orders read from draw captures.
|
||
|
|
7988f52ce7 |
re(ui): deliver the measured splash sequence the port has to author
The activation decision is code, and MISSION already says the port authors the sequence -- so the useful move is to hand over the sequence measured rather than chase the code. From the 235-frame draw capture, at 1 frame = 1/30 s (2 units/frame, 1 unit = 1/60 s, both settled in Q1): publisher: SQUARE ENIX logo f1-90 90 frames 3.00 s+ at 0.00 (gap, nothing drawn) f91-93 3 frames 0.10 s at 3.00 developer: both glows f94-115 22 frames 0.73 s at 3.10 developer: both logos f116-211 96 frames 3.20 s at 3.83 Three limits, stated with the numbers rather than after them. The capture opens with palogo_sqex already at a=255, so the publisher phase began before the window and 3.00 s is a FLOOR -- every "starts at" is relative to the capture, not to boot. palogo_anima and palogo_anima_eff get 0 draws in all 214 frames, so a third pair's phase is not in this measurement. And it is one capture, one run: the glow->logo switch being a single frame boundary with no overlap is a strong shape, but each duration is one sample. What is solid is the part that matters: the 0.73 s and 3.20 s phases are each within 2% of their element's declared span, so the durations are the bundle's own and only the ordering is authored. That is the difference between a port transcribing timing and inventing it. |
||
|
|
458585d136 |
re(ui): the structural case for last -- 2 293 of 2 305, checked disc-wide
The weakness in the rest-rule finding was that `last` had been SCORED on only two elements. It cannot be scored on more -- only two ambiguous elements sit on a screen with a live capture -- but the entry -> hold -> exit model makes a prediction that can be checked on all 2 305: what does each element's FINAL keyframe look like? final keyframe invisible (a = 0) 1 618 transient: gone at rest final keyframe visible, at max alpha 675 faded in and stopped final keyframe visible, BELOW max alpha 12 genuinely unclear Of the 687 that end visible, 472 have monotonically non-decreasing alpha -- a plain fade-in that stops, [0, 255] over two keyframes in the commonest case (pjex_eff.rat, pghud_speed_cut.t32) -- and another 203 end at their maximum after dipping. So `last` is structurally defensible for 2 293 of 2 305 (99.5 %), against a dwell rule that returns a mid-movement frame by construction. Observed correct for 2, structural for 675, model-consistent for 1 618, unclear for 12. The assumption carrying the 1 618 is stated rather than buried: that a plateau-less element's animation has finished by the time the screen is settled. The draw log establishes exactly this for the two splash glows (drawn frames 94-115, logos 116-211) and establishes nothing for the rest. Default still unchanged. The case is now observational, structural and model-based rather than two data points, but it would move 1 896 elements and the decision belongs with whoever owns the renderer. |
||
|
|
515456c59a |
re(ui): quantify what changing the rest rule would do disc-wide
The open question was whether "last keyframe" holds beyond the two
elements I could score against a capture. It cannot be scored disc-wide --
only two ambiguous elements sit on a screen with a live capture -- but the
blast radius can be measured, and it argues the same way.
genuinely ambiguous elements 2 305
the two rules AGREE on 409 (17.7 %)
they DIFFER on 1 896 (82.3 %)
dwell (current): invisible pose 1 711 (74.2 %), zero-scale 195 (8.5 %)
last : invisible pose 1 618 (70.2 %), zero-scale 43 (1.9 %)
Two things follow. It is not a marginal choice: the rules disagree on 82%
of the affected elements, so "either is fine" is not available. And the
current rule produces 4.5x more degenerate poses -- a zero-scale pose is
collapsed to nothing, i.e. an element's PRE-ROLL before it has grown in,
which is definitionally not a rest. 195 elements currently rest at a frame
they are only passing through, against 43 under last.
That is an argument from the data's own structure rather than from the two
captures, and it points the same direction.
Kept honest: it is indirect. Fewer degenerate results is not the same as
more correct results, and last still returns an invisible pose 70% of the
time -- right for a transient element, wrong for a persistent one. The
default stays put; the numbers are in HANDOFF for whoever decides.
|
||
|
|
0efd692b4c |
docs: the suite is heavy, not hung -- correcting my own "cannot terminate"
Last iteration I wrote that build-reborn test cannot finish in a working session, from having watched it run 3h26m. That was the stronger claim and I made it without measuring the work. Timing `mesh info` on each of the 166 .xpr containers with a 25 s cap: files scanned 166 exceeding 25 s 19 Hangar, 17 Stage_*, ptc_pack Stage_S02 to completion 144 s, rc = 0 Nothing hangs. Nineteen heavy containers at roughly two minutes each is about 45-60 minutes for one pass, before the 147 fast ones. The 3h26m observed was that hour of work running at a load average of 9-14 -- inflated by the two duplicate runs I had left going, which did not merely coexist with the slowness but multiplied it. The practical conclusion is unchanged and only the wording softens: an hour-scale suite is not an iteration-scale gate, and every "green" I reported from it this session was partial. But an hour-scale gate can be run deliberately, whereas a hung one cannot be run at all, so the distinction is worth having right. File list committed as reference data so the cost is attributable without re-scanning. METHOD: a slow thing observed under contention looks like a stuck thing; measure the work before choosing between "cannot finish" and "takes an hour". |
||
|
|
714a26769d |
re(ui): premultiplied alpha refuted for bit 0x02; parking the field
A per-sprite premultiplied-vs-straight-alpha flag would matter a lot to a port and has a sharp static signature: premultiplied means RGB <= A everywhere. Over the 170 decoded GP_TITLE textures that pair to a flag word: bit SET n= 61 mean %(RGB>A) 55.52 median 52.52 bit clear n=109 mean %(RGB>A) 33.66 median 30.17 Premultiplied requires ~0% for the flagged group. Both groups are far from it and the flagged group violates MORE -- the opposite of the hypothesis. Refuted. What remains is a weak association: flagged sprites carry more bright-RGB/low-alpha pixels, which is what glow art looks like. But the best single threshold classifies 76.5% against a 64.1% base rate -- a 12-point lift with badly overlapping distributions. A tendency, not a rule, and reported with its base rate so it cannot read as more. Noted for whoever returns: "0x02 selects an additive blend" was refuted by blending those sprites additively and finding every measure worse against the capture -- but that ran through a title render since fixed twice (rest_plateau, and the 8AX background the composer drops). The refutation may well stand; it was measured through a renderer with known other errors, so it is worth one re-run if blit ever gains additive blending. Parking the field. Four candidate meanings are dead -- additive blend, eff name in both directions, transient element, premultiplied alpha -- none produced a positive account, and the bit blocks nothing: the port's screens composite at 0.947 correlation against a capture without it. The negative space and the sound attribution method (child order, not size) are written down so a later attempt starts here. METHOD: report a classifier's lift over its base rate; and park a field after N failed hypotheses, saying what was eliminated. |
||
|
|
489ea12759 |
re(ui): the eff-name implication for bit 0x02 is refuted disc-wide
Last iteration I killed the biconditional and reported that the one-way
reading survived: all 10 bit-set sprites on GP_TITLE build 4 are eff
names, so "bit set => eff name". Checked over the disc, that is false.
sprites with a resolvable preceding name 14 709
bit SET & name has 'eff' 2 338
bit SET & name lacks 'eff' 2 657 <-- counterexamples
bit clear & name has 'eff' 1 399
bit clear & name lacks 'eff' 8 315
P(eff | set) = 0.468
P(eff | clear) = 0.144
The implication fails more often than it holds. What survives is an
association -- 3.3x enrichment -- and build 4's 10/10 was a local naming
habit in an 18-element bundle, not a format rule.
The counterexamples are the useful part: pv_loading_ring0,
pv_loading_light0-3, pv_loading_line, px_bunk_line, px_top_extra. Rings,
glows, lights, thin lines -- effect-like artwork that does not carry the
eff naming convention. Consistent with the bit marking effect sprites by
authoring intent rather than by name, which is a description and not a
decode, and is labelled as such.
Names here come from the string immediately preceding each T8aD,
validated 17/18 on build 4 against the RATC child order; the single
mismatch is the known pteff04.t32 -> registered as 8AX case, so this is
the element (opt) name rather than the sprite's registered name. That
mismatch is itself an independent confirmation of the 8AX finding,
reached from the opposite direction.
METHOD: a pattern perfect on one screen can be near-chance on the disc;
and when an association survives a refuted implication, the
counterexamples are the finding.
|
||
|
|
c6ce000002 |
re(ui): the game does query the display gamma -- measured, with its control
Last iteration's corrected experiment, run. Boot with --log_mask=12 --log_level=3 (Kernel logging on, Cpu/Gpu off), which changes nothing about the output and so cannot perturb the capture harness the way the gamma-cvar experiment would have. VdGetCurrentDisplayGamma is called once, at video init: d> VdGetSystemCommandBuffer(701CF830, 701CF804) d> VdGetCurrentDisplayGamma(701CE1F8(00000000), 701CE1F0(0)) d> VdSetDisplayMode(40000000) d> VdGetCurrentDisplayInformation(701CF110) The control is in the same log: 359 VdRetrainEDRAM and 358 VdGetSystemCommandBuffer lines, so an absent call would have been visible. Per the export's own comment the returned type is "used in D3D SetGammaRamp/SetPWLGamma" -- the game asks the question a ramp-builder asks, at the moment one would ask it. Still open, and stated: whether it then WRITES the ramp, and whether the measured gamma 1.34-1.49 is that ramp. The write is a GPU register operation (DC_LUT), invisible to kernel logging; a GPU trace records gamma ramps as a command type (TraceWriter::WriteGammaRamp), which is where to look next. Worth its own METHOD line: this had been parked behind "needs the emulator to reach a menu" for several iterations, and it needed the emulator only to BOOT -- video init happens in the first seconds. A blocker that stops one experiment does not stop every experiment in the same area. |
||
|
|
3490fba9e3 |
re(ui): settle 8AX vs ptbase statically -- the game draws the full-res one
I had parked this as "needs a per-draw capture recording texture base addresses". It did not. 8AX (1280x720) and ptbase (640x360 at 200%) are the SAME artwork at two resolutions, which is exactly why comparing either against a capture is inconclusive -- and why comparing their DIFFERENCE is not. Compute 8AX - upscale(ptbase), the detail only 8AX has, and ask whether the capture contains it. Both candidates are first mapped into the capture's tone domain with the measured gamma; without that the residual is dominated by the tone difference and the test is blind. main menu corr +0.0475 controls +0.0032 shift, -0.0075 flip 68% of ceiling title corr +0.0634 controls +0.0095 shift, +0.0086 flip 68% of ceiling Two independent screens, both at 68% of the theoretical ceiling (sd of the 8AX-only detail over sd of the capture residual), 7-15x their matched controls. The controls preserve spatial correlation and destroy only alignment, so they are what "no signal" looks like. So the recommendation changes: resolve the name and draw 8AX at 1:1. Upscaling ptbase 2x is wrong, not merely softer. Still do not draw both -- an opaque layer over an identical one costs fill and hides later changes, and ptbase's element is the one carrying the keyframes, so a consumer needs its timing with 8AX's pixels. Also recorded and withdrawn: a cruder pixel-pair test gave 0.00-0.72 for upscales, 0.98 native and 1.01 for the capture -- apparently decisive. Additive noise raises both terms of that ratio equally and drives any value toward 1; fitting a noise term, both "native + noise" and "bilinear + noise" reproduce the observed numbers. The conclusion is right, that test does not establish it, and it is in REFUTED because the number looks conclusive and is not. Not shown: whether ptbase is also drawn underneath. 8AX is ~86% opaque and carries the same art, so it would hide it either way. |
||
|
|
aaaa08b164 |
docs: the UI decode's own evidence images were unreachable -- 11 links repaired
The brief's rule is to commit reference data beside the finding so the port can be built without a disc. Nothing had ever checked that the docs' cited artifacts actually exist. doc_link_check.py walks every markdown file under docs/, resolves each relative link, and reports targets that are missing -- and separately targets that resolve to a ZERO-BYTE file, which looks fine in any listing. links resolving 1038 -> 1049 missing targets 16 -> 5 empty targets 0 -> 0 +11 resolving and -11 missing against 11 edits: the counts pair, which is the confirmation the pass did what it claimed and touched nothing else. Two of the sixteen were the evidence for the UI layout decode itself. structures/ui-rat-layout.md is what the port is built on, and its two figures -- backing "the tutorial PAUSE menu rebuilds pixel-accurately from its sprites" and "the same method reproduces the main menu" -- were written as captures/ui-layout/... from a file in structures/, one directory too shallow. The headline evidence for the decode could not be opened from its own document. Eleven links had the wrong relative depth with the target present. Each was rewritten only where exactly one candidate path resolved, so nothing was guessed; the first pass left three alone because equivalent spellings (captures/../captures/x) failed to collapse, and a second pass normalised them. Five remain genuinely absent and are left rather than invented: two point at MEMORY.md outside the repo, one at a header in the separate xenia-canary-native tree, and two name documents that were never written (weapon-datasheet-runtime.md, canary-build-verified-env-confound.md). None is port-relevant. A missing document is a different problem from a bad path and is not something a link fix should paper over. |
||
|
|
ba47bdebe8 |
re(ui): measure the paint-order hedge -- exact on 4 of 5, and bound the rest
`compose` claimed the derived paint order "reproduces both measured orders up to ties". That sentence was never measured and was stale by one: there are three measured orders, not two. examples/paint_order_audit.rs checks it. main menu (entries 5, 8) derived == measured 0 inverted pairs developer splash (11, 14) derived == measured 0 inverted pairs title (entry 4) DIFFERS 8, all same-key ties So the claim holds and the exception is entirely ties -- but two of those ties are total occlusions, not near-misses. The tied family is the five ptlogo_back2eff glows (key 32899); back2eff5 is 1133x280 and FULLY CONTAINS back2eff3 (82,824 px^2 = 100% of the smaller) and back2eff4 (152,047 px^2 = 100%). Derived paints it on top of two glows it entirely covers; the game paints it underneath. A tie-break by declaration index can therefore be wrong by a whole layer. The title itself is unaffected -- it has a measured order. The port's actual exposure, per screen: title, main menu and developer splash all use MEASURED orders; the publisher splash is derived but has ZERO ties, so it is fully determined; EXTRAS is derived with 15 tied pairs of which only 2 OVERLAP. Two element pairs on one screen is the whole risk, and that is what HANDOFF now says -- not the raw 15, which would have overstated it 7x. Reach stated: this compares the derived order against orders measured from the game, not an independent derivation, so where no measured order exists only the tie exposure can be checked. Overlap uses pivot*2 as the element size at its resting placement. Stale comment in compose corrected. METHOD: a hedge in a code comment is an unmeasured claim; and count the cases that can bite, not the ones that match the pattern. |
||
|
|
6d246c7a97 |
re(ui): finish the top-level rotation census; record the JP-capture blocker
Two threads had converged on needing one capture this container cannot take, so this iteration records that and finishes something reachable. BLOCKED, written into MISSION.md rather than worked around: Q1's keyframe time association and the rest() rule for plateau-less elements both now hinge on a running capture of GP_TITLE build 7, the Japanese title. The console language is not settable here -- user_language appears only as DECLARE_int32 at four call sites with no DEFINE anywhere in the tree, and it is absent from the registered cvars in xenia-canary.config.toml. There is no flag to pass, and guessing one is specifically unsafe: run-canary's own header records that xenia calls ShowSimpleMessageBox from ParseLaunchArguments before logging starts, so a bad flag blocks forever with an empty log. Rebuilding canary to add the cvar would be improvising around the blocker; it needs a human decision. Neither question blocks the five menu screens. FINISHED: the disc-wide top-level rotation count, left running four iterations ago as a shell loop over `screen info --geometry` that never completed (it decodes every texture per build). Walking the placement region directly takes seconds. top-level elements with a keyframe group 15 493 carrying a non-zero rotation 2 152 (13.89 %) Both controls pass: GP_TITLE build 4 reports 0 (its rotations are the nested ptloop records) and GP_DIALOG build 0 reports the expected two. The control earned its place -- the first version indexed the pak with a `screen list` BUILD number and got 0 for a screen that has two, because GP_DIALOG build 0 is entry 2. GP_TITLE maps 1:1, which is how the assumption survived. METHOD line added. Two free corroborations of the rotation decode. The rotated population is dominated by tactical-map ship icons -- pbb_destroyer 444, pbr_destroyer 402, pbr_fighter 276 -- i.e. markers rotated to heading, the single largest use of the field on the disc. And GP_TITLE entry 7's Japanese wordmark pieces settle from ALTERNATING tilts: ptlogo3a r = 0, -14, -4, -1, 0, ... ptlogo3b r = 0, +14, +4, +1, 0, ... ptlogo3c r = 0, -14, -4, -1, 0, ... Same magnitudes, opposite signs, all decaying to upright. A misread field does not produce that. |
||
|
|
b973d8cde3 |
re(ui): scale 0 means collapsed, not "unset" -- stop drawing it full size
blit() and fill_quad() both opened with
let sx_pct = if kf.scale_x == 0 { 100 } else { kf.scale_x };
so an element whose pose is collapsed to nothing rendered at FULL SIZE.
I first described this as "a 1-pixel sliver", reading the .max(1) in the
size arithmetic two lines below -- the guard above it meant .max(1) never
saw a zero. Read the whole function, not the lines you went looking for.
Control run before the change: if 0 meant "unset", something on the disc
would use it throughout. Nothing does.
elements with a keyframe group 15 493
at least one keyframe at scale 0 2 166
EVERY keyframe at scale 0 0
zero is a transient the element grows out of 1 762
ptlogo_eff3.t32 runs 0% -> 200%. An "unset" marker no element ever uses
throughout is not a marker.
Both functions now return without drawing when either scale is 0.
Reach: 24 of 24 renders byte-identical across GP_TITLE (all 16 builds),
GP_PAUSE_MENU and GP_OPTIONS -- additive on every screen the port needs.
195 elements have a guessed rest pose at scale 0 and 126 of those have a
non-zero alpha, so the old code painted them; all 126 are in
GP_READY_ROOM.pak, which S1 already declared a no-go. They are
tactical-map sprites at sx=0/sy=100, mid-horizontal-wipe, that the
coercion drew full-width.
So: a real correctness fix with no visible effect on the menu port.
Suite green, 122 passed / 0 failed across 3 suites at commit time.
|
||
|
|
c2c69b02be |
re(ui): size the rest() guess disc-wide, and refute my own proposed fix
Follows up the defect found last iteration: rest()'s dwell fallback is
guessing whenever it runs. Two things were open -- how big it is, and
whether "rest = the last keyframe" is the fix. Both are now answered, and
the second is answered no.
plateau_census.py walks the placement regions directly instead of going
through `screen info --geometry`, which decodes every texture and cannot
do a disc-wide pass in reasonable time. Its control reproduces GP_TITLE
build 7's three fallback elements and names ptlogo_eff3.t32 among them
before counting anything.
elements with a keyframe group 15 493
no plateau -> rest pose is guessed 3 807 (24.57 %)
... current rule returns invisible 1 711 (44.9 %)
... current rule returns scale=0 195 ( 5.1 %)
the two candidate rules agree 1 911 (50.2 %)
195 elements get a rest pose with scale 0%, which is not a pose. And
disc-wide the choice of rule is not cosmetic: the candidates agree half
the time.
But the port's exposure is one element. Across main menu, EXTRAS, title
and the developer splash, 14 elements are plateau-less and the two rules
agree on 13. The single disagreement is palogo_anima_eff.t32.
And "last keyframe" loses there, on a control that needed no new capture:
the splash carries three sibling glows with identical structure and
identical times --
palogo_gamearts_eff 15:a=0 30:a=255 45:a=255 -:a=0 plateau -> visible
palogo_seta_eff 15:a=0 30:a=255 45:a=255 -:a=0 plateau -> visible
palogo_anima_eff 15:a=0 30:a=255 45:a=212 -:a=0 no plateau
-- differing in one byte. "Last keyframe" makes anima alone invisible
while its two siblings stay lit. The capture agrees weakly: box-mean
ratios capture/render are gamearts 0.717, seta 0.723, anima 0.772, and a
glow we drew that the game does not would put anima below its siblings,
not above.
So the defect is measured and the fix is still undecided. Nothing in the
decoder changed.
|
||
|
|
531840b417 |
re(ui): Q1 -- the interpolation law holds, the group timeline does not
Q1's gate asks whether the ramp is linear. It is, and that result stands: it rests on the splash's _eff glows, which reproduce exactly. This adds the part that does not. The test is a calibration, not a fit. Fix the clock on palogo_gamearts_eff -- declared 15-unit fade-in 0@15 -> 255@30 against captured alphas 34,68,102,136,170,204,238, a constant step of 34, giving t = 2f - 171 -- then check that against the glow's own next landmark: its declared hold ends t=45, predicted frame 108.0, observed last full-alpha frame 107. Then apply it to palogo_gamearts in the same bundle and the same frames, with no free parameter left: declared a=232 at t=206 -> frame 188.5, observed alpha 255 declared a= 32 at t=210 -> frame 190.5, observed alpha 255 The logo is still at full alpha nine frames after it should read 32; its fade-out runs ~17 frames late; its declared 80-frame fade-in is never drawn. Not culling -- the same element is submitted down to a=7 on the way out. Calibration-free version: the declared fade-out spends 12 of 16 units dropping 23/255 of the alpha, and the capture has no such plateau. Candidate, offered and NOT adopted: if +36 held the NEXT keyframe's time, the fade-out shape fits (RMS 4.05 vs 12.13, two elements) and the decoder's "last block's time is unreadable" special case disappears -- the last block would simply have no successor. Rejected for now because it explains neither the missing fade-in nor the lateness, and because the _eff elements cannot discriminate between the readings at all (with four blocks the shift only relabels the phases). Decoder unchanged. Also withdrawn, mine, within the iteration: "the _eff glows hold a constant alpha 33". They ramp 34 -> 255 in steps of 34. I printed the series minimum and read it as its range, with a "14 distinct colours" column sitting next to it saying otherwise. |
||
|
|
f11fde51e5 |
re(ui): rotation is not nested-only, and the pivot-anchored scale is measured
Two corrections and one new confirmation, all from finishing the sweep I left running last iteration. Refuted, by my own sweep, within the hour: "rotation appears to live only in nested .rat leaf records". That held for GP_TITLE, GP_BUNK and GP_CHALLENGE -- the three archives the sweep had reached when I wrote it -- and fails on GP_DIALOG and GP_DEBRIEFING_PILOTLOG, which rotate top-level elements. Scoping the claim made it cheap to withdraw, but the sentence should have waited for the sweep. Those top-level cases are the best evidence on the disc, and they show up in `screen info --geometry` without a hex dump. GP_DIALOG build 0: pceff03/pceff04 ramp r = 90 -> 30 -> 10 -> 3 -> 0 while alpha ramps 0 -> 255 and they slide into place -- a swing-in settling upright. Build 6: pzeff02 ramps 43 -> 61 -> 75 -> 90 while scaling 112% -> 200% and fading to 0 -- a spin-out. Rows committed as reference data. New: the pivot-anchored scale term in blit, kf.x - pivot*(scale-100)/100, was implemented and reasoned about but never measured, because every element previously examined sits at 100% scale where the term is exactly zero. The ptloop pair scale 600% and 800%, where it is worth 450 and 630 px. Formula predicts centre y = 360.0 for both; capture measures 359.1 and 360.0. Top-left anchoring predicts 810 and 990. Horizontally it makes t-from-position agree with t-from-alpha to 0.33 / 0.65 units against ~8 without it. That agreement does NOT prove linear interpolation -- both fields were inverted through the same linear map, so a shared easing curve cancels. It shows position and alpha ride one shared parameter. Recorded as such. The disc-wide sweep is still running; the count is incomplete, the existence is settled. |
||
|
|
67fa1a1b0b |
re(ui): decode keyframe +12 as screen-plane rotation in degrees
The rotated quads on the title screen come from the keyframe block after all. The earlier negative -- "every GP_TITLE build 4 element has all three angle words at zero" -- read the right bytes over too small a region: it walked the top-level declaration table, and the rotated elements are the nested leaf records ptloop01.rat / ptloop02.rat. Confirmed against the framebuffer rather than against our own renderer. The two records declare +12 = 30 and -45; the GPU capture submits their quads at +30.26 and -45.28 degrees -- magnitude and sign, two different values. Corroborated by shape in GP_BUNK 117ca14f, where +12 ramps 0 -> 360 with position, scale and alpha constant: a spin in place. Identifying which draw it was needed edge lengths, not bounding boxes: 400x1076 and 400x1444 against pteff03/pteff03a 399x180 at the elements' two different declared scales, 600% (1080) and 800% (1440). The same test names three known-positives in the capture (ptlogo1, ptcopyright, ptbtn00), so it passes its own control. Keyframe gains rotation_deg plus unknown_4/unknown_8, carried rather than dropped. NOT rendered -- ui_layout::blit is axis-aligned only, so the reference renderer and the port will both draw these upright until a rotating blit exists. The census tool ships with the trap that broke its first version: nested RATC blobs are not 4-byte aligned, so an aligned scan found 0/3 of its own control blocks and missed 16 341 blocks. Disc-wide +12 is non-zero in 14.50 % of 83 862 blocks. sylpheed-formats tests, SYLPHEED_DISC set: 131 passed, 0 failed across the 6 suites finished at commit time; the run had not yet completed. |
||
|
|
a98e96b0f0 |
re: the ten states are ONE PHASE of five -- and phase 0 is the splash
Chasing where the event code comes from turned up the level above and corrected my own framing three sections running. sub_821C6458 has exactly one caller, sub_821C7850, and that caller is itself a dispatcher -- on a SECOND, outer state field at this+132, five phases, gated on this+16 == 3. Phase 4 is the title/menu machine I have been decoding. Phase 0 is sub_821C5690, which the iterate3E notes had already fingered as the splash mechanics from a completely different direction. That agreement is the useful check here. So GamePart_Title has two nested state fields: a phase at this+132 choosing which sub-machine runs, and the state at this+136 inside phase 4. Everything I decoded over the last three iterations -- ten states, eighteen edges, the event dispatch -- is phase 4 ONLY. Phases 1, 2 and 3 are untouched. I have called that out in REFUTED against my own earlier wording rather than quietly restating it. And the event code is forwarded, not created: all five phase handlers are called with the same (this, r29, r28), where r28 is sub_821C7850's own third argument passed through untouched. So the event vocabulary is defined at least one level further up, and finding what 3, 5, 8, 10 and 25 mean means going up again. Not done. |
||
|
|
52275ddd53 |
re: state 4's edges are selected by an event code -- 6 of 26 handled
Continuing Q6's last item: the condition on each edge. sub_821C6458's third argument is an EVENT CODE, and state 4 -- reached straight after the main menu is installed, so the input-waiting state -- dispatches on it through a second jump table at 0x821c6b7c, 26 cases. Six are handled and twenty fall through changing nothing: event 0 -> state 0 (TITLE_SCREEN, back to the title) events 3, 5, 8, 25 -> state 8 (LOADING) event 10 -> state 5 So the edges I listed last iteration as "4 -> 0, 5, 8x4" are each now attributed to a specific event, and the input state's graph is complete. One thing I deliberately did NOT promote. The main menu has five items, B returns to the title, and this table has one event to the title, four to LOADING and one elsewhere. That is a tempting fit -- B plus four items that load an external archive plus EXTRAS staying inside GP_TITLE -- and it matches everything measured. It is also a count-match and nothing more: the events are unnamed, nothing shows event 3 is a particular menu row, and state 5 installs no named screen so the EXTRAS half has no support at all. Written down as an observation with that said plainly, because the same shape of reasoning already cost me DIFFICULTY and EXTRA_MENU two iterations ago. Still open: what the event numbers mean, the conditions on edges out of the other states, and what states 1/3/5/6/7/9 do. |
||
|
|
6e92ae4597 |
re: the title's state machine is decoded -- ten states, eighteen edges
Q6's last open piece was which state leads to which, and it turned out to be a plain switch. state = this+136, compared against 9, dispatched through a jump table at 0x821C6498. The ten words at that address disassemble as lwz r16,N(r28) instructions and are nothing of the kind -- they are the table. That is the same "self-reference" I flagged two iterations ago as looking like a jump table; it is one, and the disassembler was decoding its data. Three states install a named screen: 0 is TITLE_SCREEN, 2 is TITLE_MENU, 8 is LOADING. Eighteen transitions, every one a literal li/stw pair into the state field, giving the full graph. It lines up with the behaviour measured weeks of iterations ago without either side knowing about the other: boot reaches state 0 and A opens state 2; B at the main menu returns to the title, and 4 -> 0 is the ONLY edge back to state 0, reached from 2 -> 4; entering a submenu goes through LOADING and comes back, which is 4 -> 8 at four separate sites then 8 -> 2. I have marked that as corroboration and said plainly it is me matching a graph to observations -- the conditions on the edges are not decoded, so nothing here proves which input picks which branch. Still open and written down as such: the condition on each edge, what states 1/3/5/6/7/9 do, and the fact that state 3 is never a destination in this function, so something outside sets it. |
||
|
|
c8c7677f6d |
re: the lookup argument is decoded at 46 of 48 sites -- and it refutes my
own corroboration from last iteration Last iteration I listed a candidate name vocabulary from "upper-case strings the callers reference", marked it amber because those are not proven arguments, and named the check: confirm per call site which string actually lands in r5. Ran the check. It fired. The first extractor FAILED ITS OWN CONTROL -- a backward scan for addi r5 recovered 7 of 48 sites and missed both title sites I had already read by eye, because there the name is built in r27 and reaches r5 via a later mr. Rewritten as a forward register simulation it reproduces all three known sites and recovers 46 of 48, giving 28 distinct names. Two of my own claims die. DIFFICULTY and EXTRA_MENU are NOT arguments at any site -- they are strings that merely live in the same functions, so the "independently corroborated by measurement" line I wrote was an artifact of the loose filter. Only TUTORIAL_MENU survives. And sub_821CC860 is not a screen factory at all: its real arguments include BG, BLACK, FADE, FILE, KEY, PAD, SOUND and GAMMA_RGB. It is a generic name-keyed lookup, mostly config, which the title part happens to call with its three state names. The title finding itself is untouched -- sub_821C6458 really does transition by calling this with TITLE_SCREEN, TITLE_MENU and LOADING. What is gone is the inference that the 28 names are a screen vocabulary. Two METHOD lines, both earned here: strings a function references are not arguments it passes, and a backward scan for a destination register misses arguments staged through a scratch register. |
||
|
|
2b0e66b6ca |
re: confirm, move and back -- every cue the five screens need is now
located, and two of them reproduce Continuing Q8 rather than opening anything new. The gap that mattered was the confirm cue: a menu needs a sound on A, and I had only cursor and cancel. The fix was where I was counting from. The first run started counting streams at the main menu, so the confirm cue had already fired during boot and showed up as "played before any input". Counting from the TITLE instead attributes it cleanly: the A that advances title -> main menu fires the 12288-byte wave at 0x5d6c0, together with the two stereo BGM stems, which is the menu's music starting. So move (0x1ec0, 0.533 s), confirm (0x5d6c0, 1.016 s) and back (0x0ec0, 0.344 s) are all located and decodable. Move and back came back with IDENTICAL head bytes and sizes on a second independent boot, so the dedup-keyed method is stable and those two are now n=2 rather than n=1. Two honest limits recorded rather than smoothed over. The A press both confirms and opens a screen, so its wave could be the cue the vocabulary calls DECIDE or the one it calls SUB_WIN_OPN -- the port gets the sound the game plays, not a name. And left/right fired no new stream, which excludes a DISTINCT invalid cue but cannot exclude them quietly replaying one of the three already heard, because the probe dedups on head bytes. |
||
|
|
b1b2576769 |
re: the UI cues decode -- 0.53 s, 0.34 s and 1.02 s of real audio
Finishing the step I left open last iteration rather than starting something new. Offsets and packet counts were in hand; what was missing was proof they are actually waves. slb_extract_wave.py wraps a (bank, offset, packets, channels, rate) slice in a synthesized XMA1 RIFF, following the layout the Rust decoder already uses. The three located cues decode to 0.533 s, 0.344 s and 1.016 s of mono 48 kHz audio, audible from sample 0, each with the percussive attack-and-decay envelope of a UI blip. Bitrates come out at 12-15 kB/s, about half the stereo BGM rate, which is what mono should be. The control matters more than the results. The SAME wrapper applied to BGM_001's first wave decodes to 173.808875 s -- identical to the duration that bank's own on-disc RIFF header produced back when Q10 was answered. So the header I synthesized is not approximately right, it reproduces a known-good decode exactly, and the cue durations are trustworthy for the same reason. I did not commit the decoded audio. Three commands regenerate it from the disc, and the corpus's job is measurements and tooling rather than extracted game assets. The offsets, the packet counts and the tool are the deliverable. |
||
|
|
0d7199bc22 |
re: the UI sound effects ARE extractable -- retracting "cannot be
extracted", and the tool was in the build all along Two iterations ago I closed Q8 by declaring the SE audio undecodable: Static.slb has no wave boundaries, there is no XACT container anywhere, and I said the index "exists only at runtime" as though that put it out of reach. The disc half of that stands. The conclusion did not. This build of Canary carries a cvar called xma_param_probe, added by this project, whose own comment says it logs each XMA stream's parameters and head bytes so raw sound.pak entries can be matched to real decode params. It has been sitting in the startup CONFIG DUMP of every log I have read this session. Run with it, driving the main menu: a d-pad move spawns a new mono 48 kHz stream of 4 packets / 8192 bytes, and B spawns a different one of 2 packets / 4096 bytes. Searching their logged head bytes in Static.slb finds each at exactly one offset -- 0x1ec0 and 0x0ec0 -- and the two are contiguous, 0x0ec0 + 4096 = 0x1ec0. So the bank is a packed run of whole 2048-byte packets with no delimiters, which is precisely why the seek scan found nothing: there is nothing to find. A wave is (offset, packet count) and nothing else. That splits Q8's binding cleanly. Event -> WAVE is now measured: the port can have the audio. Event -> cue NAME is still a name match on the authors' identifiers, and the page says so. The same run settled something for Q10 too. Sitting on the main menu, TWO stereo 48 kHz streams were decoding simultaneously. bgm-two-stems.md said that observation was what it needed and that this container could not make it; it can, and a music bank's two waves are now measured as simultaneous rather than only inferred. METHOD gets the general lesson, because it cost two iterations: check what instrumentation the local build already has before declaring a question blocked on tooling. |
||
|
|
c8e8dc0427 |
re: what the game actually reads at boot -- config.ini, and which
GameParts exist at all Q6's second half, advanced but not closed, and the negative is the point. config.ini is the disc's ONLY config file -- one find over the whole extract -- and its own Shift-JIS header calls it the "Application / GamePart initial settings table". Its [SYSTEM] section, which that same comment says holds what the game and every game part share, is EMPTY. So the boot order is not in disc-side configuration at all, and that search space is now closed rather than merely unexplored. What the file DOES carry is the language: XC_LANGUAGE_* -> eng/jpn/deu/ fra/esp/ita, defaulting to eng. That is the mechanism behind the EN/JP build pairs in GP_TITLE and the <lang>.pak families -- a question the corpus had described but never traced to its input. Then the registry. Pulling every RegisterToFactory diagnostic string binds 24 of the 29 GamePart ids to a C++ class, and five ids have no registration site: 1, 2, 16, 18, 28. Id 1 is GP_ADVERTISE_DEMO, which agrees with what I measured two iterations ago -- the attract loop is the TITLE replaying ADV.wmv, not a transition into an advertise part. Marked amber, not green: it is an argument from an error message, not from code. Two things fall out for Q4: ids 3 and 4 are the same class (GamePart_SaveLoad, one part with two ids), and the menu buttons' ids now match the executable's own class names rather than a list of table names. Still a name match, one level closer to the code. What is still missing is the transitions themselves, and I say so: the manifest gives the boot-side assets, config.ini the language, the registry which parts exist. What decides to advance is in GamePart_Title's code and that dig has not been started. |
||
|
|
1f822f8b30 |
re: the menu's sound events are named on the disc; the binding to them is
not Q8. The cheapest thing nobody had tried was to look at the SE cue names, and they are semantic: SE_UI_CURSOR (2), SE_UI_DECIDE (3), SE_UI_CANSEL (4), SE_UI_IMPOSI (5, the error buzz), SE_UI_SUB_WIN_OPN/CLS, and SE_UI_SPLASH_IN/OUT. That is exactly the move/confirm/back/error vocabulary the question asks for, named by the authors after the EVENT rather than the sound. 322 SE cues committed as reference data. They all live in one bank: BANK_SE is a single field reading Static.slb, and the disc-wide check agrees -- 0 of the 322 has an entry in FILES, the 5135-path list that names every voice, briefing and BGM bank. I am calling the binding a NAME MATCH, not a measurement, and the page says so in the same breath as it gives the table. It is a stronger name match than Q4's GamePart ids -- these are the authors' own event names -- but nobody has watched the game emit cue 2 on a d-pad press, and this container is muted against a dummy device so there is no audio path to watch either. One new negative, with its reach. Static.slb's 8 353 472 readable bytes contain zero RIFF, zero seek and zero WAVE, scanned over the whole buffer. The boundary marker that works for all 7 620 other banks is absent, so the cue is named, the bank is named, and the wave inside it is not locatable. The UI sound effects cannot be exported yet, and that is a gap in the assets rather than in the naming. The named next step is Pj_Silph.xgs -- the XACT project SETTINGS points at, which IS in sound.pak at TOC 9454, though its 533 bytes carry no XGSF magic and its region's phase says they are probably the previous bank's tail. |
||
|
|
e81dcad632 |
re: the boot intro and the attract loop are the same video, and the
new-game intro has a name Q9, answered twice over -- once from the disc and once from frames I had already captured and not used. The movie manifest's first eight slots are the whole boot-side flow, and the slot key is the role: LOGO1-4 -> logo1-4.wmv (not on the disc, which is why the splash is a screen), ADVERTISE_MOVIE -> ADV.wmv, STAFF_ROLL -> the credits reel, MS00A -> S00A.wmv, MS01A -> S01A.wmv. So the new-game intro is S00A.wmv, decoded -- which is the half I could not test at runtime, because A on NEW GAME hangs the emulator. And there is no separate boot-intro slot: ADV.wmv IS the advertise movie, the boot just plays it first. Confirmed independently by matching 19 attract frames against five candidates -- 15 hit ADV with a playhead that advances monotonically at the sampling rate and ends at 137 s, its full length, with the title back on the next sample. So the attract movie plays to its end; nothing cuts it short. That corrects me. Two iterations ago I recorded the attract movie as "~85 s, so probably not ADV.wmv" -- arithmetic on a start I never observed, since sampling began 39 s in. REFUTED and METHOD both take it, along with the matcher's real failure mode: a near-black frame has no signature, and its runner-up is not evidence. Skippability I did NOT settle, and the corpus contradicts itself: one page says A skips a movie every time, while the boot harness deliberately never taps during one because it breaks the title. Named the one-boot test rather than picking a side. |
||
|
|
b3c8632006 |
re: a music bank is two stems that play together -- not intro+loop, and
not something to concatenate Q10, and it starts by withdrawing the question's own premise. BGM_001 is not three sub-waves of 10 KB / 4.47 MB / 4.67 MB: the 10 KB is the bank header. A bank is exactly TWO waves, and across all 32 BGM banks on the disc the two always have the SAME duration -- equal to 0.01 s over lengths from 37 s to 277 s. That alone kills intro+loop and kills two halves of one piece, both of which require unequal lengths. Four banks appear to break the rule and do not: BGM_106-109 are the known leading-region straddle, and the giveaway is that the entry named BGM_107.slb contains BANK id=1108. The seek packet counts pin each join exactly, so they realign to the same two-equal-waves shape rather than being dropped as noise. That trap goes in METHOD. Then the roles, by decoding both waves to PCM. They are sample-synchronous: transient-envelope correlation searched over +/-5 s peaks at lag +0.00 s, and both waves stop at the same millisecond, 167.663 s. Two stems of one performance, meant to sound at once. Wave 1 is quieter, has almost no bass and is far more L/R-decorrelated, which reads as a surround-rear pair or a second intensity layer -- I cannot separate those two from the file, and say so: ChannelMask is 0x0002 on both, and this game's channel metadata is already documented as meaningless. Two things the port needs that are NOT on the disc, both marked as authored: the track is not a seamless loop (BGM_001 fades out and is followed by 6.15 s of silence, no loop-point field found), and nothing names which bank the menu plays -- all 32 BGM cues are numeric. |
||
|
|
03ece95c06 |
Merge branch 'auto/idxd-unnamed-keys'
# Conflicts: # docs/re/INDEX.md |
||
|
|
245b73243b |
Merge branch 'auto/isl-builtins-26-28-29'
# Conflicts: # docs/re/INDEX.md # tools/re-capture/isl.py |
||
|
|
23a6cfd979 |
re: the EF_IDX_ prefix - ptc_pack has 727 names, and the map reaches 128 of 137
Censusing ptc_pack's own naming vocabulary turned up a third variant of the prefix trap, and this one had been corrupting a number the corpus carried. 268 of ptc_pack's names do not start with eff_ at all. They start with EF_IDX_, as in EF_IDX_eff_d001_f. A regex anchored at eff_ chops that prefix off and merges distinct names, which is exactly where the earlier figure of 532 came from. Enumerating maximal [A-Za-z0-9_] runs gives 727. The two earlier traps were a STORED name being longer (rot_n001_break) and a BOUND name being a prefix (eff_f0002 inside eff_f0002_barnhaze); this is the third - a prefix the pattern cannot see at all, because its anchor sits in the middle of the real name. Looking each bound name up bare AND under EF_IDX_ resolves 25 of the 34 that were unlocated. The map is now 128 of 137, and the residual is 9, small enough to print: eff_e0044, eff_f0002, eff_f0002_barn, eff_h308, eff_j002_e01, eff_j002_e02, eff_m010_wep_85, eff_m011_wep_85, eff_n0071. All 17 eff_l### are among the recovered. This withdraws my own previous correction. I had recorded Base.xpr (53) as holding more bound effects than ptc_pack (46), and struck out "ptc_pack is the effect library". With the prefixed keys counted ptc_pack holds 71 - it IS the larger library, and the 46 was an undercount from the same truncating pattern. Two shared libraries remains right; which one is bigger does not. The suffix vocabulary: 106 distinct tokens over the 727 names - IDX 223 (the prefix above), _f 137, _e 119, _root 87, _col 54, _mdl 45, _break 43, _ring 38, _ALL 17, _haze 14, _thunder 10. That census counts ALL tokens rather than trailing ones, which is precisely how the EF_IDX_ PREFIX surfaced inside what I had first labelled a suffix list - the mislabel found the bug. Testing the structural candidates the way _hangar was tested, does the suffixed name have a bare parent: _ALL 17 names 17 of 17 _root 87 64 of 87 _break 30 15 of 30 _e 74 0 of 74 _f 61 0 of 61 _root is strictly terminal - 87 of 87, and it never appears mid-name. The compound shapes put it outermost: _e_root 19, _f_root 18, _break_root 13, bare _root 30. So the order is <stem>_[<faction>|<break>]_root and _root reads as a hierarchy marker rather than a variant - though 64 of 87 having a bare parent means it is not simply the parent of an existing node, and _break at 15 of 30 is likewise not a plain destroyed-twin-of-everything. _e/_f never have a bare parent, 0 of 135. That is independent asset-side confirmation of the faction law: an effect is authored per faction and there is no faction-neutral original for either side to derive from. effect-homes.txt changes 5/30 and every line pairs: five values changed (103->128, 34->9, ptc_pack 46->71 and its sort position, the residual header, 3-digit 80->105 of 110) plus 25 pure deletions, exactly the 25 recovered names. All are 3-digit, so the 4-digit line is unchanged at 23 of 27. The other sixteen artefacts are byte-identical. |
||
|
|
1265512880 |
re: _e/_f on an effect name is the binding unit's FACTION (94 of 94)
Chasing the 17 unlocated eff_l### turned up their shape first: they come in
_e/_f PAIRS - eff_l101_e + eff_l101_f, and the same for l102, l104, l105, l106,
l201, plus _e-only l010/l011/l107/l108 and _f-only l002.
Partitioning every eff_<letter><digits>_<e|f> binding by the ID letter of the
OWNING unit (one GP_MAIN_GAME_* pak = one user):
effect _e effect _f
UN_e### 33 0
UN_f### 0 61
94 of 94 agree and both off-diagonal cells are empty. The control reads the
factions straight off the IDs: UN_e### -> ADAN (42 objects), UN_f### -> TCAF
(26), UN_n### -> TTRL (2, tutorial, binding neither). So an effect ending _e
belongs to an ADAN ship and one ending _f to a TCAF ship - the same visual is
authored twice, once per faction, which is exactly why eff_l### arrives in pairs.
What the 34 unlocated ARE is now also clear, even though where they live is not.
They are one job, not a scatter: Generic binds 32 of the 34, Explosion 19,
Shell 9, Level_0 and Weapon 2 each. The binder fields rank LowerHPFxModel 252,
HitFxModel 144, then JetFxModel_00N and AfterBurnerFxModel_00N. They sit in the
six GP_MAIN_GAME_* paks at 130 bindings each plus 32 in DefTables.pak. Since
LowerHPFxModel is the damaged-ship effect, the residual is largely the
per-faction battle-damage and hit visuals. None of the 34 is a record name and
only one is a field name, so they are asset references.
Stated plainly: they remain unlocated AS ASSETS. Knowing the family and its
naming law does not say where the geometry lives - the .xpr route is exhausted
for them and the parsed pak payloads hold references, not meshes.
Also fixes a defect in the artefact shipped last commit. effect-homes.txt came
back with two equal-count lines swapped: Counter.most_common() breaks ties by
insertion order, so the package listing was not deterministic. Now sorted by
(-count, name) and verified to regenerate byte-identical twice running. This is
the corpus's own rule - any map built by iterating a set or Counter needs
sorted() - and the new tool had violated it.
The other sixteen artefacts are byte-identical; effect-homes.txt changes only in
the tie-break ordering of the five 1-count rows, with every line pairing.
|
||
|
|
e72f0f14be |
re: the effect->package map, 103 of 137 - and eff_f0002 was a substring artefact
Enumerating eff_* names per .xpr across all 166 packages and matching the bound names EXACTLY gives a real home for 103 of the 137, up from the 71 ptc_pack alone accounted for. Only 36 packages carry an effect name at all, and two dominate: Base.xpr 53 ptc_pack.xpr 46 Stage_S28.xpr 2 five rou_f001_wep_NN.xpr 1 each So there are TWO shared effect libraries, not one - and ptc_pack.xpr is the only *_pack bundle on the disc, so no third shared library is hiding. By digit-width: 3-digit 80 resolved of 110, 4-digit 23 of 27. The previous iteration's split survives and sharpens - the four-digit series really does live outside ptc_pack (that zero stands), and now we can say where: Base.xpr. Correction to the previous commit. It reported eff_f0002 and eff_f0002_barn as present in Base.xpr. Both were SUBSTRING artefacts: what the file actually holds is eff_f0002_barnhaze, one longer resource name that grep -l eff_f0002 and grep -l eff_f0002_barn each match inside. Neither bound name is there. This is the corpus's own paid-for prefix lesson arriving from the other direction - last time it was rot_n001 vs rot_n001_break with the stored name longer; here the BOUND name was the prefix. The new map is exact-keyed and does not have this failure mode, so the earlier positive is withdrawn. 34 names remain unlocated, dominated by a family the last pass did not single out: eff_l### with 17 of the 34, then h 4, s 4, j 2, m 2, t 1, and four four-digit names - eff_e0044, eff_f0002, eff_f0002_barn, eff_n0071. Scope note worth keeping: the j 22 / t 14 clustering reported last time was the residual against ptc_pack ALONE; against all packages those families are largely accounted for and l is what is left. Both numbers are right for their own population, which is exactly why a residual has to say what it was measured against. New artefact with its regenerator: tools/re-capture/effect_homes.py -> docs/re/data/effect-homes.txt, which lists all 34 by name. All sixteen existing artefacts byte-identical. |
||
|
|
fa8a538c33 |
re: the disc ships the unit datasheet's own SCHEMA (and 文字列 was not a placeholder)
This corrects the previous commit. 文字列 is not a developer's leftover: it is one member of a six-word Shift-JIS TYPE vocabulary, and the records carrying it are a machine-readable schema for the unit datasheet. The whole non-ASCII population on the disc is 6 distinct values out of 99328 - 0 of 3496 record names and 0 of 12173 field names - and all six are type words: 文字列 string 366 uses NS_"文字列" NS_ string 12 整数 / 整数値 integer 66 / 6 浮動小数値 floating-point value 504 浮動小数値[0〜1] float in [0,1] 36 990 type-valued fields. So the reader defect noted last time is real but bounded to these six strings, and name_hash re-encodes Latin-1 byte-for-byte, so hashing was never affected. They sit in 15 records x 6 GP_MAIN_GAME_* paks = 90 instances, i.e. 15 records with ONE user. The names are exactly the unit substructure family, and six carry a literal wildcard: Turret_???, Hatch_???, Bridge_???, Thruster_???, ShieldGenerator_???, Versatile_???, and NS_*. ??? is the numeric-suffix wildcard at record AND field level - Turret_??? is the schema for Turret_000..00N, and inside it CannonFrame_??? / MuzzleFrame_??? stand for the numbered slots. Where a field's type is an enumeration the schema holds an EXAMPLE value instead of a type name: Yes for the five booleans, Vessel for Generic.Type (the 43 Craft + 71 Vessel split), Ship_ for the ID prefix convention. Maneuver is the one fully-typed record, 34 of 34. Every ResistanceTo* and every Color_* channel is declared FLOAT[0..1] - normalised by declaration, matching the sampled values in unit-datasheet-static. Generic.NozzleSpec_??? has its own type NS_"文字列" and NS_* is a record, so the nozzle spec is a nested sub-schema. Control separates schema from data cleanly: the _??? records and NS_* exist ONLY as schema, 6 of 6 instances typed, while the eight real substructure names are typed in 6 instances and untyped in the rest - Generic 6 of 3651, the others 6 of 684 each. Turret_??? carries the game's own typo NomalModel beside DamagedModel. This gives the port an authoritative field-type table: types the disc declares, rather than types inferred from sampled values. New artefact with its regenerator: tools/re-capture/datasheet_schema.py -> docs/re/data/datasheet-schema.txt. All fifteen existing artefacts byte-identical. |