port: P3 -- the boot title gets its PRESS (A) plate, and two of the RE agent's numbers do not agree
ScreenView now draws two builds at once, which it never had to before. It is a second ScreenView in the same SubViewport rather than a subordinate screen inside one: each build has its own timeline, its own textures and its own hold, which is the entire content of the finding, and Node2D siblings already paint in tree order. `paint_order` still means what it meant -- an ordering WITHIN a build. The delay is authored in flow.json on the BOOT STEP, not on the `title` screen. What was measured is the boot title; whether the plate is there when the title is reached again -- (B) from the menu, or after the attract movie -- is not, and hanging it on the screen would quietly claim that it is. REFUTATION, and it is the substance of this commit: the RE agent's authoring instruction does not reproduce the RE agent's own measurement, and the gap is 3.97 s. The instruction is "when build 4 has settled, wait 2.13 s, composite build 2". But build 2 has a group and this port plays groups -- ptbtn00 is alpha 0x00 at t=214, still 0x00 at t=236 while it slides 10 px up, and 0xff only at t=238, which is 3.967 s at 60 units/s. So the plate is first VISIBLE at settle+6.10 s, while what was measured -- the glyph counter leaving 154 -- is visibility at settle+2.13 s. Both groups starting together puts it 0.38 s BEFORE settle; build 2 starting at settle puts it at settle+3.97 s; landing on the measurement needs build 2's group to start 2.51 s after build 4's, which is not a landmark of anything. The measurement is untouched -- it is an observation of the running game and I have no standing to doubt it. What is refuted is the step that turns it into an authoring rule. So the port ships the instruction, prints the discrepancy on every boot, and files the row. Same call as the BGM sub-waves: a port that quietly picks the number that looks right destroys the evidence, because a corrected boot looks exactly like a correct one. Also refuted, and it was mine: BLOCKED.md has said since P2 that "no element's alpha reverses direction anywhere in this export, so nothing pulses". ptbtn00 reverses -- 0x00 -> 0xff -> 0x00 -- and it was in the export the whole time. The claim had been checked against the screens P2 happened to be animating. The port still draws no pulse, because no reading of this group yields the measured 2.24 s: the whole group is 4.47 s and from its first keyframe 0.90 s. Gate: `--boot --capture=` writes one frame of the composited end state, instead of the 600-PNG filmstrip that was previously the only boot artifact. `--screen=title --overlay=press_start` raises the same composite in two seconds for anyone who does not want to sit through 137 s of Theora. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC
This commit is contained in:
@@ -29,6 +29,15 @@
|
||||
},
|
||||
{
|
||||
"screen": "title",
|
||||
"overlay": {
|
||||
"screen": "press_start",
|
||||
"after_settle_seconds": 2.13,
|
||||
"why": "MEASURED, 2026-08-29, docs/re/title-plate-delay-measured.md on branch auto/no-disc-and-menu-captures at fb536df (NOT yet on main when this was written). The boot title shows build 4 ALONE, and the `PRESS (A) BUTTON` plate -- build 2 -- is composited over it 2.13 s later. Two independent boots of the real game agree to 6 ms. This is the ONE case in the port where two builds are drawn at once.",
|
||||
"measured_from_why": "The 2.13 s is timed from the frame build 4's own build-in animation FINISHES, not from the frame the title art first appears. That distinction is the finding, not a detail: measured the other way the two runs differ by 0.48 s, because the build-in itself ran 1.64 s and 2.13 s and the emulator's frame pacing during an animation is not the game's clock. A number taken from the wrong instant here looks exactly like a measurement.",
|
||||
"not_a_fade_in_why": "The alternative -- that the plate was there all along, pulsing too dim to see -- was tested and refuted on two observables. The plate's declared alpha never exceeds 0x50, so a thresholded glyph counter could in principle miss a dim phase; it does not. The count sits at EXACTLY 154 with zero variation for 1.99 s / 2.13 s and then swings 714-1520, and the surface mean is flat to +/-0.03 across the plateau. A pulsing overlay moves the frame mean.",
|
||||
"scope_why": "Attached to the BOOT STEP, not to the `title` screen, and that is deliberate. What was measured is the boot title. Whether the title shows the plate when it is REACHED AGAIN -- by (B) from the main menu, or after the attract movie -- is not measured, and putting the overlay on the screen would quietly claim it is. See docs/port/BLOCKED.md.",
|
||||
"no_pulse_why": "The port draws the plate arriving and then HOLDING VISIBLE. It does not pulse it. The plate's own group encodes exactly one cycle -- alpha 0x00 at t=214, 0xff at t=238-244, 0x00 on the final untimed keyframe -- and the port holds at the settle (t=238, alpha 0xff) as it does on every other screen. The running game was measured pulsing continuously at peak intervals averaging 2.24 s, and NO reading of this group produces 2.24 s: looping the whole group gives 268 units = 4.47 s, looping from the first keyframe gives 54 units = 0.90 s. So repeating the group here would be inventing a period, and the port does not. Filed in BLOCKED.md."
|
||||
},
|
||||
"why": "HANDOFF Q2/Q6: the boot reaches the title after the intro movie. This is the LAST step, and a last step is where the sequence stops rather than fading out -- a boot that ends by fading to black looks like a boot that crashed. P5 gave the title somewhere to go, but that is a HANDOVER and not another boot step: `--boot` still stops here, and `--boot --play` hands the same held title to the menu flow, where (A) opens TITLE_MENU. Kept as a stop rather than folded into `screens` because what the boot does is authored from a measured sequence, and what (A) does is a separate measurement."
|
||||
}
|
||||
],
|
||||
|
||||
@@ -83,7 +83,11 @@ git log -1 --format=%h -- docs/port/HANDOFF.md # newer than 9ca1eb5? re-reconc
|
||||
| P5 — Ⓑ on the main menu | **is Ⓑ what returns to the title, or the idle timer?** | Q5 | 🟡 stated in HANDOFF, no capture behind it. The title self-returns after ~8–10 s idle, so one unrecorded observation cannot separate them. `authored/flow.json` implements it and marks it *authored — likely but UNPROVEN*. **Not blocking** — P5 shipped with it — but it is the only navigation rule on that screen with nothing under it. Settled by one run that presses Ⓑ well inside the idle window, timestamped. |
|
||||
|
||||
| P6 BGM — the sub-wave count | **is a music bank's LEADING REGION a stem, or a decoder artefact?** | Q10 | 🔴 **HANDOFF and the decoders disagree, and P6 ships the disagreement.** `media::sound_bank_riffs("BGM_103.slb")` returns **three** sub-waves; HANDOFF Q10's census says a music bank is *"exactly two waves of identical duration (32/32 banks on the disc)"*. The third comes from `slb.rs:380` `to_xma_riffs`, whose hybrid branch emits a leading headerless packet region ahead of the `RIFF` waves — and `docs/re/REFUTED.md` already records that region as what makes `BGM_106`–`BGM_109` *"break the two-wave rule"*. Derived at HANDOFF `9ca1eb5`. **The exporter sums all three and writes a manifest warning**, because choosing which sub-wave to drop is a decoding question and MISSION §2 forbids this exporter answering one. So the menu currently plays a sum of three things where the census predicts two. What settles it: whether that leading region carries music. Raised with the Decoder 2026-08-29. |
|
||||
| P3/P5 — the title plate | ~~does the idle title show `PRESS Ⓐ`~~ | Q2 | ✅ **answered 2026-08-29, NOT ON `main` YET, and it is the expensive answer.** Pointer from the Decoder, unverified by me at the time of writing: branch `auto/no-disc-and-menu-captures` at **`fb536df`**, `docs/re/title-plate-delay-measured.md`, per-frame traces in `docs/re/data/plate-timing-run{1,2}.tsv`. It confirms the 🔴 row below: `ScreenView` **does** have to draw two builds at once, and `--boot`'s plate-free end state is wrong. ⚠️ The Decoder flags a wrong way to read the number — time it from the frame build 4 *stops animating*, not from the frame it first appears; the two runs differ by half a second on the latter and 6 ms on the former. **Read the file, not this row.** Not taken at P6; this is the next iteration's work. |
|
||||
| P3 — the plate's ONSET | **does the plate become visible 2.13 s after settle, or does its GROUP START then?** | Q2 | 🔴 **new, and it is a disagreement between two of the Decoder's own numbers.** `docs/re/title-plate-delay-measured.md` (`auto/no-disc-and-menu-captures` at `fb536df`, **not on `main`**) measures the plate *becoming visible* at settle + **2.13 s**, and instructs the port to *"wait 2.13 s, composite build 2"*. Those are 3.97 s apart, because build 2 has a group of its own and the port plays it: `press_start`'s `ptbtn00` is alpha `0x00` at t=214, `0x00` at t=236 (it slides y 560→550 invisible), `0xff` at t=238 — **3.967 s** from group start to full alpha at 60 u/s. Neither landmark reconciles them: both groups starting together puts full alpha 0.38 s *before* build 4 settles (`title` settles at t=261 = 4.350 s); build 2 starting at settle puts it at settle + 3.97 s. Landing on settle + 2.13 s needs build 2's group to start 2.51 s after build 4's, which is neither. **The port ships the instruction, not its own arithmetic**, prints the discrepancy on every boot, and is not choosing. First thing to check, and it is about the instrument rather than the game: is *"title settled"* (the glyph counter first reading 154) the same instant as the port's last-element settle? Raised 2026-08-29. |
|
||||
| ~~P3/P5 — the title plate~~ | ~~does the idle title show `PRESS Ⓐ`~~ | Q2 | ✅ **answered and TAKEN at this iteration.** `auto/no-disc-and-menu-captures` at `fb536df`, `docs/re/title-plate-delay-measured.md`, traces in `docs/re/data/plate-timing-run{1,2}.tsv`. It is the third case: build 4 alone, then the plate composited over it. ⚠️ The delay is timed from where build 4 **stops animating**, not from where it first appears — measured the other way the two runs differ by 0.48 s against 6 ms. `ScreenView` now draws two builds at once, as a second `ScreenView` in the same `SubViewport` rather than a subordinate screen inside one. The onset question above is what is left. |
|
||||
|
||||
| P3 — the plate's PULSE | **what period does the plate pulse at, and which group boundary repeats?** | Q2 | ❔ **open, and the port draws no pulse.** The same finding measures the running plate pulsing continuously — peak/trough intervals 2.12 / 2.19 / 2.34 / 2.31 s, mean **2.24 s**. `press_start`'s group encodes exactly one cycle (`0x00` → `0xff` at t=238–244 → `0x00` untimed), and **no reading of it produces 2.24 s**: the whole group is 268 units = 4.47 s, and from its first keyframe 54 units = 0.90 s. So repeating the group would be inventing a period. The port holds the plate at its settle (t=238, alpha `0xff`), which is what it does on every other screen and what the static oracle capture `live-title-press-a.png` shows. What settles it: which instant the group restarts from. |
|
||||
| P5 focus ring — implementation | ~~the ring's spin period~~ | Q1 | ✅ answered (2.177 s, `4fa3099`) and **still not implemented** — the port draws 0°, a pose the corpus says the game never shows. Not blocked on anybody; it is queued behind P3's plate this iteration. |
|
||||
|
||||
## Answered since this file was last written — no longer blocking
|
||||
|
||||
@@ -236,9 +240,7 @@ right for an entry animation (the main menu settles at t=80, 1.33 s) and is
|
||||
proven on the screen P2 gates. The **title** runs to t=269 — 4.48 s — and there
|
||||
the port's settled pose and the decoders' `rest` disagree badly (max 142/255).
|
||||
|
||||
What is known: no element's alpha reverses direction anywhere in this export, so
|
||||
nothing pulses, which removes the obvious reason to expect a loop without
|
||||
disproving one. What would settle it: **a capture of build 4 alone**. The one
|
||||
🔴 **REFUTED 2026-08-29, by the port, against its own export.** This paragraph said: *"no element's alpha reverses direction anywhere in this export, so nothing pulses, which removes the obvious reason to expect a loop without disproving one."* `press_start`'s `ptbtn00` reverses: `fade_argb` is `0x00ffffff` at t=214 and t=236, `0xffffffff` at t=238 and t=244, and `0x00ffffff` again on the final untimed keyframe. It was in the export the whole time and the claim was never checked against it — it was checked against the screens P2 happened to be looking at. The reason to expect a loop is back, and the running game pulses this exact element. What would settle it: **a capture of build 4 alone**. The one
|
||||
live title capture composites the `PRESS Ⓐ` plate (build 2) over it, so it
|
||||
cannot be diffed against the title by itself.
|
||||
|
||||
|
||||
@@ -1591,3 +1591,162 @@ dispatch on the extension, so a temp name must preserve it.**
|
||||
The second is the more dangerous shape, and it is the one this project has
|
||||
already warned itself about: a confident line of output pointing at nothing. The
|
||||
rename's return is now checked and the failure is loud.
|
||||
|
||||
## P3, reopened — the boot title was missing the `PRESS Ⓐ` plate, 2026-08-29
|
||||
|
||||
P3 passed its gate with a boot that ended on build 4 alone. `BLOCKED.md` carried
|
||||
that as 🔴 from the start: both states were captured, so the art was never the
|
||||
question — the *sequence* was, and it is behavioural, so the port had no oracle
|
||||
for it.
|
||||
|
||||
It is answered. `docs/re/title-plate-delay-measured.md`
|
||||
(`auto/no-disc-and-menu-captures` at `fb536df`, **not on `main`** at the time of
|
||||
writing) measures two independent boots: the title presents **without** the
|
||||
plate, and the plate arrives **2.13 s** later, the two runs agreeing to 6 ms.
|
||||
|
||||
### Two builds at once, as two `ScreenView`s
|
||||
|
||||
`ScreenView` draws one screen. The obvious change was to teach it about a
|
||||
subordinate overlay screen; the change made was to put a **second `ScreenView`
|
||||
in the same `SubViewport`**, after the first.
|
||||
|
||||
That is what "two builds at once" actually is. Each build has its own timeline,
|
||||
its own textures and its own hold — the plate's group runs independently of the
|
||||
title's, which is the entire content of the finding — and Node2D siblings already
|
||||
paint in tree order. The alternative would have put an `if overlay` in every
|
||||
method that walks elements, and would have expressed the same information less
|
||||
directly. The export's `paint_order` still means what it always meant: an
|
||||
ordering *within* a build.
|
||||
|
||||
### The delay is timed from where build 4 stops animating
|
||||
|
||||
Not from where the title first appears. This is the finding rather than a detail:
|
||||
measured from first-draw the two oracle runs differ by **0.48 s**, because the
|
||||
build-in itself ran 1.64 s and 2.13 s and the emulator's frame pacing during an
|
||||
animation is not the game's clock. Measured from settle they differ by 6 ms.
|
||||
|
||||
So `_boot_done` — the moment the sequencer already had for "this screen has
|
||||
reached its hold" — is the landmark, and the overlay is due `after_settle_seconds`
|
||||
later. A number taken from the wrong instant here looks exactly like a
|
||||
measurement.
|
||||
|
||||
### The overlay is attached to the BOOT STEP, not to the `title` screen
|
||||
|
||||
What was measured is the boot title. Whether the plate is there when the title is
|
||||
reached *again* — by Ⓑ from the main menu, or after the attract movie — is not
|
||||
measured, and hanging the overlay on the screen would quietly claim that it is.
|
||||
So it lives on the boot step in `authored/flow.json`, and `_drop_overlay` takes
|
||||
it away with the screen it belongs to. `BLOCKED.md` carries the gap.
|
||||
|
||||
### Refutation — the RE agent's instruction contradicts the RE agent's measurement
|
||||
|
||||
**The claim under test**, quoted from the finding's *"What the port should
|
||||
author"*: draw build 4, *"when build 4 has settled, wait **2.13 s**, composite
|
||||
build 2 over it"*.
|
||||
|
||||
**It does not reproduce the measurement it came from**, and the gap is 3.97 s.
|
||||
Build 2 is not a static plate: it has a group, and this port plays groups.
|
||||
`press_start` has one element, `ptbtn00`, and its `fade_argb` reads
|
||||
|
||||
```
|
||||
t=214 0x00ffffff pos (383, 560) invisible
|
||||
t=236 0x00ffffff pos (383, 550) still invisible, having slid 10 px up
|
||||
t=238 0xffffffff full alpha
|
||||
t=244 0xffffffff holds
|
||||
— 0x00ffffff the exit, untimed
|
||||
```
|
||||
|
||||
At the measured 60 units/s that is **3.967 s** from the group's start to full
|
||||
alpha. Compose the instruction with the group and the plate is first *visible* at
|
||||
settle + 2.13 + 3.97 = **settle + 6.10 s**. What was measured — the glyph counter
|
||||
leaving its no-plate value of 154 — is the plate becoming visible at **settle +
|
||||
2.13 s**.
|
||||
|
||||
Neither obvious reconciliation works:
|
||||
|
||||
| reading | plate visible at | measured |
|
||||
|---|---|---|
|
||||
| both groups start together | 3.97 s (build 4 settles at **4.350 s**) — i.e. 0.38 s *before* settle | settle + 2.13 s |
|
||||
| build 2's group starts at settle | settle + 3.97 s | settle + 2.13 s |
|
||||
| build 2's group starts at settle + 2.13 s (the instruction) | settle + 6.10 s | settle + 2.13 s |
|
||||
|
||||
To land on the measurement, build 2's group has to start **2.51 s** after build
|
||||
4's, which is not a landmark of anything.
|
||||
|
||||
**Verdict: the instruction is refuted as written; the measurement is untouched.**
|
||||
The measurement is an observation of the running game and this port has no
|
||||
standing to doubt it. What is refuted is the step that turns it into an
|
||||
authoring rule, and that step is an interpretation.
|
||||
|
||||
**So the port ships the instruction, not its own arithmetic**, prints the
|
||||
discrepancy on every boot, and files the row. This is the same call as the BGM
|
||||
sub-waves and for the same reason: reconciling two of the RE agent's numbers is
|
||||
a decoding question, and a port that quietly picks the one that looks right
|
||||
destroys the evidence — a corrected boot looks exactly like a correct one.
|
||||
|
||||
The first thing to check is about the instrument rather than the game: is *"title
|
||||
settled"*, the glyph counter first reading 154, the same instant as the port's
|
||||
last-element settle (t=261, 4.350 s into the group)? If that landmark is earlier,
|
||||
the gap closes with nothing else moving.
|
||||
|
||||
### Refuting the port's own claim: things in this export DO pulse
|
||||
|
||||
`BLOCKED.md` has carried this since P2, under the port's own raised question
|
||||
about whether groups loop:
|
||||
|
||||
> no element's alpha reverses direction anywhere in this export, so nothing
|
||||
> pulses, which removes the obvious reason to expect a loop without disproving
|
||||
> one.
|
||||
|
||||
**`ptbtn00` reverses.** `0x00` → `0xff` → `0x00`, in the table above, in the
|
||||
export, the whole time. The claim was never checked against `press_start`; it was
|
||||
checked against the screens P2 happened to be animating. The RE agent has now
|
||||
measured the running game pulsing this exact element at a mean 2.24 s.
|
||||
|
||||
So the reason to expect a loop is back — and the port still does not draw one,
|
||||
because **no reading of this group produces 2.24 s**: the whole group is 268
|
||||
units = 4.47 s, and from its first keyframe 54 units = 0.90 s. The plate is drawn
|
||||
arriving and then holding at its settle (t=238, alpha `0xff`), which is what
|
||||
every other screen does and what the static oracle capture
|
||||
`live-title-press-a.png` shows. Which instant a repeat restarts from is filed,
|
||||
not guessed.
|
||||
|
||||
### `--boot --capture=` — one frame instead of six hundred
|
||||
|
||||
The boot had no artifact of its own except `--film`, a PNG every 0.25 s for the
|
||||
whole 156 s run, to answer one question: is the plate on top of the title at the
|
||||
end. `--capture` was a `--screen`-only flag taken in `_ready`, which for a boot
|
||||
run is 150 s too early. It is now deferred to the end of the sequence when
|
||||
`--boot` is given.
|
||||
|
||||
### P3 gate — the boot ends on two builds
|
||||
|
||||
```
|
||||
godot --path port -- --boot --capture=…/p3-plate.png
|
||||
→ boot sequence complete after 155.86 s, holding on title
|
||||
overlay press_start due at 157.99 s (+2.13 s after settle)
|
||||
overlay press_start raised at 158.00 s, 1 element(s), settles at t=238
|
||||
⚠ plate raised at settle+2.13 s but its own group reaches full alpha 3.97 s
|
||||
later, so it is first VISIBLE at settle+6.10 s -- the measurement is
|
||||
settle+2.13 s.
|
||||
boot ends on title + press_start at 161.99 s
|
||||
drew 16: ptbase2, ptloop01, …, ptcopyright
|
||||
overlay press_start at t = 261.00 units, drew 1: ptbtn00
|
||||
```
|
||||
|
||||
The PNG shows the title logo with **`PRESS Ⓐ BUTTON`** under it — build 4 and
|
||||
build 2 in one frame, which this port had never drawn.
|
||||
|
||||
Two things the run made obvious and that are now fixed:
|
||||
|
||||
* **The capture reported only the base build's elements.** The first composited
|
||||
capture printed `drew 16` and no mention of the plate, which reads as though
|
||||
the overlay had not drawn at all. The overlay gets its own line; folding its
|
||||
elements into the first list would have reported a screen that does not exist.
|
||||
* **`--screen=<a> --overlay=<b>`** raises the same composite immediately, by the
|
||||
same code path, with no delay. It exists because the only other way to see two
|
||||
builds was a 156 s boot of which 137 s is the intro movie — and under Xvfb's
|
||||
software Theora decode that is several minutes to answer "is the plate on top
|
||||
of the title". It applies **no** delay: the delay is a measurement and lives in
|
||||
`authored/flow.json`. The boot-mode narration is suppressed there, because a
|
||||
log line that describes a sequence it is not running is worse than no log line.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
# godot --path port -- --screen=main_menu --capture=/tmp/godot.png
|
||||
# godot --path port -- --screen=main_menu --time=0.5 --capture=/tmp/at-half.png
|
||||
# godot --path port -- --screen=main_menu --pose=rest --capture=/tmp/rest.png
|
||||
# godot --path port -- --screen=title --overlay=press_start --time=4
|
||||
# godot --path port -- --boot # the whole boot sequence
|
||||
# godot --path port -- --boot --film=/tmp/boot # ...and a frame every 0.25 s
|
||||
# godot --path port -- --menu # P5: navigate the menus
|
||||
@@ -49,6 +50,20 @@ var view: ScreenView = null
|
||||
var viewport: SubViewport = null
|
||||
var audio: MenuAudio = null
|
||||
|
||||
## The second build, drawn OVER `view`. The boot title is the only place in this
|
||||
## port where two builds are on screen at once (`authored/flow.json`, the boot's
|
||||
## `title` step): build 4 presents alone, and the `PRESS Ⓐ BUTTON` plate --
|
||||
## build 2 -- is composited over it 2.13 s after build 4 settles. MEASURED, two
|
||||
## boots agreeing to 6 ms; see the step's `why`.
|
||||
##
|
||||
## A second ScreenView rather than a second screen inside one, because that is
|
||||
## what "two builds at once" actually is: each has its own timeline, its own
|
||||
## textures and its own hold, and Node2D siblings already draw in tree order.
|
||||
## Teaching ScreenView about a subordinate screen would have been the same
|
||||
## information expressed less directly, and would have put an `if overlay` in
|
||||
## every method that walks elements.
|
||||
var overlay: ScreenView = null
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var args := _args()
|
||||
@@ -85,6 +100,10 @@ func _ready() -> void:
|
||||
if _record_to != "":
|
||||
_start_recording()
|
||||
|
||||
# In `--boot` the capture is taken at the END, not in `_ready`: the frame
|
||||
# worth having is the composited title, and `_ready` runs 150 s before it.
|
||||
if args.has("boot"):
|
||||
_capture_to = args.get("capture", "")
|
||||
_film = args.get("film", "")
|
||||
_shots = args.get("shots", "")
|
||||
if args.has("script"):
|
||||
@@ -171,7 +190,25 @@ func _ready() -> void:
|
||||
set_process(true)
|
||||
_film_capture()
|
||||
|
||||
if args.has("capture"):
|
||||
# `--overlay=<screen>` composites a second build immediately, without waiting
|
||||
# for a boot. It exists because the only other way to see two builds at once
|
||||
# is a 156 s `--boot`, of which 137 s is the intro movie -- which under Xvfb's
|
||||
# software Theora decode is several minutes to answer "is the plate on top of
|
||||
# the title". This raises the same second `ScreenView` by the same code path,
|
||||
# so what it photographs is the real composite and not a mock-up. It applies
|
||||
# NO delay: the delay is a measurement and lives in `authored/flow.json`,
|
||||
# where the boot reads it.
|
||||
if args.has("overlay") and not args.has("boot"):
|
||||
_overlay_spec = {"screen": args["overlay"]}
|
||||
_overlay_due = 0.0
|
||||
_overlay_process(0.0)
|
||||
if overlay != null and args.has("time"):
|
||||
overlay.time_units = float(args["time"]) * overlay.units_per_second
|
||||
overlay.queue_redraw()
|
||||
|
||||
# `--boot --capture=` is deferred to the end of the sequence (`_finish_boot`);
|
||||
# in every other mode the frame worth having is this one.
|
||||
if args.has("capture") and _capture_to == "":
|
||||
await _capture(args["capture"])
|
||||
get_tree().quit(0)
|
||||
|
||||
@@ -200,6 +237,7 @@ func _process(delta: float) -> void:
|
||||
view.time_units += delta * view.units_per_second
|
||||
_elapsed += delta
|
||||
view.queue_redraw()
|
||||
_overlay_process(delta)
|
||||
|
||||
if _player != null:
|
||||
return
|
||||
@@ -228,20 +266,32 @@ func _process(delta: float) -> void:
|
||||
view.holding = false
|
||||
elif not _boot_done:
|
||||
_boot_done = true
|
||||
print("boot sequence complete after %.2f s, holding on %s" % [_elapsed, _sequence[_step]])
|
||||
print("boot sequence complete after %.2f s, holding on %s" % [_elapsed, _sequence[_step].get("screen", _sequence[_step])])
|
||||
# The plate is timed from HERE -- the moment the screen reaches its
|
||||
# own hold -- and not from the frame it first appeared. That is the
|
||||
# finding, not a detail: measured from first-draw the two oracle
|
||||
# runs disagree by 0.48 s, because the build-in's own duration is
|
||||
# the emulator's frame pacing rather than the game's clock.
|
||||
var spec: Variant = _sequence[_step].get("overlay", null)
|
||||
if typeof(spec) == TYPE_DICTIONARY:
|
||||
_overlay_spec = spec
|
||||
_overlay_due = _elapsed + float(spec.get("after_settle_seconds", 0.0))
|
||||
print(" overlay %s due at %.2f s (+%.2f s after settle)"
|
||||
% [spec.get("screen", "?"), _overlay_due, _overlay_due - _elapsed])
|
||||
# P5 takes over here: the boot ends on the title and the title has
|
||||
# somewhere to go. Without `--play` the run still stops, because a
|
||||
# boot that ends by waiting for a key it will never get is worse
|
||||
# than one that exits.
|
||||
if _play:
|
||||
_menu_enter(String(_sequence[_step].get("screen", "")), true)
|
||||
elif _film == "":
|
||||
elif _film == "" and _overlay_spec.is_empty():
|
||||
get_tree().quit(0)
|
||||
elif not view.holding and view.time_units >= view.exit_time():
|
||||
_advance()
|
||||
|
||||
|
||||
func _advance() -> void:
|
||||
_drop_overlay()
|
||||
_step += 1
|
||||
var next: Dictionary = _sequence[_step]
|
||||
if next.has("video"):
|
||||
@@ -394,6 +444,8 @@ func _menu_enter(name: String, fresh: bool) -> void:
|
||||
|
||||
## The moment a screen has finished fading out and the next one takes over.
|
||||
func _menu_arrive() -> void:
|
||||
# The plate goes with the screen it was measured on. See `_drop_overlay`.
|
||||
_drop_overlay()
|
||||
var action: Dictionary = _pending
|
||||
_pending = null
|
||||
var name := String(action["goto"])
|
||||
@@ -456,6 +508,15 @@ func _capture(path: String) -> void:
|
||||
print("drew %d: %s" % [view.drawn.size(), ", ".join(view.drawn)])
|
||||
if not view.skipped.is_empty():
|
||||
print("not drawn %d: %s" % [view.skipped.size(), ", ".join(view.skipped)])
|
||||
# The overlay is a second build in the same frame, so it needs its own line.
|
||||
# Folding its elements into the list above would make the capture report a
|
||||
# screen that does not exist; leaving it out entirely made the first
|
||||
# composited capture read as though the plate had not been drawn at all.
|
||||
if overlay != null:
|
||||
print("overlay %s at t = %.2f units (%.3f s), drew %d: %s" % [
|
||||
overlay.screen.get("name", "?"), overlay.time_units,
|
||||
overlay.time_units / overlay.units_per_second,
|
||||
overlay.drawn.size(), ", ".join(overlay.drawn)])
|
||||
var err := img.save_png(path)
|
||||
if err != OK:
|
||||
push_error("cannot write %s (%d)" % [path, err])
|
||||
@@ -643,3 +704,118 @@ func _exit_tree() -> void:
|
||||
print("recorded %.3f s of Master bus -> %s (driver %s)"
|
||||
% [float(wav.data.size()) / float(wav.mix_rate * 2 * (2 if wav.stereo else 1)),
|
||||
_record_to, MenuAudio.driver()])
|
||||
|
||||
|
||||
# ── The second build ─────────────────────────────────────────────────────────
|
||||
|
||||
## The overlay the current boot step owes, if it has not been raised yet.
|
||||
var _overlay_spec: Dictionary = {}
|
||||
## Wall-clock second at which it is raised, measured from the screen's settle.
|
||||
var _overlay_due: float = 0.0
|
||||
|
||||
|
||||
func _overlay_process(delta: float) -> void:
|
||||
if overlay != null:
|
||||
overlay.time_units += delta * overlay.units_per_second
|
||||
overlay.queue_redraw()
|
||||
if _overlay_quit_at >= 0.0 and _elapsed >= _overlay_quit_at:
|
||||
print("boot ends on %s + %s at %.2f s"
|
||||
% [view.screen.get("name", "?"), overlay.screen.get("name", "?"), _elapsed])
|
||||
_overlay_quit_at = -1.0
|
||||
_finish_boot()
|
||||
return
|
||||
if _overlay_spec.is_empty() or _elapsed < _overlay_due:
|
||||
return
|
||||
_raise_overlay(String(_overlay_spec.get("screen", "")))
|
||||
|
||||
|
||||
## Composite a second build over the first.
|
||||
##
|
||||
## It starts at `time_units = 0` and plays its OWN group, so the plate rises and
|
||||
## fades in exactly as the disc declares -- alpha 0x00 at t=214, 0xff by t=238 --
|
||||
## rather than appearing as a cut. `holding` then parks it at its settle, which
|
||||
## for this build is the visible pose.
|
||||
##
|
||||
## ⚠️ It does NOT pulse, and that is a decision with arithmetic behind it rather
|
||||
## than an omission. See `authored/flow.json`, `no_pulse_why`.
|
||||
func _raise_overlay(name: String) -> void:
|
||||
var spec := _overlay_spec
|
||||
_overlay_spec = {}
|
||||
if name == "":
|
||||
return
|
||||
overlay = ScreenView.new()
|
||||
overlay.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
overlay.units_per_second = view.units_per_second
|
||||
overlay.exit_ramp_units = view.exit_ramp_units
|
||||
overlay.holding = true
|
||||
overlay.time_units = 0.0
|
||||
if not overlay.load_screen(view.tree, name):
|
||||
push_error(view.tree.error)
|
||||
overlay.queue_free()
|
||||
overlay = null
|
||||
return
|
||||
# After `view`, so it draws over it: Node2D siblings paint in tree order and
|
||||
# the export's own `paint_order` only orders WITHIN a build.
|
||||
viewport.add_child(overlay)
|
||||
print(" overlay %s raised at %.2f s, %d element(s), settles at t=%d"
|
||||
% [name, _elapsed, overlay.screen.get("elements", []).size(), int(overlay.settle_time())])
|
||||
# A boot with no menu to hand over to has now finished: it was held open for
|
||||
# this. Give the plate its own group time to play before leaving, so the
|
||||
# artifact shows the composited state rather than the frame it began on.
|
||||
var visible_at := overlay.settle_time() / overlay.units_per_second
|
||||
# The two lines below describe a BOOT. `--screen --overlay=` raises the same
|
||||
# composite with no delay, as a fast check, and must not narrate a sequence
|
||||
# it is not running -- a log line that lies is worse than no log line.
|
||||
if _sequence.is_empty():
|
||||
return
|
||||
# 🔴 SAY THE NUMBER OUT LOUD. `authored/flow.json` implements the RE agent's
|
||||
# instruction literally -- "when build 4 has settled, wait 2.13 s, composite
|
||||
# build 2" -- and build 2 then takes its OWN declared 238 units to fade in.
|
||||
# So the plate is first VISIBLE at settle + 2.13 + 3.97 s, while the thing
|
||||
# that was measured is the plate becoming visible at settle + 2.13 s. The two
|
||||
# cannot both be right and the port is not the one to choose. Printed on
|
||||
# every boot so the disagreement cannot go quiet.
|
||||
print(" ⚠ plate raised at settle+%.2f s but its own group reaches full alpha %.2f s later, \
|
||||
so it is first VISIBLE at settle+%.2f s -- the measurement is settle+%.2f s. See docs/port/BLOCKED.md."
|
||||
% [float(spec.get("after_settle_seconds", 0.0)), visible_at,
|
||||
float(spec.get("after_settle_seconds", 0.0)) + visible_at,
|
||||
float(spec.get("after_settle_seconds", 0.0))])
|
||||
if not _play and _film == "":
|
||||
_overlay_quit_at = _elapsed + visible_at
|
||||
print(" boot ends at %.2f s, once the plate has settled" % _overlay_quit_at)
|
||||
# `spec` is read only for the log; the reasoning lives in flow.json where a
|
||||
# reader looking for a decision will find it.
|
||||
if spec.has("why"):
|
||||
print(" why: %s" % String(spec["why"]).substr(0, 96))
|
||||
|
||||
|
||||
var _overlay_quit_at: float = -1.0
|
||||
|
||||
|
||||
## Take the overlay away with the screen it belongs to.
|
||||
##
|
||||
## The plate was measured on the BOOT title only. Whether it is there when the
|
||||
## title is reached again -- (B) from the main menu, or after the attract movie
|
||||
## -- is not measured, so leaving it up would be claiming something nobody has
|
||||
## watched. `authored/flow.json` says the same thing in the step's `scope_why`.
|
||||
func _drop_overlay() -> void:
|
||||
_overlay_spec = {}
|
||||
_overlay_quit_at = -1.0
|
||||
if overlay != null:
|
||||
overlay.queue_free()
|
||||
overlay = null
|
||||
|
||||
|
||||
## End a `--boot` run, photographing the composited end state first if asked.
|
||||
##
|
||||
## `--capture` used to be a `--screen`-only flag, taken in `_ready`. The boot had
|
||||
## no artifact of its own except a whole `--film` filmstrip, which is 600+ PNGs
|
||||
## to answer one question: is the plate on top of the title at the end. This
|
||||
## takes that one frame.
|
||||
func _finish_boot() -> void:
|
||||
if _capture_to != "":
|
||||
await _capture(_capture_to)
|
||||
get_tree().quit(0)
|
||||
|
||||
|
||||
var _capture_to := ""
|
||||
|
||||
Reference in New Issue
Block a user