From fac9b002c36735a50e7b3847762dac4e5e020659 Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sun, 1 Nov 2026 21:37:39 +0100 Subject: [PATCH] Fix normal radio report submission The radio button was connected only to the emergency hold handlers, so a normal Send request never reached _submit. Connect pressed for non-emergency calls while keeping hold-to-confirm exclusive to emergencies, and add tests for click, short-hold rejection and full emergency hold. Headless 249/249, Python 94/94. --- CHANGELOG.md | 7 ++++++ MEMORY.md | 1 + PLAN.md | 2 +- README.md | 6 ++--- docs/architecture.md | 2 +- docs/development-status.md | 4 +-- project.godot | 2 +- src/game/ui/radio_panel.gd | 7 ++++++ src/test/tests/test_radio_panel.gd | 39 ++++++++++++++++++++++++++++++ 9 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 src/test/tests/test_radio_panel.gd diff --git a/CHANGELOG.md b/CHANGELOG.md index eed65b2..b260ad4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable user-facing changes to Gig Line. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions use Semantic Versioning. +## [0.8.1] — 2026-11-01 + +### Fixed + +- Normal radio reports now send on click. The button was wired only to the + emergency hold-to-confirm handlers, so a non-emergency press did nothing. + ## [0.8.0] — 2026-11-01 ### Added diff --git a/MEMORY.md b/MEMORY.md index df8e773..fe98649 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -43,6 +43,7 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o - RNG: NPC reactions and event scheduling are seedable for deterministic tests. - **Mouse look (v0.7.3):** guard look is handled in `GuardController._input()` (before GUI), not `_unhandled_input`; focus/click-back recaptures the cursor and the HUD is made mouse-transparent recursively. Regression tests cover yaw/pitch and HUD filters. +- **Radio submit fix (v0.8.1):** `RadioPanel.action_button` now connects `pressed` for normal reports; `button_down/up` remains exclusive to emergency hold-to-confirm. Regression test covers both modes. - **Campaign/v0.8.0:** `Campaign` owns Hollywood → Joke Shop → TV Studio progression. `campaign.json`, `shift_results.json`, `settings.json` and versioned `shift_state.json` persist separately; pause saves a checkpoint, review Continue advances, Settings resets with confirmation. Difficulty keys are `easy/normal/hard` (`ai_difficulty`); scripted QA bypasses tutorial unless `--tutorial` is passed. Help is searchable and generated from `InputBindings.REMAPPABLE`. - **Tutorial (0.8.0 work):** `docs/tutorial.md` is the eight-beat design; `src/game/tutorial.gd` is the pure state machine, `TutorialOverlay` is the non-blocking card, and `GameManager` wires it with a safe visitor, suppressed spawns, persisted completion/skip and Settings replay. Scripted test/capture runs bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers completion/replay/skip. - **Documentation ownership (2026-11-01):** `docs/architecture.md` §12 defines the source-of-truth map and consolidation rules. Obsolete/historical plan documents are removed rather than kept; current work belongs in `TODO.md`. README/development-status/Blender AO/Forward+ stale claims were corrected. The v1.0.0 gap register and 0.8/0.9/0.9.5/1.0 roadmap live in `TODO.md`. diff --git a/PLAN.md b/PLAN.md index dd13ef2..5c61e69 100644 --- a/PLAN.md +++ b/PLAN.md @@ -172,7 +172,7 @@ Primary decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md), [AD ## Versioning and Delivery -`config/version` in `project.godot` is the single version source (currently `0.8.0`). Releases are tagged `v`; the CI pipeline builds and publishes the standard artifacts plus the optional Desktop HQ zip. Version bumps follow [Semantic Versioning](https://semver.org/). See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands. +`config/version` in `project.godot` is the single version source (currently `0.8.1`). Releases are tagged `v`; the CI pipeline builds and publishes the standard artifacts plus the optional Desktop HQ zip. Version bumps follow [Semantic Versioning](https://semver.org/). See [`docs/RELEASING.md`](docs/RELEASING.md) and [`README.md`](README.md) for commands. ## Design Direction diff --git a/README.md b/README.md index 3de59e8..d563d1f 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,9 @@ precision walk, the Joke Shop alley, the TV Studio arrival, and the prop lineup. > Downloads are public (no sign-in required) since the host's instance-wide > `REQUIRE_SIGNIN_VIEW` was disabled on 2026-10-31. > -> **Latest: v0.8.0** — first-shift tutorial, campaign/checkpoint resume, -> difficulty and a searchable Help overlay, plus the v0.7.x judge, Desktop HQ, -> venue and gear work. +> **Latest: v0.8.1** — normal radio reports now send correctly, on top of the +> v0.8.0 tutorial/campaign/resume pass and the v0.7.x judge, Desktop HQ, venue +> and gear work. > Releases are built automatically when a `vX.Y.Z` tag is pushed > (see [`docs/RELEASING.md`](docs/RELEASING.md)); manual builds use > `./tools/build_releases.sh`. Installer/AppImage packaging runs in the same pipeline diff --git a/docs/architecture.md b/docs/architecture.md index 7d7fd12..b0e12e5 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -37,7 +37,7 @@ This document follows the structure of the arc42 architecture documentation temp - **MINOR** — new venues, NPC archetypes, systems, or features that are backward compatible. - **PATCH** — bug fixes and small polish that do not change behavior. -The single source of truth for the version is `config/version` in `project.godot` (currently `0.8.0`). Each release is tagged in git as `v` (e.g. `v0.6.0`). The `0.x` series is pre-1.0, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md). +The single source of truth for the version is `config/version` in `project.godot` (currently `0.8.1`). Each release is tagged in git as `v` (e.g. `v0.6.0`). The `0.x` series is pre-1.0, so MINOR bumps may introduce breaking changes while the prototype is still stabilizing. `tools/build_releases.sh` and `tools/publish_release.sh` read the version from `project.godot`, so bumping it there is the only required step before a release. The full bump checklist lives in [`RELEASING.md`](RELEASING.md). ## 3. Context and Scope diff --git a/docs/development-status.md b/docs/development-status.md index f30db17..748b163 100644 --- a/docs/development-status.md +++ b/docs/development-status.md @@ -5,7 +5,7 @@ Implemented so far: - Godot 4.3 project scaffold, input map, procedural world builder - Venue-driven simulation core: `ShiftCoordinator`, `VenueScenario`, `EventScheduler`, `NPCController` + FSM, `AccessVerifier`, `EscalationManager`, `RadioSystem`, `IncidentRecorder`, `ReputationEvaluator`, `CrowdSystem` - Three authored scenarios (Hollywood Boulevard building, The Joke Shop, Late Night TV Studio) with access policies, NPC archetypes, and event pools -- Headless unit tests: **247 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings) +- Headless unit tests: **249 passing** (access rules and venue-correct access loadout, escalation, radio cooldown/battery, NPC reaction determinism, door lock, flashlight perception, persistence round-trip, settings round-trip, input remaps, navmesh, step colliders, skyline alpha, scenario data, full-shift simulation, wand screening, responder dispatch, localization strings, focus cone, action wheel, session shell, PLAN verbs and incident recording, crowd presentation, emergency aftermath, body-camera timeline, NPC theater poses, audio, accessibility, authored scenario content, string formatting, `.tres` venue round-trip, surface materials and environment, focus ranking and outfit/gait helpers, generated prop models, web export preset, prop atlas material, player movement, NPC character layers/LOD/queue, input bindings) - Simulation extras: protester archetype, CALL_SOMEONE reaction, door lock/secure mechanic (visual indicator), flashlight with night perception bonus, LATE shift phase reaching the violent-threat event - First-person prototype with interaction HUD, access inspection, wand screening, radio report form (incl. emergency hold-to-confirm), dispatched responder avatars, aim-free targeting, venue cycling between all three scenarios, color-coded escalation feedback, procedural audio cues, navigation-mesh NPC paths, JSON-backed localized strings, persisted shift history, debug overlay, and shift review - Control pass: focus cone on NPC heads with a billboarded blue circle in front of the face and name, action wheel (LMB/Space) with de-escalation-first slices, mouse-look freeze while a panel is open, acceleration-based walking, and named input actions (`action_menu`, `confirm`, `cancel`, `inspect`, `wand`) @@ -40,7 +40,7 @@ Implemented so far: - Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. - Realism adoption (post-v0.6.0): v2 per-phase skylines and surface tiles with normal maps, retuned sun/exposure/fog, wet asphalt, denser foliage, a visible additive flashlight beam, and refreshed staging captures. `tools/check_screenshots.sh` is the repeatable capture/compare gate; judge is clean of `diff` verdicts. -- Input/reliability fixes: selecting a venue releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. Mouse look now reads at `_input`, restores capture on window focus/click-back, and the HUD is mouse-transparent. +- Input/reliability fixes: selecting a venue releases the start-menu input lock and recaptures the mouse; the body-camera overlay no longer covers the shift status. Mouse look now reads at `_input`, restores capture on window focus/click-back, and the HUD is mouse-transparent. Normal radio reports submit on click; emergency calls still require the hold-to-confirm path. - Campaign and first-hour pass (v0.8.0): campaign progression, checkpoint resume, difficulty presets, a searchable Help overlay, recovery events, contextual dialogue, policy/debrief lines and tutorial/replay/skip. Scripted QA bypasses the tutorial unless `--tutorial` is passed. - Tutorial pass (0.8.0 work): the eight-beat first shift is wired through a non-blocking prompt overlay, a safe compliant visitor, real wheel/inspection/warning/radio panels, persisted completion/skip and a Settings replay action. Scripted tests/captures bypass it unless `--tutorial` is passed; `smoke_tutorial.gd` covers the full flow. - Vision judge (v0.7.2): discovers the cheapest current OpenRouter image+JSON model, reports the model/fallbacks used, and clears the full screen-design judge (13/13 match, 0 diff). diff --git a/project.godot b/project.godot index 4f287a6..5049947 100644 --- a/project.godot +++ b/project.godot @@ -12,7 +12,7 @@ config_version=5 config/name="Gig Line" config/description="Workplace security simulation about observation, access verification, and proportionate escalation." -config/version="0.8.0" +config/version="0.8.1" run/main_scene="res://scenes/main.tscn" config/features=PackedStringArray("4.3", "GL Compatibility") diff --git a/src/game/ui/radio_panel.gd b/src/game/ui/radio_panel.gd index c2330b0..faa0aaa 100644 --- a/src/game/ui/radio_panel.gd +++ b/src/game/ui/radio_panel.gd @@ -105,6 +105,7 @@ func _init() -> void: action_button.text = "Send request" action_button.button_down.connect(func(): _start_hold()) action_button.button_up.connect(func(): _stop_hold(false)) + action_button.pressed.connect(_on_action_pressed) buttons.add_child(action_button) var cancel_btn := Button.new() cancel_btn.text = "Cancel" @@ -144,6 +145,12 @@ func open(controller: Object, emergency: bool, truth: Dictionary) -> void: _hold_time = 0.0 visible = true +## Non-emergency requests are a normal click. Emergency calls require the +## press-and-hold path, so the click release must not submit early. +func _on_action_pressed() -> void: + if not _emergency: + _submit() + func _start_hold() -> void: _holding = _emergency _hold_time = 0.0 diff --git a/src/test/tests/test_radio_panel.gd b/src/test/tests/test_radio_panel.gd new file mode 100644 index 0000000..2e205ec --- /dev/null +++ b/src/test/tests/test_radio_panel.gd @@ -0,0 +1,39 @@ +extends RefCounted +## Radio form submission: normal calls send on click; emergency calls require +## the documented hold and do not submit on a quick release. + +const RadioPanel := preload("res://src/game/ui/radio_panel.gd") + +func test_normal_radio_sends_on_click() -> bool: + var panel: RadioPanel = RadioPanel.new() + var got := {} + panel.submitted.connect(func(report, emergency): + got["report"] = report + got["emergency"] = emergency) + panel.open(null, false, {}) + panel.action_button.pressed.emit() + var a := TestUtils.check(got.has("report"), "normal click emits a report") + var b := TestUtils.check(not bool(got.get("emergency", true)), "normal report is not emergency") + var c := TestUtils.check(not panel.visible, "panel closes after sending") + var d := TestUtils.check_eq(str(got.get("report", {}).get("location", "")), "front entrance", + "default location is included") + panel.free() + return a and b and c and d + +func test_emergency_requires_hold() -> bool: + var panel: RadioPanel = RadioPanel.new() + var calls := {"count": 0} + panel.submitted.connect(func(_report, _emergency): + calls["count"] = int(calls["count"]) + 1) + panel.open(null, true, {}) + panel.action_button.pressed.emit() + var a := TestUtils.check_eq(int(calls["count"]), 0, "quick emergency click does not send") + panel.action_button.button_down.emit() + panel._process(0.2) + panel.action_button.button_up.emit() + var b := TestUtils.check_eq(int(calls["count"]), 0, "short hold does not send") + panel.action_button.button_down.emit() + panel._process(RadioPanel.HOLD_NEEDED + 0.1) + var c := TestUtils.check_eq(int(calls["count"]), 1, "full hold sends exactly once") + panel.free() + return a and b and c