- New src/game/ui/event_timeline.gd flattens every recorded incident event (ADR-0005) into one time-ordered list and shows time, person, and a one-line summary. Step with Left/Right or the buttons; Esc returns to the review. No live 3D replay in this first version. - Shift review gains a "Body camera timeline" button that hands the review to the timeline; the review dim is hidden while it is open (stacked overlays made the screen nearly black) and restored on close. - ShiftCoordinator records started_at_ms so the review can stamp events as mm:ss. - 8 new string keys for the timeline UI and the review button. Tests/docs - Headless suite 113 -> 120 (test_event_timeline.gd), new smoke_timeline.gd that opens the timeline from the review, steps through every event, and closes back. - README, TODO section moved to DONE, MEMORY notes. Version 0.2.6.
84 lines
2.5 KiB
GDScript
84 lines
2.5 KiB
GDScript
extends SceneTree
|
|
## Body-camera timeline smoke: records a few real interactions, opens the review's
|
|
## timeline, steps through every event, and closes back to the review.
|
|
##
|
|
## Usage:
|
|
## godot --headless --path . --script res://src/test/smoke_timeline.gd
|
|
|
|
const MAX_FRAMES := 900
|
|
|
|
var frame := 0
|
|
var gm: Node = null
|
|
var started := false
|
|
var step := 0
|
|
var failures: Array[String] = []
|
|
|
|
func _initialize() -> void:
|
|
root.size = Vector2i(1280, 720)
|
|
root.add_child(load("res://scenes/main.tscn").instantiate())
|
|
|
|
func _process(_delta: float) -> bool:
|
|
frame += 1
|
|
if gm == null:
|
|
gm = root.find_child("GameManager", true, false)
|
|
return false
|
|
if not started:
|
|
gm.call("_on_venue_selected", "hollywood_building")
|
|
started = true
|
|
return false
|
|
if frame > MAX_FRAMES:
|
|
_fail("timeout")
|
|
return true
|
|
if frame < 200:
|
|
return false
|
|
match step:
|
|
0:
|
|
if gm.npcs.is_empty():
|
|
return false
|
|
gm.call("_on_guard_target", gm.npcs[0])
|
|
gm.call("_apply_interaction", 1, "eye contact")
|
|
gm.call("_request_reason")
|
|
gm.call("_apply_interaction", 2, "formal warning")
|
|
var review: Dictionary = gm.coordinator.build_review()
|
|
# Real flow: the review screen shows first, then its timeline button.
|
|
gm.shift_review_panel.show_review(review, gm.selected_scenario, [])
|
|
var button: Button = gm.shift_review_panel.find_child("TimelineButton", true, false) as Button
|
|
_check(button != null, "review offers a body-camera timeline button")
|
|
if button:
|
|
button.pressed.emit()
|
|
var count: int = gm.event_timeline.events.size()
|
|
_check(count >= 3, "timeline collected the interaction events (%d)" % count)
|
|
_check(gm.event_timeline.visible, "timeline is showing")
|
|
_check(not gm.shift_review_panel.visible, "review is hidden behind the timeline")
|
|
_check(gm.guard.input_locked, "the guard is frozen while the timeline is open")
|
|
for i in count + 2:
|
|
gm.event_timeline.step(1)
|
|
_check(gm.event_timeline.index == count - 1, "stepping clamps at the last event")
|
|
gm.event_timeline.close()
|
|
step = 1
|
|
1:
|
|
_check(not gm.event_timeline.visible, "timeline closes")
|
|
_check(gm.shift_review_panel.visible, "closing returns to the review")
|
|
_finish()
|
|
return true
|
|
return false
|
|
|
|
func _check(condition: bool, message: String) -> void:
|
|
if condition:
|
|
print("ok: " + message)
|
|
else:
|
|
_fail(message)
|
|
|
|
func _fail(message: String) -> void:
|
|
failures.append(message)
|
|
_finish()
|
|
|
|
func _finish() -> void:
|
|
if failures.is_empty():
|
|
print("SMOKE OK: body-camera timeline")
|
|
quit(0)
|
|
else:
|
|
for message in failures:
|
|
printerr("SMOKE FAIL: " + message)
|
|
quit(1)
|