From ba3f490c67d15e4957f2c54a2e1a8381ce41183b Mon Sep 17 00:00:00 2001 From: Sylpheed port agent Date: Tue, 1 Sep 2026 18:32:48 +0000 Subject: [PATCH] port: P7's gate has an artifact, and the GPU kills my finding-4 candidate outright MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things this iteration, and the middle one is the human's doing. P7 -- GATE MET, with an artifact. The path had been wired for some time (then_video S00A, skipped_chain, after_video -> title, and S00A.ogv in the export) and NOBODY HAD RUN IT. A milestone is done when its artifact exists, not when the wiring reads correctly. Pre-registered: unskipped, S00A is 93.78 s of media, so it should end on its own at ~94 s and hand off to title. Measured: video ended at 94.13 s -- +0.35 s, 0.4 % -- then -> title, plate raised, script complete at 96.83 s. Both predictions held. docs/port/p7-gate.md. And it genuinely decodes, checked because "ends at the right time" does not prove it: 45 frames in a 2.03 s window, 244 in an 18.91 s one. Scales with the window, sub-linearly, at the software fill ceiling -- those two runs predate the GPU. The counts are upper bounds and the port says so itself. Free corroboration of the fill-rate finding, from a direction it was not designed for: the unskipped run reports main_menu at 60.3 fps for 94 s, because the screen never changes during a movie. Same process, same container, same screen: 60.3 fps with one full-screen video texture on top, 9.7 fps drawing the menu's five additive full-screen quads. โŒ THE FINDING-4 FRAME-RATE CANDIDATE IS DEAD, tested rather than argued away. The human activated a hardware GPU in both containers; Godot takes it with no change on our side (NVIDIA GTX 1070 Ti, Vulkan 1.4.312, Forward+): publisher_logo 17.3-25.0 -> 69.4 fps developer_logos 16.7-22.8 -> 69.1 title 12.7-17.2 -> 61.1 main_menu 9.7 -> 59.6 Pre-registered that 5-7 full-screen textured quads at 720p is trivial for that card and the fade would get its full 45 steps. At 69 fps the 45-unit build-in gets 52 drawn steps and the companion glow's 15-unit rise gets 17 -- MORE frames than declared units, so every declared alpha is drawn and the quantisation is absent rather than reduced. So every candidate for finding 4 is now dead: keyframes vindicated against the vertex stream, companion quads drawn, blend space matching, settled pose at 0.01 %, no post-process pass, and the frame rate drawing every declared step. The port has nothing left that is KNOWN to be wrong about the splashes -- which is a statement about our knowledge, not about the port. The human saw something and nothing we can measure reproduces it. The next play-test is now the highest-value thing on this focus, and the rate line will say 60-something. One figure did not improve: main_menu's worst gap is 117 ms on the GPU against 150 ms on llvmpipe, essentially unchanged while the mean improved 6x. A hitch that survives a 6x fill speed-up is not fill. Likely first-frame cost, STATED AS UNTESTED, recorded rather than chased. check-all now asserts the display before any Godot step. Xvfb does not survive a container restart and LEAVES ITS SOCKET BEHIND, so Godot reports "X11 Display is not available", falls back to Wayland, fails that too, and exits non-zero -- and every Godot-backed step would have gone red for that one reason. Cost me one run before I noticed. Checked with xdpyinfo rather than by looking for the socket, because the stale socket is what makes it confusing. Also recorded from the P7 run, neither chased: Godot reports 4 leaked ObjectDB instances at exit on that path, and the menu bed keeps playing under the movie (already an open ask -- does the menu music duck?). Not settled: the 117 ms menu hitch; H6's +0x04 exposure; H1 (with the Decoder); the four red verify-screen rows; and what the human actually saw. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018AHUQvXGyNcKonSEWsgWcX --- docs/port/BLOCKED.md | 13 ++++- docs/port/p7-gate.md | 95 ++++++++++++++++++++++++++++++++++++ docs/port/port-frame-rate.md | 72 +++++++++++++++++++++++++-- tools/port/check-all | 22 +++++++++ 4 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 docs/port/p7-gate.md diff --git a/docs/port/BLOCKED.md b/docs/port/BLOCKED.md index 28760f9f..d5ae9d66 100644 --- a/docs/port/BLOCKED.md +++ b/docs/port/BLOCKED.md @@ -123,8 +123,17 @@ hitches**, which draws the splash's 45-unit build-in at **12โ€“17 distinct alpha instead of 45** and the companion glow's 15-unit rise at **four to six instead of fifteen**. -๐Ÿ”ด **DOWNGRADED the next iteration by a matched control, and the downgrade is -mine.** An empty scene in the same container runs at **161.6 fps**, and matched +โŒ **DEAD as of 2026-09-01, tested on hardware.** The human activated a GPU in +both containers; Godot takes it with no change on our side (NVIDIA GTX 1070 Ti, +Vulkan, Forward+) and the port goes from 9.7โ€“25 fps to **59.6โ€“69.4 fps**, every +screen at or above 60. At 69 fps the 45-unit build-in gets **52** drawn steps and +the companion glow's 15-unit rise gets **17** โ€” more frames than declared units, +so **every declared alpha is drawn** and the quantisation is absent rather than +reduced. **Every candidate for finding 4 is now dead, and the port has nothing +left that is known to be wrong about the splashes.** The next play-test is the +highest-value thing on this focus. + +๐Ÿ”ด **Downgraded first by a matched control, and that downgrade was mine.** An empty scene in the same container runs at **161.6 fps**, and matched textured controls give **23.9 fps for three full-screen quads and 11.2 for seven** โ€” every port screen lands inside that bracket in the order its quad count predicts (`main_menu`'s 9.7 against `tex7`'s 11.2). So the rate is software diff --git a/docs/port/p7-gate.md b/docs/port/p7-gate.md new file mode 100644 index 00000000..77323578 --- /dev/null +++ b/docs/port/p7-gate.md @@ -0,0 +1,95 @@ +# P7 โ€” the new-game intro plays and returns to a defined state โœ… + +**Status:** โœ… **gate met, with an artifact.** Run 2026-09-01 by the Port at +`4be90c2` + this commit; HANDOFF on this branch answers `9ca1eb5`. + +`PORT-MISSION.md` P7: *"New-game intro video after NEW GAME | Plays, then returns +to a defined state."* + +The path had been **wired** for some time โ€” `authored/flow.json` gives `ptbtn01` +a `then_video: "S00A"`, a `skipped_chain`, and `after_video: {goto: "title"}`, +and `S00A.ogv` is in the export. **Nobody had run it.** A milestone is done when +its artifact exists, not when the wiring reads correctly, and this file is the +difference. + +## Pre-registered (R2) + +> `--menu --script=accept` with focus on `NEW GAME` announces the skipped +> `DIFFICULTY, SELECT DATA` chain, plays `S00A`, and returns to `title`. +> Unskipped, `S00A` is 93.78 s of media, so it should end **on its own** at +> โ‰ˆ94 s and hand off. + +## What happened + +``` + menu on main_menu, focus ptbtn01 +script[1] accept at 1.00 s + (NEW GAME) -> the real chain is DIFFICULTY -> SELECT DATA, then the movie. + Neither screen is in this export. + -> video S00A at 0.97 s (/work/export/video/S00A.ogv) + + voice S00A + video ended at 94.13 s + -> title (authored: authored) + (after the movie) -> title + overlay press_start raised, settles at t=236 +script complete after 96.83 s on title +``` + +**94.13 s against 93.78 s of declared media โ€” +0.35 s, 0.4 %.** It ends on its +own, at the right time, and hands off. Both predictions held. + +Artifacts: `s_00_start.png` (main menu, `NEW GAME` focused) and `s_01_accept.png` +(the title with the plate up) โ€” written to the scratchpad by `--shots`, not +committed, because they are frames of the user's own disc. + +## It genuinely decodes โ€” checked, because "ends at the right time" does not prove it + +A player that consumed 94 s of *time* while showing one frame would also "end at +94 s". So the frame counts, at two window lengths: + +| window | frames shown | of 2813 | +|---|---|---| +| 2.03 s | 45 | 2 % | +| 18.91 s | **244** | 9 % | + +Frames scale with the window โ€” it is decoding, not stalled. **Sub-linearly +though**, 22.2 fps early against 12.9 fps over the longer window, and that is +the same software fill ceiling documented in +[`port-frame-rate.md`](port-frame-rate.md); these two runs predate the GPU. + +โš ๏ธ **The counts are upper bounds and the port says so itself** โ€” the log reads +*"at most 244 of 2813 frame(s) shown"*. It cannot see inside `VideoStreamPlayer`, +so it reports what it can bound rather than a number it cannot support. + +## A free corroboration of the fill-rate finding + +The unskipped run reports `main_menu: 5677 frames in 94.13 s โ€” **60.3 fps**`. +The screen never changes during a movie, so the rate line attributes the whole +playback to it. + +**The same process, in the same container, on the same screen: 60.3 fps while a +full-screen video texture is on top, and 9.7 fps while drawing the menu's five +additive full-screen quads.** That is the fill-rate conclusion arriving from a +direction it was not designed for โ€” one large textured quad is cheap, five +additive ones are not โ€” and it cost nothing to obtain. + +## Two things this run surfaced that are not P7 + +* ๐Ÿ”ด **`4 ObjectDB instances were leaked at exit`.** Godot's own warning, on + every run of this path. Small and at shutdown, so it costs a player nothing, + but it is a real signal that something in the video/overlay teardown is not + freed. **Not chased, recorded.** +* ๐ŸŸก **The menu bed keeps playing under the movie.** The port prints this itself + and it is already an open ask โ€” *does the menu music duck?* โ€” in + `BLOCKED.md`. Left audible on purpose rather than guessed at. + +## What this does not claim + +* That the skipped chain is right. `DIFFICULTY` and `SELECT DATA` live in + archives this milestone does not export; the port **announces the skip** rather + than pretending the sequence is complete, which is the honest half of a gate it + cannot fully reach. +* That `after_video: title` is what the game does. It is **authored**, and its + `why` says so: the game goes into Mission 1, and gameplay is out of scope + (`PORT-MISSION` ยง7). "Returns to a defined state" is the gate; `title` is the + state we defined. diff --git a/docs/port/port-frame-rate.md b/docs/port/port-frame-rate.md index 0655796b..89dbe764 100644 --- a/docs/port/port-frame-rate.md +++ b/docs/port/port-frame-rate.md @@ -1,8 +1,8 @@ # The port never reported its own frame rate โ€” it does now, and it is 13โ€“25 fps here -**Status:** โœ… **instrument added and measured.** ๐Ÿ”ด **The candidate it raised for -play-test finding 4 is now DOWNGRADED to probably-dead by a matched control โ€” -see the last section. The port's draw path has no case to answer.** Written 2026-09-01 by the Port at +**Status:** โœ… **instrument added and measured.** โŒ **The candidate it raised for +play-test finding 4 is DEAD โ€” tested on real hardware, not argued away. See the +final section.** The port's draw path never had a case to answer. Written 2026-09-01 by the Port at `977965e`; HANDOFF on this branch answers `9ca1eb5`. ## The gap this closes @@ -222,3 +222,69 @@ prints on every boot. If their next run says 60 fps, this is finished. iteration. * No `max_fps` or vsync change. It would not raise the rate here, and it is a presentation decision rather than a defect. + + +--- + +# โŒ Settled on hardware: 60โ€“69 fps, and the candidate is dead + +The human activated a **hardware GPU** in both containers on 2026-09-01, which +made the one open half of this page directly testable. Godot picks it up with no +change on our side: + +``` +Vulkan 1.4.312 - Forward+ - Using Device #0: NVIDIA - NVIDIA GeForce GTX 1070 Ti +``` + +## Pre-registered (R2) + +> Five to seven full-screen textured quads at 720p is trivial for that card. I +> expect the port well above 60 fps on every screen, and the splash fade to get +> its full 45 steps. + +| screen | llvmpipe (before) | **GPU (after)** | worst gap | +|---|---|---|---| +| `publisher_logo` | 17.3โ€“25.0 | **69.4** | 83 ms | +| `developer_logos` | 16.7โ€“22.8 | **69.1** | 15 ms | +| `title` | 12.7โ€“17.2 | **61.1** | 67 ms | +| **`main_menu`** | **9.7** | **59.6** | 117 ms | + +**A 3โ€“6ร— jump, and every screen is now at or above 60 fps.** The prediction held. + +## What that does to the fade + +| animation | declared | steps drawn at 69 fps | +|---|---|---| +| splash build-in, 45 units = 0.750 s | 45 alphas | **52** | +| companion glow's rise, 15 units = 0.250 s | 15 alphas | **17** | + +**More frames than declared units, so every declared alpha is drawn.** The +quantisation this page raised does not exist on this hardware โ€” not reduced, +*absent*. + +## โŒ So the candidate is dead, and it died the right way + +It was raised as a mechanism, downgraded by a matched control, and is now closed +by a direct measurement on the hardware in question. **Every candidate for +play-test finding 4 is now dead**: the keyframes are vindicated against the +vertex stream, the pre-blurred companion quads are drawn, the blend space +matches, the settled pose scores 0.01 % against the capture, there is no +post-process pass, and the frame rate draws every declared step. + +๐Ÿ”ด **The port has nothing left that is known to be wrong about the splashes, and +that is a statement about our knowledge rather than about the port.** The human +saw something. Nothing we can measure reproduces it. + +**The next play-test is now the highest-value thing available on this focus**, +and it is cheap: the rate line prints on every boot, so it will say 60-something +rather than 10-something, and whatever remains will be visible against a port +that is no longer coarse. + +## The one figure that did not improve + +`main_menu`'s **worst gap is 117 ms** on the GPU against 150 ms on llvmpipe โ€” +essentially unchanged while the mean improved 6ร—. A hitch that survives a 6ร— fill +speed-up is not fill. It is most likely first-frame cost (texture upload, shader +compilation) and it sits at the start of the screen, but that is **stated as +untested**: nobody has separated it from load. It is small, it is once per +screen, and it is recorded rather than chased. diff --git a/tools/port/check-all b/tools/port/check-all index 4333992d..f5cd2a87 100755 --- a/tools/port/check-all +++ b/tools/port/check-all @@ -36,6 +36,28 @@ step() { # name, expectation, command... esac } +# ๐Ÿ”ด THE DISPLAY CAN BE GONE, AND EVERY GODOT STEP THEN FAILS FOR ONE REASON. +# +# Xvfb does not survive a container restart, and its socket does: /tmp/.X11-unix +# keeps `X97` after the server is gone, so Godot reports +# +# ERROR: X11 Display is not available +# +# rather than "no such display", falls back to Wayland, fails that too, and +# exits non-zero. Every Godot-backed step below would then report red, and all of +# it would mean one thing -- there is no display -- which is exactly the wall of +# meaningless failures a check suite exists to avoid. Cost one run on 2026-09-01 +# before it was noticed. +# +# Checked with `xdpyinfo` rather than by looking for the socket, because the +# stale socket is what makes the failure confusing in the first place. +if ! DISPLAY="$DISPLAY" timeout 10 xdpyinfo >/dev/null 2>&1; then + echo "๐Ÿ”ด no X display on $DISPLAY -- every Godot step below would fail for that one reason." + echo " Xvfb does not survive a container restart and leaves its socket behind. Start it with:" + echo " rm -f /tmp/.X11-unix/X\${DISPLAY#:} ; Xvfb $DISPLAY -screen 0 1280x720x24 -nolisten tcp &" + exit 3 +fi + # ๐Ÿ”ด GODOT'S SCRIPT CLASS LIST IS A BUILD CACHE, AND IT IS GITIGNORED. # # `port/.godot/global_script_class_cache.cfg` is what resolves a `class_name`,