Linked repository source
examples/rewind_lab/project/main.gd
This read-only view comes from the file in the Gododin repository. It retains its design, example or evidence context.
Download the original file · Return to the guide
extends Node2D
# loop owned by the native subsystem, and the interactive time controls: a
# timeline slider that scrubs through recorded history, a speed dial,
#
# - Interactive (no --eval): the run advances `speed` accumulated ticks per
# physics step; the slider drags the playhead to any recorded tick; a
# left click freezes or unfreezes the picked body; keys: space pause,
# left/right one tick when paused, [ ] speed.
# - Eval (argument-driven, the windowed gate): a scripted scrub proves
# oracle equality against the recorded digest chain, a same-tick
# freeze/unfreeze pair proves the round-trip preserves the replayed
# window bitwise, and the HUD restore-cost counter is cross-checked
# against the native telemetry. The staged readback is then verified
# bitwise and the run shuts down cooperatively.
const PRESET_NAMES := ["pyramid_stack", "newtons_cradle", "chain_bridge", "domino_spiral", "seesaw", "pendulum_chain"]
const WARM_TICKS := 120
const SPEEDS := [0.25, 0.5, 1.0, 2.0, 4.0]
const PICK_RADIUS := 0.75
# The scripted freeze proof's reference window: ticks whose recorded digests
# must come back identical after a rewind plus a same-tick park/resume pair.
const FREEZE_REF_FIRST := 121
const FREEZE_REF_LAST := 180
var failed := false
var lab_ready := false
var eval_active := false
var eval_preset := "pyramid_stack"
var eval_frames := 600
var eval_seed := 20260916
var eval_rewind := -1
var eval_body_cap := 48
var eval_screenshot := false
var steps := 0
# Scrub proof state: the phase machine walks warm -> scrubbed away -> back
# at warm -> reference captured and pair applied -> pair verified.
var scrub_phase := 0
var scrub_digest_before := 0
var scrub_digest_after := 0
var scrub_recorded_target := 0
var scrub_restore_steps := 0
var scrub_verified := false
var freeze_ref_digests := PackedInt64Array()
var freeze_digest_match := false
var freeze_frozen_ok := false
var hud_restore_native := -1
var hud_restore_label := -1
# Interactive state.
var speed_index := 2
var paused := false
var scrubbing := false
var tick_acc := 0.0
func _ready() -> void:
# Staged publishers are consumed by the separate render thread's server
# calls, the same Safe model Bunny_Field requires.
if not RenderingServer.is_on_render_thread():
_fail("separate_render_thread_unsupported")
return
for arg in OS.get_cmdline_user_args():
if arg == "--eval":
eval_active = true
elif arg == "--screenshot":
eval_screenshot = true
elif arg.begins_with("--preset="):
eval_preset = arg.trim_prefix("--preset=")
elif arg.begins_with("--frames=") and arg.trim_prefix("--frames=").is_valid_int():
eval_frames = int(arg.trim_prefix("--frames="))
elif arg.begins_with("--seed=") and arg.trim_prefix("--seed=").is_valid_int():
eval_seed = int(arg.trim_prefix("--seed="))
elif arg.begins_with("--rewind=") and arg.trim_prefix("--rewind=").is_valid_int():
eval_rewind = int(arg.trim_prefix("--rewind="))
elif arg.begins_with("--body-cap=") and arg.trim_prefix("--body-cap=").is_valid_int():
eval_body_cap = int(arg.trim_prefix("--body-cap="))
else:
_fail("unknown_or_invalid_argument=" + arg)
return
var preset_index := PRESET_NAMES.find(eval_preset)
if preset_index < 0:
_fail("bad_preset=" + eval_preset)
return
if eval_frames < 1 or eval_frames > 7200:
_fail("bad_frames")
return
# The scrub target is any recorded tick strictly below the warm tick.
if eval_rewind >= 0 and (eval_rewind < 1 or eval_rewind >= WARM_TICKS):
_fail("bad_rewind_target")
return
if not _build_unit_meshes():
return
# Native time: the tick itself stays 1/60 simulation time in both modes.
# Eval runs four times wall speed to keep the gate quick; interactive
# runs one tick per step and paces through the speed accumulator.
if eval_active:
Engine.set_physics_ticks_per_second(240)
else:
Engine.set_physics_ticks_per_second(60)
var code: int = $TimeLab.call("setup", preset_index, eval_seed, 1.0, 4, 3600, eval_body_cap)
if not _check(code, "setup"):
return
lab_ready = true
if eval_active:
Engine.max_fps = 0
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED, get_window().get_window_id())
print("GODODIN_REWIND_LAB_EVAL_START workload=rewind-lab preset=%s frames=%d rewind=%d seed=%d bodies=%d" % [
eval_preset, eval_frames, eval_rewind, eval_seed, $TimeLab.call("body_count")])
else:
%Timeline.value_changed.connect(_on_timeline_value_changed)
%Timeline.drag_started.connect(_on_timeline_drag_started)
%Timeline.drag_ended.connect(_on_timeline_drag_ended)
%Slower.pressed.connect(_on_slower)
%Faster.pressed.connect(_on_faster)
%Pause.pressed.connect(_on_pause_pressed)
_update_hud()
# Unit meshes for the staged instances: a 2x2 quad (scaled by box half
# extents) and a 24-segment unit-radius circle (scaled by ball radii), both
# plain white so the staged instance color multiplies in untouched.
func _build_unit_meshes() -> bool:
var quad_arrays := []
quad_arrays.resize(Mesh.ARRAY_MAX)
quad_arrays[Mesh.ARRAY_VERTEX] = PackedVector3Array([Vector3(-1, -1, 0), Vector3(1, -1, 0), Vector3(1, 1, 0), Vector3(-1, 1, 0)])
quad_arrays[Mesh.ARRAY_INDEX] = PackedInt32Array([0, 1, 2, 0, 2, 3])
var quad := ArrayMesh.new()
quad.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, quad_arrays)
$TimeLab/Boxes.multimesh.mesh = quad
$TimeLab/Statics.multimesh.mesh = quad
var points := PackedVector3Array()
var indices := PackedInt32Array()
points.append(Vector3(0, 0, 0))
for i in 24:
var a := TAU * i / 24.0
points.append(Vector3(cos(a), sin(a), 0))
if i < 23:
indices.append_array([0, 1 + i, 2 + i])
else:
indices.append_array([0, 24, 1])
var circle_arrays := []
circle_arrays.resize(Mesh.ARRAY_MAX)
circle_arrays[Mesh.ARRAY_VERTEX] = points
circle_arrays[Mesh.ARRAY_INDEX] = indices
var circle := ArrayMesh.new()
circle.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, circle_arrays)
$TimeLab/Circles.multimesh.mesh = circle
return true
func _physics_process(_delta: float) -> void:
if not lab_ready or failed:
return
if eval_active:
_eval_step()
return
if not paused and not scrubbing:
tick_acc += SPEEDS[speed_index]
var n := int(tick_acc)
if n > 0:
tick_acc -= n
if not _check($TimeLab.call("advance", n), "advance"):
return
_update_timeline()
_update_hud()
# --- interactive time controls -------------------------------------------------
func _unhandled_input(event: InputEvent) -> void:
if failed or not lab_ready or eval_active:
return
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
var pos := get_global_mouse_position()
var slot: int = $TimeLab.call("pick", pos.x, pos.y, PICK_RADIUS)
if slot < 0:
return
if $TimeLab.call("frozen", slot) == 1:
_check($TimeLab.call("unfreeze", slot), "unfreeze")
else:
_check($TimeLab.call("freeze", slot), "freeze")
elif event is InputEventKey and event.pressed and not event.echo:
match event.keycode:
KEY_SPACE:
_on_pause_pressed()
KEY_LEFT:
_step_back()
KEY_RIGHT:
_step_forward()
KEY_BRACKETLEFT:
_on_slower()
KEY_BRACKETRIGHT:
_on_faster()
func _on_timeline_drag_started() -> void:
scrubbing = true
func _on_timeline_drag_ended(_changed: bool) -> void:
scrubbing = false
func _on_timeline_value_changed(value: float) -> void:
if not lab_ready or failed or not scrubbing:
return
if not _check($TimeLab.call("rewind_to", int(value)), "rewind_to"):
return
_update_hud()
func _on_slower() -> void:
if speed_index > 0:
speed_index -= 1
tick_acc = 0.0
func _on_faster() -> void:
if speed_index < SPEEDS.size() - 1:
speed_index += 1
func _on_pause_pressed() -> void:
paused = not paused
%Pause.text = "play" if paused else "pause"
func _step_back() -> void:
var tick: int = $TimeLab.call("tick")
if tick <= $TimeLab.call("window_base"):
return
if _check($TimeLab.call("rewind_to", tick - 1), "rewind_to"):
_update_timeline()
_update_hud()
func _step_forward() -> void:
if _check($TimeLab.call("advance", 1), "advance"):
_update_timeline()
_update_hud()
func _update_timeline() -> void:
var base: int = $TimeLab.call("window_base")
var tick: int = $TimeLab.call("tick")
%Timeline.min_value = base
%Timeline.max_value = tick
if not scrubbing:
%Timeline.set_value_no_signal(tick)
func _update_hud() -> void:
%HUD.text = "tick=%d speed=%.2fx bodies=%d frozen=%d subs=%d rebuilds=%d restore_steps=%d digest=%d" % [
$TimeLab.call("tick"), SPEEDS[speed_index], $TimeLab.call("body_count"),
$TimeLab.call("frozen_count"), $TimeLab.call("submissions"), $TimeLab.call("rebuilds"),
$TimeLab.call("restore_steps"), $TimeLab.call("state_digest")]
# --- eval gate ------------------------------------------------------------------
func _eval_step() -> void:
var tick: int = $TimeLab.call("tick")
# Phase 1, at the warm tick: capture the live digest and the history
# ring's record for the target, then scrub to the target.
if eval_rewind >= 0 and scrub_phase == 0 and steps == WARM_TICKS:
scrub_digest_before = $TimeLab.call("state_digest")
scrub_recorded_target = $TimeLab.call("recorded_digest", eval_rewind)
var replayed: int = $TimeLab.call("rewind_to", eval_rewind)
if not _check(replayed, "rewind_to"):
return
scrub_restore_steps = $TimeLab.call("restore_steps")
scrub_phase = 1
# Phase 2, back at the warm tick: the replayed live digest must equal
# the pre-scrub digest bitwise, and the ring's record for the target
# must have been rewritten identically.
if eval_rewind >= 0 and scrub_phase == 1 and steps > WARM_TICKS and tick == WARM_TICKS:
scrub_digest_after = $TimeLab.call("state_digest")
# Fingerprints are raw u64 patterns seen as i64, so only the named
# -1/-2 error codes mean a missing record, never a bare negativity
# check.
scrub_verified = scrub_digest_after == scrub_digest_before \
and scrub_recorded_target != -1 and scrub_recorded_target != -2 \
and $TimeLab.call("recorded_digest", eval_rewind) == scrub_recorded_target
print("GODODIN_REWIND_LAB_SCRUB target=%d restore_steps=%d digest_match=%s" % [
eval_rewind, scrub_restore_steps, str(scrub_verified).to_lower()])
if not scrub_verified:
_fail("scrub_digest_mismatch")
return
scrub_phase = 2
# Phase 3, at the end of the reference window: record the frames chain's
# whole ring, then rewind to the warm tick and cross-check the HUD's
# restore-cost counter. The rewind starts the control chain: a pure
# replay to 120 continued by frame-stepping — the same interleaving
# shape the pair chain will have.
if eval_rewind >= 0 and scrub_phase == 2 and steps > WARM_TICKS and tick == FREEZE_REF_LAST:
freeze_ref_digests.resize(FREEZE_REF_LAST + 1)
for t in range(1, FREEZE_REF_LAST):
freeze_ref_digests[t] = $TimeLab.call("recorded_digest", t)
freeze_ref_digests[FREEZE_REF_LAST] = $TimeLab.call("state_digest")
if not _check($TimeLab.call("rewind_to", WARM_TICKS), "rewind_to"):
return
hud_restore_native = $TimeLab.call("restore_steps")
_update_hud()
hud_restore_label = -1
for token in %HUD.text.split(" "):
if token.begins_with("restore_steps="):
hud_restore_label = int(token.trim_prefix("restore_steps="))
var hud_match := hud_restore_label == hud_restore_native
print("GODODIN_REWIND_LAB_HUD_PASS native=%d hud=%d match=%s" % [
hud_restore_native, hud_restore_label, str(hud_match).to_lower()])
if not hud_match:
_fail("hud_restore_mismatch")
return
scrub_phase = 3
# Phase 4, back at the window end on the control chain: capture its ring
# as the pair proof's reference, and record the measured finding that
# replay chains and frames-interleaved chains carry the same visible
# state through the settling window but need not stay identical past it.
if eval_rewind >= 0 and scrub_phase == 3 and steps > WARM_TICKS and tick == FREEZE_REF_LAST:
var control_ring := PackedInt64Array()
control_ring.resize(FREEZE_REF_LAST + 1)
for t in range(1, FREEZE_REF_LAST):
control_ring[t] = $TimeLab.call("recorded_digest", t)
control_ring[FREEZE_REF_LAST] = $TimeLab.call("state_digest")
var interleave_match: bool = control_ring[FREEZE_REF_LAST] == freeze_ref_digests[FREEZE_REF_LAST]
for t in range(1, FREEZE_REF_LAST):
if control_ring[t] != freeze_ref_digests[t]:
interleave_match = false
print("GODODIN_REWIND_LAB_INTERLEAVE window_match=%s" % str(interleave_match).to_lower())
if not _check($TimeLab.call("rewind_to", WARM_TICKS), "rewind_to"):
return
# Park and resume slot 0 with a same-tick freeze/unfreeze pair: both
# commands execute before tick 120's step.
if not _check($TimeLab.call("freeze", 0), "freeze"):
return
if not _check($TimeLab.call("unfreeze", 0), "unfreeze"):
return
freeze_ref_digests = control_ring
scrub_phase = 4
# Phase 5, the pair replayed back to the window end: the whole window's
# digests must be bitwise the reference's, and the frozen machinery must
# round-trip its counters. The counter checks ride real execution: the
# freeze applies at the next tick's command sweep, so each toggle is
# observed after one advance.
if eval_rewind >= 0 and scrub_phase == 4 and steps > WARM_TICKS and tick == FREEZE_REF_LAST:
freeze_digest_match = $TimeLab.call("state_digest") == freeze_ref_digests[FREEZE_REF_LAST]
for t in range(FREEZE_REF_FIRST, FREEZE_REF_LAST):
if $TimeLab.call("recorded_digest", t) != freeze_ref_digests[t]:
freeze_digest_match = false
freeze_frozen_ok = $TimeLab.call("frozen_count") == 0
if not _check($TimeLab.call("freeze", 0), "freeze"):
return
if not _check($TimeLab.call("advance", 1), "advance"):
return
freeze_frozen_ok = freeze_frozen_ok and $TimeLab.call("frozen_count") == 1 \
and $TimeLab.call("frozen", 0) == 1
if not _check($TimeLab.call("unfreeze", 0), "unfreeze"):
return
if not _check($TimeLab.call("advance", 1), "advance"):
return
freeze_frozen_ok = freeze_frozen_ok and $TimeLab.call("frozen_count") == 0
if not freeze_digest_match:
for t in range(1, FREEZE_REF_LAST):
var now: int = $TimeLab.call("recorded_digest", t)
if now != freeze_ref_digests[t]:
print("GODODIN_REWIND_LAB_FREEZE_DEBUG first_divergence_tick=%d ref=%d now=%d endpoint_match=%s" % [
t, freeze_ref_digests[t], now,
str($TimeLab.call("state_digest") == freeze_ref_digests[FREEZE_REF_LAST]).to_lower()])
break
print("GODODIN_REWIND_LAB_FREEZE_PASS slot=0 digest_match=%s frozen_ok=%s" % [
str(freeze_digest_match).to_lower(), str(freeze_frozen_ok).to_lower()])
if not freeze_digest_match or not freeze_frozen_ok:
_fail("freeze_round_trip")
return
scrub_phase = 5
if steps >= eval_frames:
_finish_eval()
return
if not _check($TimeLab.call("advance", 1), "advance"):
return
steps += 1
_update_hud()
func _dump_readback() -> void:
for child in [$TimeLab/Boxes, $TimeLab/Circles, $TimeLab/Statics]:
var buf: PackedFloat32Array = RenderingServer.multimesh_get_buffer(child.get_multimesh().get_rid())
print(child.name, " len=", buf.size())
for i in mini(22, buf.size()):
print(child.name, " [", i, "] ", buf[i])
func _finish_eval() -> void:
if not _check($TimeLab.call("verify"), "staged_readback"):
_dump_readback()
return
print("GODODIN_REWIND_LAB_READBACK_PASS meshes=3 stride=12 bitwise=true")
var total_submissions: int = $TimeLab.call("submissions")
if not _shutdown():
return
if DisplayServer.get_name() == "headless":
_fail("rendered_campaign_requires_display")
return
if eval_screenshot:
await RenderingServer.frame_post_draw
var image := get_viewport().get_texture().get_image()
if image == null or image.is_empty() or image.save_png("user://rewind-lab-%s.png" % eval_preset) != OK:
_fail("screenshot")
return
print("GODODIN_REWIND_LAB_PASS workload=rewind-lab preset=%s frames=%d bodies=%d verified=3 rewind=%d scrub=%s restore_steps=%d freeze=%s hud=%s submissions=%d" % [
eval_preset, eval_frames, eval_body_cap, eval_rewind,
str(scrub_verified).to_lower() if scrub_started() else "n/a", scrub_restore_steps,
str(freeze_digest_match and freeze_frozen_ok).to_lower() if scrub_started() else "n/a",
str(hud_restore_label == hud_restore_native and hud_restore_native >= 0).to_lower() if hud_restore_native >= 0 else "n/a",
total_submissions])
print("GODODIN_REWIND_LAB_TEARDOWN_PASS staging_live=false")
get_tree().quit(0)
func scrub_started() -> bool:
return scrub_phase >= 2
func _check(code: int, context: String) -> bool:
# Setup/verify/shutdown return 0 on success; advance, rewind_to, and the
# pick return the reached playhead / the picked slot. Every other
# negative value is a named error code from the authored class.
return true if code >= 0 else _fail("%s code=%d" % [context, code])
func _fail(detail: String) -> bool:
if not failed:
failed = true
push_error("GODODIN_REWIND_LAB_FAIL " + detail)
get_tree().quit(1)
return false
func _shutdown() -> bool:
var code: int = $TimeLab.call("shutdown")
lab_ready = false
if code != 0:
return _fail("shutdown code=%d" % code)
return true
func _exit_tree() -> void:
if lab_ready:
_shutdown()