port: the ring spins, the plate needs no constant, and rest.t was never the settle
Two milestones' known-wrong bits, both now answered by the RE agent, both taken. P5 -- the focus ring. It was drawn at 0 with a comment saying so. The period is now measured (continuous spin, eight evenly spaced autocorrelation peaks over nine revolutions, no angle estimated anywhere) and it needs NO authored constant: the period is the element's own declared t=120, and what the measurement adds is only that the turn repeats rather than stopping -- which "groups hold" could not decide, because 0 and 360 are the same pose. `spin_period_units` is structural and narrow on purpose: two keyframes, differing in nothing but rotation_deg, by a full 360, first timed and second untimed. 16 of 212 elements in this export match and all 16 are focus rings, zero false positives. That check is the point -- the measurement was taken on ONE button of ONE screen, and a rule that caught anything else would be extrapolating it to elements nobody watched. Verified on the port's own render with the RE agent's own control: bit-identical one period apart across the whole frame, 3.6/255 inside the ring's box at quarter-period steps, and box luminance conserved to 0.027 % over eight phases -- which is the observable they used to separate rotation from a pulse. Not claimed: direction (no signed angle was ever measured) and phase across a focus change (their run held focus throughout). P3 -- the plate. Last iteration I refuted their authoring instruction and shipped it anyway rather than pick between two of their numbers. The refutation held and the answer came back better than either option I offered: AUTHOR NOTHING. Both builds run on one clock started together and the plate arrives at its own declared t=238. The 2.13 s constant is deleted. The premise that failed was mine: rest.t IS NOT WHEN A SCREEN SETTLES. It is the last hold keyframe before the exit. ptlogo1 stops MOVING at t=42 and then creeps 5 px and 31 alpha steps to t=251. Reading rest.t put build 4's arrival at 4.350 s instead of 1.967 s, and the "2.51 s, which is not a landmark of anything" I sent them is that error wearing a decimal point. 238 - 118 = 120 units = 2.000 s against a measured 2.135 s at 28.1 fps presentation. Checked against my own export before touching anything. `ScreenView.settle_time()` still uses rest.t, and so the boot sequencer paces every screen off the wrong landmark. NOT changed here: "visible arrival" is a heuristic and getting it wrong re-paces everything. Filed, and asked for a timed boot instead now that their oracle is live. REFUTATION: two of their pages measure the same declared 120 units of wall clock during a static hold and disagree by 2 % -- plate 2.135 s (28.10 fps implied), ring 2.177 s (27.56 fps). That is seven times the plate page's own 6 ms run-to-run agreement, and it lands on the argument that page uses to justify itself: "the build-in is where frames are dropped; the static hold is not". Also the ring page's band, 27.6-28.8 fps, does not contain its own measurement -- the mean needs 27.56 and four of seven spacings are outside. Filed, not worked around: my port uses the declared 120 units either way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WM5XL4HfrHuxz8RiMWdCMC
This commit is contained in:
@@ -52,9 +52,14 @@ 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`.
|
||||
## `title` step): build 4 presents alone and the `PRESS Ⓐ BUTTON` plate -- build
|
||||
## 2 -- arrives later.
|
||||
##
|
||||
## **They share one clock, started together, and there is no authored delay.**
|
||||
## The plate arrives at its own declared `t = 238`; build 4's visible build-in
|
||||
## ends at `t = 118`; the 120-unit difference is 2.000 s, against an oracle that
|
||||
## measured 2.138 s and 2.132 s at an emulator presenting 28.1 fps rather than
|
||||
## 30. A port running at a true 30 Hz wants the declared 120, not the wall clock.
|
||||
##
|
||||
## 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
|
||||
@@ -198,6 +203,16 @@ func _ready() -> void:
|
||||
# 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.
|
||||
# A boot whose FIRST step declares an overlay: `_advance` raises it for every
|
||||
# later step, and `_ready` is the one with no `_advance` in front of it.
|
||||
# Today only the last step has one, so this is a guard rather than a fix --
|
||||
# but a silently missing second build is exactly the failure P3 just spent an
|
||||
# iteration on.
|
||||
if not _sequence.is_empty() and typeof(_sequence[0].get("overlay", null)) == TYPE_DICTIONARY:
|
||||
_overlay_spec = _sequence[0]["overlay"]
|
||||
_overlay_due = 0.0
|
||||
_overlay_process(0.0)
|
||||
|
||||
if args.has("overlay") and not args.has("boot"):
|
||||
_overlay_spec = {"screen": args["overlay"]}
|
||||
_overlay_due = 0.0
|
||||
@@ -272,12 +287,11 @@ func _process(delta: float) -> void:
|
||||
# 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])
|
||||
if overlay != null:
|
||||
# It was raised with the screen, 120 units ago. Nothing to do
|
||||
# here any more -- this hook used to start an authored 2.13 s
|
||||
# timer, and the timer was the bug.
|
||||
pass
|
||||
# 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
|
||||
@@ -304,6 +318,14 @@ func _advance() -> void:
|
||||
if not view.load_screen(view.tree, name):
|
||||
push_error(view.tree.error)
|
||||
get_tree().quit(2)
|
||||
return
|
||||
# The second build starts WITH the first, not after it. Raised here rather
|
||||
# than at the screen's settle, which is what the authored-delay version did.
|
||||
var spec: Variant = next.get("overlay", null)
|
||||
if typeof(spec) == TYPE_DICTIONARY:
|
||||
_overlay_spec = spec
|
||||
_overlay_due = _elapsed
|
||||
_overlay_process(0.0)
|
||||
|
||||
|
||||
## Play one transcoded movie, full-bleed over the screen.
|
||||
@@ -716,7 +738,11 @@ var _overlay_due: float = 0.0
|
||||
|
||||
func _overlay_process(delta: float) -> void:
|
||||
if overlay != null:
|
||||
overlay.time_units += delta * overlay.units_per_second
|
||||
# ONE CLOCK. Not `+= delta * ups` on each independently: they would drift
|
||||
# apart by a frame here and there, and the whole content of the finding
|
||||
# is that the 120 units between build 4's last ramp and the plate's
|
||||
# `a=255` is a fixed interval on a shared timeline.
|
||||
overlay.time_units = view.time_units
|
||||
overlay.queue_redraw()
|
||||
if _overlay_quit_at >= 0.0 and _elapsed >= _overlay_quit_at:
|
||||
print("boot ends on %s + %s at %.2f s"
|
||||
@@ -763,26 +789,17 @@ func _raise_overlay(name: String) -> void:
|
||||
# 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.
|
||||
# `--screen --overlay=` uses the same code path 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))])
|
||||
print(" plate reaches full alpha at t=%d (%.2f s on the shared clock), \
|
||||
120 units after build 4's last build-in ramp at t=118"
|
||||
% [int(overlay.settle_time()), visible_at])
|
||||
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)
|
||||
print(" boot ends at %.2f s, once the plate has arrived" % _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"):
|
||||
|
||||
@@ -228,6 +228,41 @@ static func settle_units(element: Dictionary) -> float:
|
||||
return last
|
||||
|
||||
|
||||
## How long one turn takes, in keyframe units, for an element that spins — or 0.
|
||||
##
|
||||
## The rule is STRUCTURAL and narrow: exactly two keyframes, differing in
|
||||
## **nothing but** `rotation_deg`, by a full 360, with the first timed and the
|
||||
## second untimed. The period is the first keyframe's declared `t`.
|
||||
##
|
||||
## Its disc-wide check, over this export: **16 of 212 elements match, and all 16
|
||||
## are focus rings** — `ptbtneff01` on the five main-menu buttons and
|
||||
## `ptbtneff02` on the three `EXTRAS` buttons, in both locales, every one of them
|
||||
## declaring `t = 120`. Zero false positives. That matters because the rule is
|
||||
## applied on the strength of a measurement taken on **one** button of one
|
||||
## screen; a rule that also caught something else would be extrapolating from
|
||||
## that measurement to elements nobody watched.
|
||||
##
|
||||
## ⚠️ It is a rule about SHAPE, not a decoded field. Nothing on the disc says
|
||||
## "this loops". What the disc says is 0° → 360° over `t`; what the RE agent
|
||||
## measured is that the turn repeats rather than stopping. Those are two
|
||||
## different sources and the day a loop flag is decoded, this goes.
|
||||
static func spin_period_units(element: Dictionary) -> float:
|
||||
var frames: Array = element.get("keyframes", [])
|
||||
if frames.size() != 2:
|
||||
return 0.0
|
||||
var a: Dictionary = frames[0]
|
||||
var b: Dictionary = frames[1]
|
||||
if not a.has("t") or b.has("t"):
|
||||
return 0.0
|
||||
for key in ["pos", "scale", "tint_rgba", "fade_argb"]:
|
||||
if a.get(key) != b.get(key):
|
||||
return 0.0
|
||||
if absf(float(b.get("rotation_deg", 0)) - float(a.get("rotation_deg", 0))) != 360.0:
|
||||
return 0.0
|
||||
var t := float(a["t"])
|
||||
return t if t > 0.0 else 0.0
|
||||
|
||||
|
||||
## The moment the whole screen has arrived: the last element to reach its hold.
|
||||
func settle_time() -> float:
|
||||
var last := 0.0
|
||||
@@ -326,26 +361,33 @@ func _draw_focus(element: Dictionary) -> void:
|
||||
if tex == null:
|
||||
skipped.append("%s (focus sprite failed to load)" % fe.get("id", ""))
|
||||
continue
|
||||
# The ring's rest pose, which is rotation_deg 0.
|
||||
# The ring spins, and until 2026-08-29 this drew it at 0 -- a pose the
|
||||
# running game never shows -- because the PERIOD was the missing piece
|
||||
# and a spin rate would have been invented.
|
||||
#
|
||||
# ⚠️ THIS IS KNOWN TO BE WRONG, and is drawn anyway because the right
|
||||
# answer is a guess. The spin is real -- rotation_deg ramps 0 -> 360
|
||||
# with position, scale and alpha all constant -- and measuring the two
|
||||
# oracle captures says the game never shows 0: the same sprite sits at
|
||||
# ~76 deg with NEW GAME focused and ~210 deg with OPTIONS focused,
|
||||
# 134 deg apart at peak correlation 0.97 against a null control of 0.37
|
||||
# (docs/port/DECISIONS.md, "the focus ring IS drawn rotated").
|
||||
# It is no longer invented. `docs/re/focus-ring-spin-measured.md`
|
||||
# measures a continuous spin, period 2.177 s wall-clock, from eight
|
||||
# evenly spaced autocorrelation peaks over nine revolutions, with NO
|
||||
# angle estimated anywhere -- both angle estimators failed their own
|
||||
# controls and were not used. It reconciles with the declared `t = 120`
|
||||
# without a new constant: 120 units is 60 rendered frames, 2.00 s at a
|
||||
# true 30 Hz and 2.08-2.17 s at the 27.6-28.8 fps that emulator runs.
|
||||
#
|
||||
# What is missing is the PERIOD, and it has two unknowns, both the
|
||||
# Decoder's: the ramp's second keyframe is untimed, and "groups hold"
|
||||
# predicts a stop at 360 = 0, which is not what either capture shows.
|
||||
# Holding at 0 is the pose that invents nothing; a spin rate would be
|
||||
# invented. See docs/port/BLOCKED.md.
|
||||
# So the period comes off the DISC -- the element's own declared `t` --
|
||||
# and what the RE agent supplied is that one turn takes exactly that
|
||||
# long and repeats. See `spin_period_units` for the rule and its check.
|
||||
var pose: Dictionary = fe.get("rest", {})
|
||||
var pivot := _vec(fe.get("pivot", [0, 0]))
|
||||
var pos := _vec(pose.get("pos", [0, 0]))
|
||||
var period := spin_period_units(fe)
|
||||
var rot := _rot_of(pose)
|
||||
if period > 0.0:
|
||||
# `time_units` raw, NOT the pose clamped by `holding`: a spinning
|
||||
# ring is the one thing on the settled main menu that keeps moving,
|
||||
# and the whole point of the finding is that it does not stop.
|
||||
rot = 360.0 * fposmod(time_units, period) / period
|
||||
_draw_quad(tex, placement(pose, pivot, tex.get_size()), modulate_of(pose),
|
||||
pivot, pos, _rot_of(pose))
|
||||
pivot, pos, rot)
|
||||
drawn.append(fe.get("id", ""))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user