port: the PRESS (A) plate could not be drawn at any instant -- four faults, and a misquoted number

1. --time= was silently ignored on any screen with a settle window >= 30 units:
   pose_at overwrote the requested instant with settle_instant. ScreenView.frozen
   now marks an explicit instant and skips both clamps.

2. press_start's settle window was [0,214] -- the dead stretch BEFORE the plate
   exists -- so its settle instant was t=107, where the element is alpha 0. The
   exporter now rejects intervals in which nothing is visible. title keeps
   [160,236], the interval the Decoder's draw stream confirmed.

3. My authored looping_focus_records entry for press_start/ptbtn00 drew a dim
   focus record INSTEAD of the plate's own sprite: max 0 vs max 252.5. Deleted --
   an authored guess that overrides a decode with a worse answer is removed.

4. verify-capture passed --time=5.9617 for the title and it was never applied.
   Every title figure it has printed, including the 0.26% quoted to the Decoder,
   was measured at the settle instant under a note claiming t=357.7. Both rows now
   pose by omission and the note matches. title is 0.21% honestly; splashes
   unchanged at 0.01%.

The boot's end artifact now contains the plate (region mean 95.7 vs 33.6).
Corrects last iteration's BLOCKED row, which had the entry's effect backwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
Sylpheed port agent
2026-08-30 00:24:33 +00:00
parent 09188ccbb2
commit 3c71962698
7 changed files with 210 additions and 71 deletions

View File

@@ -73,73 +73,37 @@
],
"looping_focus_records": {
"_": [
"WHICH focus records the port draws unconditionally, and loops. CHOSEN, and",
"deliberately a LOOKUP rather than a rule -- delete an entry the day the disc",
"or a capture states it.",
"WHICH focus records the port draws unconditionally, and loops.",
"",
"THE PRESS (A) PLATE GLOWS AND PULSES. Measured on the running game:",
"`ptbtn00f`, the plate's focus record, ramps alpha 0x00->0x50 and back, and",
"its period was timed four times at 2.12 / 2.19 / 2.34 / 2.31 s. You cannot",
"measure a period unless the thing repeats, so THAT IT LOOPS IS MEASURED. The",
"port drew nothing at all here, because press_start has no `buttons` and",
"nothing is focused.",
"🔴 EMPTY AS OF 2026-08-30, and the deletion is the finding.",
"",
"WHY A LOOKUP AND NOT A RULE, which is the part worth keeping. The spinning",
"ring is a RULE in the renderer (`spin_period_units`) because it has a",
"disc-wide check: 16 of 212 elements match its shape and all 16 are focus",
"rings, zero false positives. The obvious rule for a pulse -- a group whose",
"keyframes vary only in alpha and whose first and last alpha are equal -- was",
"CENSUSED BEFORE BEING WRITTEN and it fails: 82 OF 212 ELEMENTS MATCH,",
"including ptcopyright, palogo_sqex, ptmsg and every _eff fade-in. A renderer",
"rule on that shape would make the copyright notice pulse.",
"The one entry here -- `press_start/ptbtn00`, period 120 -- was AUTHORED to",
"give the PRESS (A) plate a glow on a screen that has no buttons and focuses",
"nothing. Measured, it did the opposite: it made the plate INVISIBLE.",
"",
"Narrowing to focus records leaves exactly ONE distinct element (ptbtn00f,",
"plus its JP twin), and a rule justified by n=1 is a special case wearing a",
"rule's clothes. So it is written here, per element, by name."
],
"press_start/ptbtn00": {
"record_element": "ptbtn00f",
"period_units": 120,
"kind": "chosen",
"why": [
"120 units, and it is DECODED rather than chosen -- but it is still written",
"here because the field is not in the pinned crate yet. DELETE THIS the day a",
"tag exposes it and the exporter can emit it.",
"",
"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. A record's 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 before repeating.",
"The five `ptbtn0Nf` records on the main menu fill their 120 exactly, which is",
"what shows the slack belongs to this record and not to the format.",
"",
"Disc-wide over 1781 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.",
"",
"AND IT SURVIVES THE OBJECTION THIS ENTRY RAISED, using THIS PORT's number.",
"Both candidate periods need the same emulator pacing factor, and the ring",
"measures it independently: declared 120 units -> 2.177 s = 1.0885.",
"",
" 105 units = 1.750 s nominal, needs a factor of 1.211-1.337 -> EXCLUDES it",
" 120 units = 2.000 s nominal, needs a factor of 1.060-1.170 -> CONTAINS it",
"",
"So 105 cannot reach the corpus's measured 2.12-2.34 s under any pacing the",
"ring also satisfies. At 120 the prediction is 2.177 s. Different elements,",
"different bundles, separate runs, tied only by both declaring 120.",
"",
"HISTORY, because the sequence is what makes this checkable: this entry read",
"129 (last keyframe 105 + the since-deleted exit_ramp_units), then 105 (the",
"group length under the corrected layout, which I shipped knowing it was 17%",
"short of every measurement). The 123-vs-129 pair I once called unseparable",
"STRADDLED the right answer without containing it.",
"",
"The ~3% between 2.177 and the corpus's 2.24 mean is four wall-clock samples of",
"a 2 s period; the Decoder looked for a further declared hold and found none."
]
}
"`_draw` treats a looping focus record the way it treats a focused button --",
"it draws the focus record INSTEAD of the base sprite and moves on. For a",
"focused button that is right and measured (the focused sprite covers the",
"base at 100.0 % of base-visible pixels). For the plate it substituted a dim",
"glow at the wrong phase for the element's own bright sprite:",
"",
" with the entry without it",
" press_start t=236 max 0 max 252.5",
" press_start t=240 max 0 max 252.5",
" press_start t=250 max 15.3 max 252.5",
"",
"So the plate could not be seen at any instant, on the screen whose entire",
"content is the plate. The element's own fade is DECODED -- alpha 255 at",
"t=236-238 -- and this authored guess was overriding decoded data with a",
"worse answer. That is the wrong way round, and an authored entry that",
"suppresses a decode is deleted rather than tuned.",
"",
"⚠️ What is NOT claimed: that the plate has no glow. The focus record",
"`ptbtn00f.rat` exists and may well be drawn ON TOP of the base in the real",
"game. Drawing both would be a rendering rule nobody has measured, so the",
"port draws what the disc's own fade says and leaves the glow out. See",
"docs/port/BLOCKED.md -- a capture of the idle title settles it."
]
},
"exit_ramp_deleted_why": [
"DELETED 2026-08-29, and the deletion is the point.",

View File

@@ -651,9 +651,33 @@ fn settle_window(elements: &[Element]) -> Option<[i64; 3]> {
if times.len() < 2 {
return None;
}
// 🔴 A GAP IN WHICH NOTHING IS VISIBLE IS NOT A SETTLE WINDOW.
//
// The widest keyframe-free interval is only a settled state if the screen is
// actually PRESENTING something across it. `press_start` is the case that
// proves it: its keyframes are 0, 214, 236, 238, 244, so the widest gap is
// 0..214 -- the dead stretch BEFORE the plate appears, where `ptbtn00` is
// alpha 0 throughout. Taking its midpoint gave a settle instant of t=107,
// and the runtime then answered every question about that screen at t=107.
// The result was that the PRESS (A) plate could not be drawn at any instant
// at all, including the boot's own end state, whose entire purpose is to
// show it.
//
// The fix is not a tuned threshold: it is that the heuristic was reading an
// interval where the screen is BLANK as the interval where it has arrived.
// Rejecting those leaves `press_start` with 214..236 (22 units), which is
// under the runtime's 30-unit bar, so it falls back to each element's own
// hold -- which is the plate, opaque, exactly as the disc declares it.
//
// ⚠️ This does not disturb the windows the settle instant was measured on.
// `title` keeps [160, 236]: elements are visible across it, and the
// Decoder's draw stream independently found the game's clock freezing in
// that same interval.
let visible_at = |t: i64| elements.iter().any(|e| alpha_at(e, t) > 0);
let (a, b) = times
.windows(2)
.map(|w| (w[0], w[1]))
.filter(|(a, b)| visible_at((a + b) / 2))
.max_by_key(|(a, b)| b - a)?;
Some([a, b, (a + b) / 2])
}

View File

@@ -536,6 +536,14 @@ pulses on a period (`authored/timing.json` already carries a *speculative*
what a pulse would need); or it genuinely blinks once and vanishes, in which case
the port is right and this closes ✅.
⚠️ Note the port already draws a looping focus record for this element. If the
answer is "blinks once", **that entry is wrong and should be deleted**, not kept
as harmless — it is the only thing making the plate reappear at all.
🔴 **Correction, 2026-08-30.** This row previously said the looping focus record
was "the only thing making the plate reappear at all". The opposite was true: the
entry was the only thing making it **disappear** — it drew a dim glow *instead of*
the plate's own sprite, max 0 against max 252.5. It has been deleted, and the port
now draws the plate from its own decoded fade. See `DECISIONS.md`.
⚠️ So the question narrows rather than closes. The port shows the plate at its
declared instants and holds it. What a capture would still settle is whether the
real plate **pulses** after that, and whether `ptbtn00f.rat` — the focus record,
which the port now draws not at all — is a glow layered *over* the plate. Drawing
both would be a rendering rule nobody has measured.

View File

@@ -5828,3 +5828,95 @@ did not match the flag I passed, and that was visible in my first `ls`.** A
verbatim re-run produces six correctly-named per-step shots. I do not know where
those 410 files came from, which is itself the point: I drew a conclusion from a
file set whose provenance I never checked.
## The `PRESS Ⓐ` plate: four bugs in a row, and a number I have been misquoting
Last iteration I filed that the plate was visible for 8 units and simply missed by
the boot's capture instant. That was wrong in the direction that matters: **the
plate could not be drawn at any instant at all**, and three separate faults had to
be removed before it appeared. The fourth is a correction to figures I have quoted
to the Decoder repeatedly.
### 1. `--time=` was silently ignored on half the screens
`pose_at` did `if holding: t = settle_instant`. The requested instant was
**discarded** on every screen with a settle window ≥ 30 units — `title`,
`press_start`. The flag parsed, the log printed the time asked for, and the pose
came from somewhere else entirely.
`ScreenView.frozen` now marks an explicitly pinned instant and skips both clamps.
### 2. The settle window picked an interval where nothing is visible
`press_start`'s keyframes are 0, 214, 236, 238, 244. The widest keyframe-free gap
is **0…214** — the dead stretch *before* the plate exists, where `ptbtn00` is
alpha 0 throughout — so its settle instant was **t=107**, and every question about
that screen was answered there.
🔴 **A gap in which nothing is visible is not a settled state.** The exporter now
rejects those intervals. `press_start` becomes [214, 236] — 22 units, under the
runtime's 30-unit bar — so it falls back to each element's own hold, which is the
plate, opaque, as the disc declares it.
⚠️ It disturbs no window the settle instant was measured on: `title` keeps
[160, 236], the interval the Decoder's draw stream independently found the game's
clock freezing in.
### 3. An authored entry of mine was suppressing the decode
Even then the plate stayed dark, because `authored/timing.json`'s
`looping_focus_records` entry for `press_start/ptbtn00` made `_draw` take the
focus path — which draws the focus record **instead of** the base sprite:
| | with the entry | without |
|---|---|---|
| `press_start` t=236 | max **0** | max **252.5** |
| t=240 | max 0 | max 252.5 |
| t=250 | max 15.3 | max 252.5 |
I authored that entry to give the plate a glow. It substituted a dim glow at the
wrong phase for the element's own bright sprite, on the screen whose entire
content is that sprite. **Deleted** — an authored guess that overrides decoded
data with a worse answer is removed, not tuned. The glow is not claimed either
way; drawing both would be a rendering rule nobody has measured.
✅ The boot's end artifact now contains the plate: mean **95.7** in its region
against **33.6** for the title art alone, and the overlay reports `drew 1:
ptbtn00` where it reported `drew 0`.
### 4. 🔴 `verify-capture` has been measuring a different pose than it reported
It passed `--time=5.9617` for the title — t=357.7 units, the Decoder's refined
sweep fit — and **that value was never applied**, because of fault 1. Every title
figure this tool has printed, including the **0.26 %** I have quoted to the
Decoder more than once, was measured at the **settle instant, t=198**, under a
note claiming t=357.7.
Honouring the flag made it visible: t=357.7 is past the title's own group, which
ends at t=269, so the whole screen posed at its faded-out final keyframes and the
disagreement went to **30.97 %**. The instant was only ever meant for the `ptloop`
leaf, which runs to t=600 and is looped separately by `loop_leaf`. Applying it to
the whole screen was always wrong and was harmless only while it was ignored.
The splashes had the same shape: `--time=99` was an idiom for "settled" that
worked only because it was discarded. Both rows now pose by omission, and the
tool's note says what it does.
| | before | now |
|---|---|---|
| `title` | 0.26 % *(labelled t=357.7, actually t=198)* | **0.21 %** at t=198, labelled t=198 |
| `publisher_logo` | 0.01 % | 0.01 % |
| `developer_logos` | 0.01 % | 0.01 % |
| `main_menu` | 0.07 % | 0.08 % |
| `extras` | 0.19 % | 0.19 % |
The agreements were real; the **stated pose was not**. Corrected with the
Decoder, since they have those numbers.
### The flag audit that started it
All 16 flags `boot.gd` parses were exercised for an observable effect after last
iteration's two silent ones. `--pose=rest` (max 111 against the timeline),
`--play` (enters the menu), `--no-hold` (max 255 on two screens) all pass. `--time`
was the one that did not, and it took a screen whose content is a single late
spike to make the failure visible.

View File

@@ -241,6 +241,8 @@ func _ready() -> void:
if args.has("time"):
_frozen = true
# An explicit instant beats the settle instant -- see `ScreenView.frozen`.
view.frozen = true
view.time_units = float(args["time"]) * view.units_per_second
view.queue_redraw()
@@ -271,6 +273,7 @@ func _ready() -> void:
_overlay_due = 0.0
_overlay_process(0.0)
if overlay != null and args.has("time"):
overlay.frozen = true
overlay.time_units = float(args["time"]) * overlay.units_per_second
overlay.queue_redraw()

View File

@@ -77,6 +77,26 @@ var exit_ramp_units: float = 24.0
var settle_instant: float = -1.0
const SETTLE_WINDOW_MIN := 30.0
## Set when a caller pinned an EXPLICIT instant (`--time=`), which then wins over
## `settle_instant`.
##
## 🔴 Without this, `--time=` was silently ignored on every screen with a settle
## window of 30 units or more, because `pose_at` overwrote the requested `t` with
## `settle_instant` whenever `holding` was true. The flag parsed, the log printed
## the time asked for, and the pose came from somewhere else.
##
## `press_start` is the case that exposed it. Its window is [0, 214] -- the long
## dead stretch BEFORE the plate appears -- so its settle instant is t=107, where
## `ptbtn00` is alpha 0. The plate's only opaque frames are t=236-238. The result
## was that the `PRESS (A)` plate could not be rendered **at any time at all**:
## every instant anyone asked for was answered at t=107, and the screen came back
## empty with `ptbtn00 (transparent at rest)`.
##
## The settle instant is still right for a screen that has ARRIVED and is sitting
## there, which is what it was measured for. It is not right as an answer to a
## question about a different instant.
var frozen := false
var looping_focus: Dictionary = {}
## Element ids whose nested `.rat` leaf the runtime actually draws.
@@ -225,7 +245,9 @@ func pose_at(element: Dictionary, t: float) -> Dictionary:
return frames[0] if not frames.is_empty() else element.get("rest", {})
# While holding, stop at the hold: past it the group is ramping out, and a
# screen that has arrived and is sitting there is not leaving.
if holding:
# `frozen` means a caller pinned an EXPLICIT instant and wants THAT instant,
# not the settled pose and not a per-element clamp. Both clamps are skipped.
if holding and not frozen:
# One instant for the whole screen where the disc gives a wide enough
# window; otherwise each element's own hold, which is what this port did
# everywhere until 2026-08-29.

View File

@@ -95,10 +95,36 @@ for row in "${MAP[@]}"; do
"--shots=$OUT/$name" >"$OUT/$name.log" 2>&1 || true
[ -f "$OUT/${name}_00_start.png" ] && cp "$OUT/${name}_00_start.png" "$OUT/$name.render.png"
elif [ "$pose" = t357 ]; then
godot --path port --resolution 1280x720 -- "--screen=$name" --time=5.9617 \
# 🔴 NO `--time` HERE EITHER, and the row's note used to claim otherwise.
#
# It passed `--time=5.9617` (t=357.7 units, the Decoder's refined sweep fit)
# and that value was NEVER APPLIED: `pose_at` replaced it with the screen's
# settle instant, t=198, on every run. Every title figure this tool has ever
# printed -- including the 0.26 % the port has quoted repeatedly -- was
# measured at the SETTLE, under a note saying t=357.7.
#
# Honouring it now makes that visible: t=357.7 is PAST the title's own group,
# which ends at t=269, so the whole screen poses at its faded-out final
# keyframes and the disagreement goes to 30.97 %. The instant was only ever
# meant for the `ptloop` LEAF, which runs to t=600 and is looped separately
# by `loop_leaf` (authored/rendering.json). Applying it to the whole screen
# was always wrong; it was harmless only while it was ignored.
#
# So: pose at the settle, which is what was actually being measured, and let
# the leaf loop carry the sweeps' phase.
godot --path port --resolution 1280x720 -- "--screen=$name" \
"--capture=$OUT/$name.render.png" >"$OUT/$name.log" 2>&1 || true
else
godot --path port --resolution 1280x720 -- "--screen=$name" --time=99 \
# NO `--time`. It used to pass `--time=99` as an idiom for "settled", and
# that worked only because `--time` was SILENTLY IGNORED on a screen with a
# settle window: `pose_at` overwrote the requested instant with
# `settle_instant` whenever `holding` was true. The tool asked for t=5940
# units and was handed the settle instant, which is the pose it actually
# wants -- and the 0.01 % agreements on both splashes were measured through
# that accident. Now that `--time` is honoured, asking for it explicitly
# would pose past the end of every group, so the request is simply dropped
# and the settled pose asked for by omission.
godot --path port --resolution 1280x720 -- "--screen=$name" \
"--capture=$OUT/$name.render.png" >"$OUT/$name.log" 2>&1 || true
fi
[ -f "$OUT/$name.render.png" ] || { printf '%-17s %s\n' "$name" "render failed"; continue; }
@@ -109,7 +135,7 @@ for row in "${MAP[@]}"; do
note=""
case "$name" in
main_menu|extras) note="rendered with authored initial focus" ;;
title) note="posed at t=357.7, the Decoder's refined sweep fit" ;;
title) note="posed at the SETTLE (t=198); see the note in this file" ;;
esac
# Where the difference lives. This comes FIRST because it is what the gamma
# sweep has to be protected from.