port: the PRESS (A) plate pulses -- authored per element, because 82 of 212 share its shape
The human listed pulsation as first-class and the port drew nothing: the plate's focus record `ptbtn00f` was never reached, because press_start has no `buttons` and nothing is focused. That it LOOPS is measured -- the corpus timed the period four times (2.12 / 2.19 / 2.34 / 2.31 s) and you cannot measure a period unless the thing repeats. THE RULE I WAS GOING TO WRITE DIED IN THE CENSUS. The spinning ring is a rule in the renderer because it has a disc-wide check: 16 of 212 elements match its shape and all 16 are focus rings. The analogous shape for a pulse -- keyframes varying only in alpha, first alpha equal to last -- matches 82 OF 212, including ptcopyright, palogo_sqex, ptmsg and every _eff fade. A renderer rule on it would make the copyright notice pulse. Narrowed to focus records it matches exactly one distinct element, and a rule justified by n=1 is a special case wearing a rule's clothes. So it is a LOOKUP in authored/timing.json keyed <screen>/<element>, with the census recorded beside it so nobody widens it later. The period is 129 units -- the element's own group under the port's existing model: last timed keyframe t=105 plus the authored exit_ramp_units of 24. No new constant. 2.150 s at 60 units/s, 2.295 s at the ~28.1 fps the emulator presents, against measurements of 2.12-2.34. IT IS A CHOICE AND THE ALTERNATIVE IS STATED: restarting at the group's first keyframe (t=6) instead of 0 gives 123 units = 2.050 / 2.189 s, also inside the measured spread. Nothing separates them. t=0 is taken because it is where every other group starts -- consistency, not evidence. Verified the way the ring was, by bit-identity one period apart. 20 periods is 43.00 s = exactly 172 film frames: frames N and N+172 differ by 0-1/255, while the control a quarter-second off (43.25 s) differs by 58.7/255. On the held boot title the glow-box mean swings 26.0 <-> 37.7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N7FiFFFwbvG2uxdcEh8HyF
This commit is contained in:
@@ -178,8 +178,13 @@ func _ready() -> void:
|
||||
# The one unknown duration per screen: the ramp into the final untimed
|
||||
# keyframe. Authored, because the disc has no time slot there.
|
||||
view.exit_ramp_units = float(timing["exit_ramp_units"])
|
||||
# Which focus records draw unconditionally and loop. Kept out of ScreenView's
|
||||
# own logic on purpose -- see `looping_focus` there for the census that says
|
||||
# this cannot be a rule.
|
||||
_looping = timing.get("looping_focus_records", {})
|
||||
viewport.add_child(view)
|
||||
|
||||
view.looping_focus = _looping_for(name)
|
||||
if not view.load_screen(export_tree, name):
|
||||
push_error(export_tree.error)
|
||||
get_tree().quit(2)
|
||||
@@ -241,6 +246,8 @@ var _flow: Variant = null
|
||||
var _menu: MenuFlow = null
|
||||
var _play := false
|
||||
var _pending: Variant = null
|
||||
## `authored/timing.json` `looping_focus_records`, keyed `<screen>/<element>`.
|
||||
var _looping: Dictionary = {}
|
||||
var _script: PackedStringArray = PackedStringArray()
|
||||
var _shots := ""
|
||||
## `--skip-at=SECONDS`: when to send a synthetic (A) during a movie, or 0.
|
||||
@@ -340,6 +347,7 @@ func _advance() -> void:
|
||||
print(" -> %s at %.2f s" % [name, _elapsed])
|
||||
view.holding = true
|
||||
view.time_units = 0.0
|
||||
view.looping_focus = _looping_for(name)
|
||||
if not view.load_screen(view.tree, name):
|
||||
push_error(view.tree.error)
|
||||
get_tree().quit(2)
|
||||
@@ -541,6 +549,7 @@ func _menu_arrive() -> void:
|
||||
var name := String(action["goto"])
|
||||
view.holding = true
|
||||
view.time_units = 0.0
|
||||
view.looping_focus = _looping_for(name)
|
||||
if not view.load_screen(view.tree, name):
|
||||
push_error(view.tree.error)
|
||||
get_tree().quit(2)
|
||||
@@ -854,6 +863,7 @@ func _raise_overlay(name: String) -> void:
|
||||
overlay.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
overlay.units_per_second = view.units_per_second
|
||||
overlay.exit_ramp_units = view.exit_ramp_units
|
||||
overlay.looping_focus = _looping_for(name)
|
||||
overlay.holding = true
|
||||
overlay.time_units = 0.0
|
||||
if not overlay.load_screen(view.tree, name):
|
||||
@@ -925,3 +935,19 @@ func _finish_boot() -> void:
|
||||
|
||||
|
||||
var _capture_to := ""
|
||||
|
||||
|
||||
## The authored looping-focus entries that apply to one screen.
|
||||
##
|
||||
## The table is keyed `<screen>/<element>` so a reader can see at a glance which
|
||||
## screen an entry belongs to -- `ptbtn00` exists on more than one build, and an
|
||||
## entry that silently applied to all of them would be a rule again.
|
||||
func _looping_for(screen_name: String) -> Dictionary:
|
||||
var out := {}
|
||||
for key: String in _looping.keys():
|
||||
if key == "_":
|
||||
continue
|
||||
var parts := key.split("/", true, 1)
|
||||
if parts.size() == 2 and parts[0] == screen_name:
|
||||
out[parts[1]] = _looping[key]
|
||||
return out
|
||||
|
||||
@@ -42,6 +42,22 @@ var units_per_second: float = 60.0
|
||||
## that keyframe, so this is the one unknown duration per screen.
|
||||
var exit_ramp_units: float = 24.0
|
||||
|
||||
## Focus records this screen draws unconditionally, and the period each loops on.
|
||||
##
|
||||
## `{ <parent element id>: { "record_element": String, "period_units": float } }`,
|
||||
## from `authored/timing.json` `looping_focus_records`, keyed there by
|
||||
## `<screen>/<element>` and narrowed to this screen by `load_screen`.
|
||||
##
|
||||
## ⚠️ A LOOKUP, NOT A RULE, and the census is why. The spinning ring is a rule
|
||||
## (`spin_period_units`) because 16 of 212 elements match its shape and all 16
|
||||
## are focus rings. The analogous rule for a pulse -- keyframes varying only in
|
||||
## alpha, first alpha equal to last -- matches **82 of 212**, including
|
||||
## `ptcopyright`, `palogo_sqex`, `ptmsg` and every `_eff` fade. It would make the
|
||||
## copyright notice pulse. Narrowed to focus records it matches exactly one
|
||||
## distinct element, and a rule justified by n=1 is a special case wearing a
|
||||
## rule's clothes.
|
||||
var looping_focus: Dictionary = {}
|
||||
|
||||
## While true the screen holds at `rest` and never plays its exit. The
|
||||
## sequencer clears it to send the screen away.
|
||||
var holding: bool = true
|
||||
@@ -353,6 +369,7 @@ static func _rot_of(pose: Dictionary) -> float:
|
||||
## down-right and off-centre.
|
||||
func _draw_focus(element: Dictionary) -> void:
|
||||
var focus: Dictionary = element.get("focus", {})
|
||||
var parent_id := String(element.get("id", ""))
|
||||
for fe: Dictionary in focus.get("elements", []):
|
||||
var rel: String = fe.get("sprite", "")
|
||||
if rel == "":
|
||||
@@ -377,6 +394,19 @@ func _draw_focus(element: Dictionary) -> void:
|
||||
# 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", {})
|
||||
# An authored loop plays the record's OWN group on repeat instead of
|
||||
# holding it at rest. `pose_at` already synthesises the final untimed
|
||||
# keyframe at `exit_ramp_units`, so a loop is a modulo and nothing else --
|
||||
# no new machinery and no new constant. `holding` is bypassed for the
|
||||
# same reason the ring bypasses it: a thing that pulses does not stop
|
||||
# because the screen has arrived.
|
||||
var loop: Dictionary = looping_focus.get(parent_id, {})
|
||||
if float(loop.get("period_units", 0.0)) > 0.0 \
|
||||
and String(loop.get("record_element", "")) == String(fe.get("id", "")):
|
||||
var was := holding
|
||||
holding = false
|
||||
pose = pose_at(fe, fposmod(time_units, float(loop["period_units"])))
|
||||
holding = was
|
||||
var pivot := _vec(fe.get("pivot", [0, 0]))
|
||||
var pos := _vec(pose.get("pos", [0, 0]))
|
||||
var period := spin_period_units(fe)
|
||||
@@ -413,8 +443,11 @@ func _draw() -> void:
|
||||
var pivot := _vec(element.get("pivot", [0, 0]))
|
||||
var pos := _vec(pose.get("pos", [0, 0]))
|
||||
var rot := _rot_of(pose)
|
||||
# A focused button draws its own record instead of its base sprite.
|
||||
if focused_id == id and element.has("focus"):
|
||||
# A focused button draws its own record instead of its base sprite -- and
|
||||
# so does an element the authored table says always shows it, which is
|
||||
# how the PRESS (A) plate gets its glow on a screen that has no buttons
|
||||
# and focuses nothing.
|
||||
if (focused_id == id or looping_focus.has(id)) and element.has("focus"):
|
||||
_draw_focus(element)
|
||||
continue
|
||||
var rel: String = element.get("sprite", "")
|
||||
|
||||
Reference in New Issue
Block a user