Gododin Gododin Documentation

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

# RL-3: one authored TimeLab class, three staged MultiMeshes, one fixed tick
# loop owned by the native subsystem, and the interactive time controls: a
# timeline slider that scrubs through recorded history, a speed dial,
# pause, and a freeze gun. Two modes share the scene:
#
#  - 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()