From e805a91ad37b696fd6d4295999f2dbfd218e23cb Mon Sep 17 00:00:00 2001 From: Gig Line Dev Date: Mon, 2 Nov 2026 16:36:01 +0100 Subject: [PATCH] Add end-to-end interaction flow smoke - Boot the real scene and verify inspection allow/deny, normal vs emergency radio, Help filtering, wand allow/deny and review return-to-menu. - Document the flow coverage and smoke command; mark the 0.9.3 end-to-end audit complete. --- MEMORY.md | 4 ++ TODO.md | 4 +- docs/development-status.md | 1 + docs/getting-started.md | 3 + docs/ui-copy.md | 18 ++++- src/test/smoke_ui_flows.gd | 131 +++++++++++++++++++++++++++++++++++++ 6 files changed, 158 insertions(+), 3 deletions(-) create mode 100644 src/test/smoke_ui_flows.gd diff --git a/MEMORY.md b/MEMORY.md index c7266f7..506b8ff 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -61,6 +61,10 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o gallery is 13 good / 0 bad, the screen-design judge is 13/13 and a clean portrait measured a 0.732 vision gap against the photoreal concept. No release build, version bump or tag was created at this gate. +- **UI flow smoke (2026-11-02):** `smoke_ui_flows.gd` boots the real scene and + exercises inspection allow/deny, normal vs emergency radio, Help filtering, + wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.3/0.9.6 + handoff; it complements the unit tests that only exercise panels. - **Destructive actions (2026-11-02):** `SettingsPanel._open_destructive_dialog()` owns one confirmation instance for reset/replay; repeated presses focus the existing dialog, OK emits once, Cancel is inert. Emergency send remains the diff --git a/TODO.md b/TODO.md index 291279a..84d28e0 100644 --- a/TODO.md +++ b/TODO.md @@ -98,8 +98,10 @@ screen-design judge before any model/texture changes. No gameplay rules change. emergency send) require the intended confirmation/hold and cannot double-fire. (Reset/replay share one guarded confirmation dialog; emergency hold submits once and ignores the post-submit release. Covered by session/radio tests.) -- [ ] Verify radio normal-send vs emergency hold, inspection/wand allow/deny, +- [x] Verify radio normal-send vs emergency hold, inspection/wand allow/deny, review continue/retry/menu and Help search/filter behavior end to end. + (`smoke_ui_flows.gd` covers inspection, radio, Help, wand and review; + `smoke_campaign.gd` covers review continue/checkpoint resume.) ### Visual and judge verification diff --git a/docs/development-status.md b/docs/development-status.md index 3ce1aef..7da5a63 100644 --- a/docs/development-status.md +++ b/docs/development-status.md @@ -51,6 +51,7 @@ Implemented so far: - Joke Shop / Sunset Strip map (map optimization): the second venue is now a stylized reconstruction of The Comedy Store block at 8433 Sunset Blvd, with a 27 m black hall, signature-covered sloping canopy, circular **THE JOKE SHOP** blade sign, red velvet curtain bays, a west-end entrance, cream and white low-rise neighbours, a dry hillside with stucco houses and palms behind the roofline, and a low retail/billboard frontage across Sunset. The same geometry serves day and late lighting through the phase system. Real-site references and limits are in [`joke-shop-map.md`](joke-shop-map.md); the map measures **22,680 triangles across 454 meshes** in the budget test. - Venue geometry (v0.7.0, Joke Shop frontage superseded by the map-optimization pass): the TV Studio builds a stone facade, bulb canopy, glossy black car and barrier line. `WorldBuilder.VENUE_SPECS` drives facade/street/nav dimensions, and `GameManager` rebuilds only the world container when the venue changes. - Gear fit (post-v0.6.0): Blender gear is measured against the avatar head/skirt silhouette and the checks run in both the Python tooling and the headless suite. Sunglasses, the shoulder bag and the firefighter helmet were fitted to the new faces; the Influencer portrait and gallery were recaptured with a before/after report. +- End-to-end interaction pass (0.9.3 work): `smoke_ui_flows.gd` drives inspection allow/deny, normal vs emergency radio, Help filtering, wand allow/deny and review return-to-menu in the real scene; campaign and timeline smokes cover review continue and timeline round-trips. - Destructive-action pass (0.9.3 work): reset campaign and tutorial replay share a guarded confirmation dialog (one open instance, one emission, Cancel is inert); emergency send remains hold-to-confirm with no post-submit double-fire. Covered by session/radio tests and recorded in `docs/ui-copy.md`. - Font/layout pass (0.9.3 work): HUD font scale was exercised at 0.75× and 1.75×; the target chip measures and caps scaled names, top labels clip, and shared action buttons ellipsize long 1.5–2× labels instead of overflowing. Covered by accessibility/button tests and recorded in `docs/ui-copy.md`. - Modal-close pass (0.9.3 work): one top-modal close path handles Esc/right-click for every overlay, Settings opened from the menu restores the menu, Help no longer opens Pause behind it, and pointer/guard freeze state is recaptured after each close. The session smoke covers these paths. diff --git a/docs/getting-started.md b/docs/getting-started.md index 8afb45f..2ff7122 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -35,6 +35,9 @@ godot --path . --script res://src/test/smoke_focus_radial.gd # Session-shell smoke: menu → shift → pause → settings → menu (headless or windowed) godot --headless --path . --script res://src/test/smoke_session_shell.gd +# End-to-end interaction smoke: inspection, radio, Help search, wand and review +godot --headless --path . --script res://src/test/smoke_ui_flows.gd + # PLAN-verb smoke: recognize, request reason, body camera, warning, blocking godot --headless --path . --script res://src/test/smoke_plan_verbs.gd diff --git a/docs/ui-copy.md b/docs/ui-copy.md index fcec1c3..17de612 100644 --- a/docs/ui-copy.md +++ b/docs/ui-copy.md @@ -1,6 +1,6 @@ # UI copy and button inventory -Status: dialogue, button-state, focus, modal-close, font/layout and destructive-action passes complete; end-to-end audit in progress for 0.9.3. +Status: dialogue, button-state, focus, modal-close, font/layout, destructive-action and end-to-end passes complete; capture/smoke sign-off remains for 0.9.3. ## Screen inventory @@ -181,10 +181,24 @@ Regression coverage lives in `test_accessibility.gd` (target-chip width/cap) and translator workflow remains a 0.10.0 task; this pass uses deterministic long strings and the maximum supported HUD scale. +## End-to-end interaction flows + +`smoke_ui_flows.gd` boots the real scene and verifies, in order: + +- inspection opens, then allow and deny both close the panel; +- a normal radio click sends and spends battery; +- an emergency hold sends once, spends battery and starts cooldown; +- Help opens, filters to radio controls and closes; +- the wand opens at The Joke Shop, then allow and deny close it; +- returning from the review restores the venue menu. + +`smoke_campaign.gd` covers review continue plus checkpoint resume, and +`smoke_timeline.gd` covers review → timeline → review. These are end-to-end +scene smokes, not just panel-unit tests. + ## Remaining audit work - Player-facing labels are now routed through `content/strings.en.json`; numeric values, empty-state clears and debug-only text remain in code by design. - Focus/hover/pressed/disabled states and keyboard/gamepad rings are recorded above. -- Long strings and 0.75×/1.75× HUD scale are covered above. - Capture every dialog/button state and run `screen_designs.py compare` plus the pinned judge. diff --git a/src/test/smoke_ui_flows.gd b/src/test/smoke_ui_flows.gd new file mode 100644 index 0000000..3a935d3 --- /dev/null +++ b/src/test/smoke_ui_flows.gd @@ -0,0 +1,131 @@ +extends SceneTree +## End-to-end interaction flows: inspection allow/deny, normal radio vs +## emergency hold, Help filtering, wand allow/deny and review return-to-menu. +## +## Usage: +## godot --headless --path . --script res://src/test/smoke_ui_flows.gd +## Exit code 0 = pass, 1 = failure. + +const MAX_FRAMES := 1800 + +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 + var npc: Node = gm.npcs[0] + gm.call("_on_guard_target", npc) + gm.call("_toggle_inspection") + _check(gm.inspection_open and gm.hud.inspect_panel.visible, "inspection opens") + gm.decide_access(true) + _check(not gm.inspection_open and not gm.hud.inspect_panel.visible, + "inspection allow closes the panel") + gm.call("_toggle_inspection") + gm.decide_access(false) + _check(not gm.inspection_open, "inspection deny closes the panel") + + var battery_before: float = gm.coordinator.radio.battery + gm.call("_open_radio", false) + _check(gm.radio_panel.visible, "normal radio form opens") + gm.radio_panel.action_button.pressed.emit() + _check(not gm.radio_panel.visible and gm.coordinator.radio.battery < battery_before, + "normal radio sends and spends battery") + battery_before = gm.coordinator.radio.battery + gm.call("_open_radio", true) + _check(gm.radio_panel.visible, "emergency radio form opens") + gm.radio_panel.action_button.button_down.emit() + gm.radio_panel._process(1.7) + _check(not gm.radio_panel.visible + and gm.coordinator.radio.battery < battery_before + and gm.coordinator.radio.emergency_cooldown_remaining > 0.0, + "emergency hold sends once and starts cooldown") + + gm.call("_open_help") + _check(gm.help_overlay.visible, "Help opens") + gm.help_overlay._refresh("radio") + var help_text: String = gm.help_overlay._body.text + _check(help_text.to_lower().contains("radio") + and not help_text.contains("Movement:"), + "Help search filters to radio controls") + gm.help_overlay.close_panel() + _check(not gm.help_overlay.visible, "Help closes") + step = 1 + frame = 0 + 1: + gm.switch_scenario("joke_shop") + step = 2 + frame = 0 + 2: + if frame < 200: + return false + var wand_target: Node = null + for node in gm.npcs: + var c: Object = node.get_npc_controller() if node.has_method("get_npc_controller") else null + if c != null and not c.refuses_screening: + wand_target = node + break + if wand_target == null: + return false + gm.call("_on_guard_target", wand_target) + gm.call("_toggle_wand_screening") + _check(gm.wand_open and gm.hud.wand_panel.visible, "wand opens for a guest") + gm.decide_wand(true) + _check(not gm.wand_open and not gm.hud.wand_panel.visible, + "wand allow closes the panel") + gm._wand_cooldown = 0.0 + gm.call("_toggle_wand_screening") + gm.decide_wand(false) + _check(not gm.wand_open and not gm.hud.wand_panel.visible, + "wand deny closes the panel") + var review: Dictionary = gm.coordinator.build_review() + gm.shift_review_panel.show_review(review, gm.selected_scenario, [], + {"complete": false, "next_venue": "tv_studio", "shift_number": 2}) + _check(gm.shift_review_panel.visible, "review opens") + gm.call("_on_review_restart") + _check(not gm.shift_review_panel.visible and gm.start_menu.visible, + "review return-to-menu restores the venue menu") + _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: inspection, radio, Help, wand and review flows") + quit(0) + else: + for message in failures: + printerr("SMOKE FAIL: " + message) + quit(1)