port: the rest() flash defect reaches all four splashes I ship; the port survives it

The Decoder censused rest()'s dwell fallback from the file side: 2305 elements
have no plateau, 1697 of those get a visible pose, and in GP_TITLE all four
visible fires are on the splash screens this port ships. Confirmed in my export.

Refinement to their description: they named the [0:a0 15:a255 30:a212 45:a0]
shape, but palogo_gamearts_eff and palogo_seta_eff hold 255 through t=30, so
their fallback lands on the flash PEAK rather than its decay. Same defect, worse
pose.

The port ships the right frame and there is now a number for it. Publisher splash
against the committed oracle capture: timeline (shipped) RMSE 2.17 / 0.01%
differing; --pose=rest 9.05 / 0.75% -- 75x the differing area on a screen I ship.
So the rule added to verify-screen's header after the title_jp mistake
generalises, and is demonstrated against an oracle rather than argued.

What did need fixing: ScreenView logged '(transparent at rest)' for every skipped
element whatever instant it posed, so it said that about palogo_sqex_eff, whose
resting alpha is 212. That is the same rest-versus-posed-instant confusion that
cost a wrong conclusion, pre-printed in the log. It now names the instant.
Controlled both ways: timeline says 'at t=6' and skips the flash, --pose=rest
still says 'at rest' and draws it.

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 11:53:01 +00:00
parent 835acf930e
commit 955f5b0c02
2 changed files with 75 additions and 2 deletions

View File

@@ -9,7 +9,7 @@ dies, which is what this file is for.
<!-- INDEX: generated by tools/port/index-decisions -- do not hand-edit -->
149 sections. Search this before re-deriving anything.
150 sections. Search this before re-deriving anything.
* [P0 — the exporter, 2026-08-28](#p0--the-exporter-2026-08-28)
* [P1 — Godot draws the screen, 2026-08-28](#p1--godot-draws-the-screen-2026-08-28)
@@ -160,6 +160,7 @@ dies, which is what this file is for.
* [🔴 `verify-screen` was nondeterministic, and it looked fine most of the time](#verify-screen-was-nondeterministic-and-it-looked-fine-most-of-the-time)
* [The JP title capture adjudicates `title_jp` — and it goes against the port](#the-jp-title-capture-adjudicates-title_jp--and-it-goes-against-the-port)
* [🔴 CORRECTION: the port did not move away from the game — I scored the wrong frame](#correction-the-port-did-not-move-away-from-the-game--i-scored-the-wrong-frame)
* [The `rest()` flash defect reaches four screens I ship — and the port already survives it](#the-rest-flash-defect-reaches-four-screens-i-ship--and-the-port-already-survives-it)
<!-- /INDEX -->
## P0 — the exporter, 2026-08-28
@@ -8695,3 +8696,60 @@ would score the port against a 45 px shift. With it, `title_jp` reads **RMSE
⚠️ The row prints `no capture` until the Decoder's branch merges. Their capture is
theirs to commit; it was staged locally to test the row and removed.
## The `rest()` flash defect reaches four screens I ship — and the port already survives it
The Decoder censused it from the file side while I was looking at one instance:
of 13 991 elements with ≥2 keyframes, **2 305** have no plateau so the dwell
fallback decides, and **1 697 (74 %)** of those get a *visible* pose. In
`GP_TITLE`, 5 fires and 4 are visible — **all four on the splash screens this
port ships**.
✅ Confirmed in my own export, and it is exactly the JP-title shape on different
screens:
| element | keyframes | `rest` |
|---|---|---|
| `palogo_sqex_eff` | `0:a0 15:a255 30:a212 45:a0` | t=30, **a=212** |
| `palogo_anima_eff` | `0:a0 15:a255 30:a212 45:a0` | t=30, **a=212** |
| `palogo_gamearts_eff` | `0:a0 15:a255 **30:a255** 45:a0` | **t=15, a=255** |
| `palogo_seta_eff` | `0:a0 15:a255 **30:a255** 45:a0` | **t=15, a=255** |
📌 **A refinement to their description**, which named the `212` shape: two of the
four hold **255 through t=30**, so their fallback lands on the flash's *peak*
rather than its decay. Same defect, worse pose — full brightness, not
four-fifths. The logos themselves (`palogo_sqex` holds 255 from t=30 to t=235)
have a real plateau and are unaffected.
### The port ships the right frame, and now there is a number for it
Both poses of the publisher splash against the **committed oracle capture**:
| pose | RMSE | differing |
|---|---|---|
| **timeline — what the port ships** | **2.17** | **0.01 %** |
| `--pose=rest` — the harness frame | 9.05 | 0.75 % |
🔴 **75× the differing area on a screen this port ships.** So the rule I wrote
into `verify-screen`'s header after getting it wrong on `title_jp` is not a
special case — it generalises, and here it is demonstrated against an oracle
rather than argued.
✅ The port's settled pose evaluates `pose_at(hold)`, not `rest`, so it skips the
flashes and agrees with the capture at 0.01 %. The defect is confined to the
harness pose. **Nothing shipped is wrong; nothing needed fixing in the render.**
### What did need fixing: the port said "at rest" about a pose it never looked at
`ScreenView` logged `"%s (transparent at rest)"` for every skipped element,
whatever instant it had posed. On the timeline path the pose is
`pose_at(time_units)` — so it reported `palogo_sqex_eff (transparent at rest)`
about an element whose **resting alpha is 212**.
⚠️ That is not cosmetic. The rest-versus-posed-instant confusion is precisely what
made me score a `--pose=rest` frame against a capture and write up a drift that
did not exist. A log line that erases the distinction is that error pre-printed,
waiting to be believed. It now names the instant: `transparent at t=6`.
Controlled both ways on one screen: timeline → `transparent at t=6` and the flash
skipped; `--pose=rest` → still `at rest`, and the flash **drawn**.

View File

@@ -640,7 +640,22 @@ func _draw() -> void:
else pose_at(element, time_units)
var colour := modulate_of(pose)
if colour.a <= 0.0:
skipped.append("%s (transparent at rest)" % id)
# 🔴 THIS LINE USED TO SAY "at rest" WHATEVER INSTANT IT HAD POSED.
#
# On the timeline path the pose is `pose_at(time_units)`, not
# `rest`, and on the screens where those differ the message named a
# pose it had not looked at. `palogo_sqex_eff` on the publisher
# splash is `[0:a0 15:a255 30:a212 45:a0]` -- a flash whose `rest`
# alpha is **212**. The port skips it correctly at the settled
# instant and then reported "transparent at rest" about a resting
# pose that is four-fifths opaque.
#
# ⚠️ That is not cosmetic. The rest-versus-posed-instant confusion is
# exactly what made me score a `--pose=rest` frame against a capture
# and write up a drift that did not exist (DECISIONS.md). A log line
# that erases the distinction is the same error, pre-printed.
skipped.append("%s (transparent %s)" % [id,
"at rest" if pose_mode == Pose.REST else "at t=%.0f" % time_units])
continue
var pivot := _vec(element.get("pivot", [0, 0]))
var pos := _vec(pose.get("pos", [0, 0]))