port: the port never reported its own frame rate, and the menu draws at 9.7 fps
TEMPORAL-VERIFICATION §1 requires every instrument to state its achieved rate against its requested one. That has been applied to --film (which I fixed for exactly this), to the Decoder's harnesses and to the oracle. It had never once been applied to the thing being shipped. The port had no idea what rate it drew at and no way to say. It matters now because the splashes are the focus and the open complaint is that ours is LESS PRONOUNCED than the game's. A fade drawn in 45 frames and the same fade drawn in 12 are different animations, and nothing here could tell them apart. boot.gd now counts frames per screen and reports at every boot transition, at the end of the boot, and at every menu arrival. `worst gap` sits beside the mean because a hitch is what reads as wrong and a mean hides one by construction. Measured in this container, three boots: publisher_logo 17.3 / 19.6 / 25.0 fps worst gap 100-115 ms developer_logos 16.7 / 21.9 / 22.8 fps worst gap 103-138 ms title 17.2 / 14.2 / 12.7 fps worst gap 150 ms, all three main_menu 9.7 fps worst gap 150 ms 🟡 A LIVE CANDIDATE FOR PLAY-TEST FINDING 4, and the first one that is not dead. The timeline is delta-driven so durations stay correct at any rate; what changes is how many alphas the fade is DRAWN at. At the measured rates the 45-unit build-in gets 12-17 distinct alphas instead of 45, and the pre-blurred companion glow -- the thing that IS the splash's blur -- rises over 15 units and is drawn at FOUR TO SIX steps instead of fifteen. ⚠️ It is a candidate, not a cause: this is llvmpipe under Xvfb and not the human's hardware. The point is that the line now prints on every boot, so the next play-test answers it for free. Every other candidate for finding 4 is already dead -- keyframes vindicated against the vertex stream, companion quads drawn, blend space matching, settled pose at 0.01 % against the capture, no post-process pass to add. ✅ And nothing published is invalidated, which was worth checking rather than assuming: every timing result here comes from `_elapsed` (+= delta) or `time_units` (the same sum scaled), so all are correct at any frame rate. The splash dwells were measured across runs whose rates differed by 2x and agreed to ±0.03 s. Had the timeline been frame-counted, every number in this corpus would have been wrong by a factor that changed between runs -- which is precisely the failure the Decoder found in the emulator's rate and withdrew a finding over. Two defects in the instrument itself, both caught and fixed before it was trusted: - its first version printed "-9223372036854775808 requested". DisplayServer.screen_get_refresh_rate() returns a FLOAT and is -1.0 when the display cannot say, which Xvfb cannot, and %d underflows to INT64_MIN. It now names the cap or says `uncapped`. - it was BOOT-ONLY and said so nowhere -- `--menu` arrives through _menu_arrive, not _advance, so the mode a human spends time in reported nothing. That is the shape this port keeps finding in other people's work, and it lasted one measurement here. Refutation attempt: I checked whether the port re-decodes PNGs per frame, which would have been a real defect. It does not -- _load_textures caches at load_screen. Hypothesis dead, cheaply, and recorded. Not settled: what rate the human's machine manages; whether 9.7 fps on the menu is llvmpipe or something in our draw path; H6's +0x04 exposure; H1 (the Decoder is taking it this iteration). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AHUQvXGyNcKonSEWsgWcX
This commit is contained in:
@@ -456,11 +456,65 @@ var _elapsed := 0.0
|
||||
var _boot_done := false
|
||||
|
||||
|
||||
## Frames rendered on the current screen, and the worst gap between two of them.
|
||||
##
|
||||
## 🔴 THIS PORT REQUIRED EVERY MEASUREMENT TO REPORT ITS ACHIEVED RATE AND NEVER
|
||||
## REPORTED ITS OWN. `TEMPORAL-VERIFICATION.md` §1 -- a capture that asked for one
|
||||
## rate and delivered another "is not a slow capture, it is a different capture"
|
||||
## -- was applied to `--film`, to the Decoder's harnesses and to the oracle, and
|
||||
## not once to the thing actually being shipped.
|
||||
##
|
||||
## It matters here specifically. The splashes are the current focus, the
|
||||
## developer splash's whole build-in is 45 units = 0.75 s, and the human's
|
||||
## complaint about it is that ours is *less pronounced* than the game's. A fade
|
||||
## drawn in 45 frames and the same fade drawn in 11 are different animations, and
|
||||
## nothing in this port could have told the difference. Every timing check so far
|
||||
## has measured `_elapsed`, which is the sum of the deltas -- it is exactly as
|
||||
## correct at 8 fps as at 60 and says nothing about what a person sees.
|
||||
##
|
||||
## `worst` is kept beside the mean because a hitch is what reads as wrong: a
|
||||
## screen averaging 55 fps with one 400 ms stall looks broken, and a mean hides
|
||||
## that by construction.
|
||||
var _screen_frames := 0
|
||||
var _screen_t0 := 0.0
|
||||
var _worst_gap := 0.0
|
||||
|
||||
|
||||
## What the port managed on the screen just finishing, in the form this project
|
||||
## demands of everyone else: achieved against requested, plus the worst hitch.
|
||||
func _rate_report(name: String) -> String:
|
||||
var span := _elapsed - _screen_t0
|
||||
if span <= 0.0 or _screen_frames == 0:
|
||||
return ""
|
||||
# 🔴 THE "REQUESTED" HALF PRINTED -9223372036854775808 ON ITS FIRST RUN.
|
||||
# `DisplayServer.screen_get_refresh_rate()` returns a float and is -1.0 when
|
||||
# the display cannot say -- which Xvfb cannot -- and `%d` on that underflows
|
||||
# to INT64_MIN. A rate line whose own denominator is nonsense is worse than
|
||||
# no rate line, so it now names the cap or says plainly that there is none.
|
||||
var cap := "uncapped"
|
||||
if Engine.max_fps > 0:
|
||||
cap = "%d requested" % Engine.max_fps
|
||||
else:
|
||||
var hz := DisplayServer.screen_get_refresh_rate()
|
||||
if hz > 0.0:
|
||||
cap = "%.0f Hz display" % hz
|
||||
return " %s: %d frames in %.2f s -- %.1f fps achieved, %s, worst gap %.0f ms" % [
|
||||
name, _screen_frames, span, _screen_frames / span, cap, _worst_gap * 1000.0]
|
||||
|
||||
|
||||
func _rate_reset() -> void:
|
||||
_screen_frames = 0
|
||||
_screen_t0 = _elapsed
|
||||
_worst_gap = 0.0
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if _frozen or view == null:
|
||||
return
|
||||
view.time_units += delta * view.units_per_second
|
||||
_elapsed += delta
|
||||
_screen_frames += 1
|
||||
_worst_gap = maxf(_worst_gap, delta)
|
||||
view.queue_redraw()
|
||||
_overlay_process(delta)
|
||||
|
||||
@@ -554,6 +608,12 @@ func _process(delta: float) -> void:
|
||||
elif not _boot_done:
|
||||
_boot_done = true
|
||||
print("boot sequence complete after %.2f s, holding on %s" % [_elapsed, _sequence[_step].get("screen", _sequence[_step])])
|
||||
# The LAST screen never reaches `_advance`, so without this the one
|
||||
# screen the boot ends on -- the title, where the plate lands -- was
|
||||
# the only one with no rate reported.
|
||||
var last := _rate_report(String(view.screen.get("name", "?")))
|
||||
if last != "":
|
||||
print(last)
|
||||
# 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
|
||||
@@ -588,6 +648,13 @@ func _process(delta: float) -> void:
|
||||
|
||||
|
||||
func _advance() -> void:
|
||||
# Before the step changes, say what the screen that is leaving managed.
|
||||
if view != null:
|
||||
var leaving := String(view.screen.get("name", "?"))
|
||||
var line := _rate_report(leaving)
|
||||
if line != "":
|
||||
print(line)
|
||||
_rate_reset()
|
||||
_drop_overlay()
|
||||
_step += 1
|
||||
var next: Dictionary = _sequence[_step]
|
||||
@@ -958,6 +1025,17 @@ 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 RATE REPORT WAS BOOT-ONLY ON ITS FIRST VERSION, AND SAID SO NOWHERE.
|
||||
# `_advance` walks the boot; the MENU arrives through here, so `--menu` --
|
||||
# the mode a human actually spends time in, and the one where a slow frame is
|
||||
# felt as input lag rather than seen as a coarse fade -- reported nothing. An
|
||||
# instrument covering half the application while its own page claimed "every
|
||||
# boot" is the shape this port keeps finding in other people's work.
|
||||
if view != null:
|
||||
var line := _rate_report(String(view.screen.get("name", "?")))
|
||||
if line != "":
|
||||
print(line)
|
||||
_rate_reset()
|
||||
# The plate goes with the screen it was measured on. See `_drop_overlay`.
|
||||
_drop_overlay()
|
||||
var action: Dictionary = _pending
|
||||
|
||||
Reference in New Issue
Block a user