P23-03: 生活の観察

This commit is contained in:
Agent Control Runner
2026-10-15 09:52:50 +09:00
parent baa4f2d563
commit 55c29f1192
5 changed files with 41 additions and 10 deletions
+28 -5
View File
@@ -12,25 +12,35 @@ class MotionObserver extends Node:
var scaled_elapsed := 0.0
func _ready() -> void:
RenderingServer.frame_post_draw.connect(after_draw)
view.draw.connect(on_view_draw)
func _process(delta: float) -> void:
elapsed += delta
var clock := get_node_or_null("/root/GameClock")
if clock == null or not bool(clock.get("paused")):
scaled_elapsed += delta * maxf(float(clock.get("speed")) if clock != null else 1.0, 0.0)
func after_draw() -> void:
if not is_instance_valid(view): return
var report := view.get_life_observation_report()
if report.is_empty(): return
func on_view_draw() -> void:
# Read the draw-consumed report after the real _draw completes.
after_draw.call_deferred()
func display_sample(report: Dictionary) -> Dictionary:
var sample := {}
for key: String in ["animation_time", "paused", "layer", "zoom", "pan", "viewport", "actor_tracks", "elevator_tracks", "actor_drawings", "cabins"]:
sample[key] = report[key]
return sample
func after_draw() -> void:
if not is_instance_valid(view): return
var frame := Engine.get_process_frames()
if not frames.is_empty() and frame <= int(frames[-1]["frame"]): return
var report := view.get_life_observation_report()
if report.is_empty(): return
var sample := display_sample(report)
if not frames.is_empty() and sample == frames[-1]["display"] and not sample["paused"]: return
# Keep only two draw frames, never whole Runtime copies per frame.
frames.append({"frame": Engine.get_process_frames(), "delta": elapsed, "scaled_delta": scaled_elapsed, "display": sample})
frames.append({"frame": frame, "delta": elapsed, "scaled_delta": scaled_elapsed, "display": sample})
if frames.size() > 2: frames.pop_front()
elapsed = 0.0
scaled_elapsed = 0.0
func _exit_tree() -> void:
if is_instance_valid(view) and view.draw.is_connected(on_view_draw): view.draw.disconnect(on_view_draw)
if RenderingServer.frame_post_draw.is_connected(after_draw): RenderingServer.frame_post_draw.disconnect(after_draw)
func watch(view: WorldPreview) -> void:
@@ -38,6 +48,19 @@ func watch(view: WorldPreview) -> void:
observer.view = view
view.add_child(observer)
func await_draws(tree: SceneTree, view: WorldPreview, n: int = 2, bound: int = 120) -> bool:
var start := Engine.get_process_frames()
for unused: int in range(bound):
view.queue_redraw()
await tree.process_frame
if not is_instance_valid(observer): break
var report := view.get_life_observation_report()
if observer.frames.size() == n and not report.is_empty() and int(observer.frames[0]["frame"]) > start and observer.frames[-1]["display"] == observer.display_sample(report):
return true
# Persist timeout as a failure, never a skip.
record("draw-history-timeout", {"start_frame": start, "bound": bound, "required_frames": n})
return false
func open(path: String) -> bool:
directory = path
if DirAccess.dir_exists_absolute(path):
+2 -4
View File
@@ -86,10 +86,8 @@ func run(tree: SceneTree, directory: String, layout: String) -> bool:
if guest[&"current_cell"] != [GOAL.x, GOAL.y, GOAL.z] or finished.has(guest[&"guest_id"]): continue
finished[guest[&"guest_id"]] = offset
if offset % 30 == 0:
view.queue_redraw()
await tree.process_frame
await tree.process_frame
ok = stream.capture("frame-%03d" % offset, runtime, view) and ok
var drawn: bool = await stream.await_draws(tree, view)
ok = drawn and stream.capture("frame-%03d" % offset, runtime, view) and ok
ok = stream.record("first-arrivals", {&"offsets": finished}) and ok
ok = stream.record("final", runtime.create_snapshot()) and ok
stream.close()
+3
View File
@@ -39,6 +39,7 @@ func advance(minutes: int) -> void:
await frames()
await observe_progress()
if clock.absolute_minute % 30 == 0:
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
expect(stream.capture("minute-%d" % clock.absolute_minute, runtime, main.world_preview), "sparse minute observation")
func observe_progress() -> void:
@@ -118,8 +119,10 @@ func prepare() -> void:
func layer_observations(prefix: String) -> void:
await button(main.front_button)
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
expect(stream.capture(prefix + "-FRONT", runtime, main.world_preview), "FRONT state/report")
await button(main.back_button)
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
expect(stream.capture(prefix + "-BACK", runtime, main.world_preview), "BACK state/report")
func writer() -> void:
+3
View File
@@ -44,10 +44,12 @@ func motion_observations() -> void:
while clock.absolute_minute == minute and not terminated:
await tree.process_frame
await frames()
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
expect(stream.capture("active-motion", runtime, main.world_preview), "active interpolation history")
await button(main.pause_button)
expect(clock.paused, "ordinary pause for stable observation")
await frames()
expect(await stream.await_draws(tree, main.world_preview), "real draw history before capture")
expect(stream.capture("paused-motion", runtime, main.world_preview), "paused interpolation history")
class InputObserver extends Node:
@@ -93,6 +95,7 @@ func request(action: Dictionary) -> void:
DirAccess.make_dir_recursive_absolute(folder)
var window: Window = main.get_window()
var view: WorldPreview = main.world_preview
expect(await stream.await_draws(tree, view), "real draw history before capture")
action["report"] = Codec.new().typed(view.get_life_observation_report())
action["viewport_to_screen"] = affine(view.get_screen_transform() * view.get_global_transform_with_canvas().affine_inverse())
action["local_to_viewport"] = affine(view.get_global_transform_with_canvas())