From 2b631ae6d978013117cc7e712a43dc34ec6ca18c Mon Sep 17 00:00:00 2001 From: Zwitschi Date: Sat, 31 Oct 2026 15:04:12 +0100 Subject: [PATCH] =?UTF-8?q?Add=20ADRs=200007=E2=80=930010=20for=20locomoti?= =?UTF-8?q?on,=20focus=20ring,=20radial=20menu,=20NPC=20presentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 0007: input layers so WASD/look never fire person-verbs; freeze while modal - 0008: cone focus + billboarded head ring; no type names in the world - 0009: doughnut action menu (LMB/Space open, click or A/D then Space, Esc/RMB cancel); emergency stays on R - 0010: keep procedural rigs, add face/hair/neck; no Mixamo this pass Linked from architecture.md, PLAN.md, TODO.md, MEMORY.md. --- MEMORY.md | 7 +-- PLAN.md | 2 + TODO.md | 4 +- docs/adr/0007-locomotion-and-input-layers.md | 42 ++++++++++++++ docs/adr/0008-cone-focus-and-head-ring.md | 46 +++++++++++++++ docs/adr/0009-radial-action-menu.md | 60 ++++++++++++++++++++ docs/adr/0010-procedural-npc-presentation.md | 53 +++++++++++++++++ docs/architecture.md | 4 ++ 8 files changed, 212 insertions(+), 6 deletions(-) create mode 100644 docs/adr/0007-locomotion-and-input-layers.md create mode 100644 docs/adr/0008-cone-focus-and-head-ring.md create mode 100644 docs/adr/0009-radial-action-menu.md create mode 100644 docs/adr/0010-procedural-npc-presentation.md diff --git a/MEMORY.md b/MEMORY.md index 1f5023e..3171f8a 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -15,10 +15,9 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o ## Current State -- Next playable pass (planned, not coded): first-person stay, but focus becomes a camera cone + - head ring, and NPC verbs move onto a round pie menu (LMB/`Space` open, click or A/D/arrows then - `Space` confirm, `Esc`/RMB cancel). Emergency stays on `R`. Wand leaves `W` (conflicts with walk). - Host sign-in follow-up is deferred. Details in PLAN.md "Player Control, Focus, and Action Menu". +- Next playable pass (planned, not coded): ADR-0007 locomotion/input layers, ADR-0008 cone focus + + head ring, ADR-0009 radial action menu, ADR-0010 procedural NPC presentation. Emergency stays on + `R`. Wand leaves `W`. Host sign-in follow-up is deferred. PLAN.md has the full pass notes. - **Release v0.2.1 is LIVE** (2026-10-31): CI run 1110 built the NSIS installer + AppImage and published — installers/AppImage are on the releases page. Releasing = bump version → tag → push. - NSIS gotchas (validated in a local ubuntu:24.04 docker repro): makensis uses `-D` defines diff --git a/PLAN.md b/PLAN.md index eca1eec..05f2cf7 100644 --- a/PLAN.md +++ b/PLAN.md @@ -456,6 +456,8 @@ Desktop is the first target: Windows, Linux, and macOS. Web export is a later op The prototype already walks and looks in first person, but the control scheme fights itself and targeting is a hairline. This pass replaces that with three things that belong together: solid locomotion, a visible focus on a person, and a round action menu for what to do with them. +Decisions: [ADR-0007](docs/adr/0007-locomotion-and-input-layers.md) locomotion and input layers, [ADR-0008](docs/adr/0008-cone-focus-and-head-ring.md) cone focus and head ring, [ADR-0009](docs/adr/0009-radial-action-menu.md) radial action menu, [ADR-0010](docs/adr/0010-procedural-npc-presentation.md) procedural NPC presentation. + ### What is wrong today - `W` is walk-forward in the input map and wand-screening in `GameManager._handle_key`. Walking at someone with a wand venue fires the wand. diff --git a/TODO.md b/TODO.md index ab76c4b..1cc21bc 100644 --- a/TODO.md +++ b/TODO.md @@ -2,8 +2,8 @@ ## Player control, focus ring, and radial action menu -Design is in `PLAN.md` → "Player Control, Focus, and Action Menu (next pass)". Distribution -follow-up (host `REQUIRE_SIGNIN_VIEW`) is deferred. +Design is in `PLAN.md` → "Player Control, Focus, and Action Menu (next pass)" and ADRs 0007–0010. +Distribution follow-up (host `REQUIRE_SIGNIN_VIEW`) is deferred. ### Locomotion diff --git a/docs/adr/0007-locomotion-and-input-layers.md b/docs/adr/0007-locomotion-and-input-layers.md new file mode 100644 index 0000000..458278c --- /dev/null +++ b/docs/adr/0007-locomotion-and-input-layers.md @@ -0,0 +1,42 @@ +# ADR-0007: Locomotion and input layers + +- **Status:** Accepted +- **Date:** TBD + +## Context + +The prototype is first-person: WASD walks, mouse looks, `Esc` toggles capture. Action keys were then bound on the same letters. `W` is walk-forward in the input map and wand screening in `GameManager._handle_key`. `A` / `D` strafe and also allow or deny during inspection. `Q` is polite direction and is also bound as `cancel`. + +A player who walks toward a guest on a wand venue therefore fires the wand. A player who strafes during an inspection can admit or refuse by accident. The mouse cannot click UI while it is captured, and `Esc` cannot mean both "release the mouse" and "I changed my mind." + +## Decision + +Treat input as layers, only one of which owns a key at a time. + +1. **Locomotion** (default): WASD walk relative to body yaw, mouse look on the captured cursor, pitch clamped. Short acceleration and deceleration. Gravity only when airborne; snap to the floor so the steps work. Walk speed stays around 3.2 m/s. No sprint or crouch in this pass. +2. **Modal UI** (radial, inspect, wand, radio, shift review): freeze walk and look. Recapture the mouse when the modal closes. `A` / `D` and arrows belong to the modal while it is open. +3. **Equipment and global keys** stay off the person-verb set: `G` flashlight, `L` door lock, `Tab` cycle nearby people, `R` emergency radio, `F` debug, `V` venue (debug). Wand leaves `W`; the optional shortcut is `C`. + +`Esc` closes the top modal first and does nothing else. With no modal open it keeps the capture/release toggle. + +## Consequences + +### Positive + +- Walking cannot fire wand, inspect, or admission. +- The mouse can click the action menu because capture is released while it is open. +- Cancel has one meaning when a menu is up. +- Locomotion stays first-person, which matches the body-camera framing. + +### Negative + +- Freeze-while-modal means the player cannot sidestep during an inspect. That is accepted for this pass. +- Power users lose `W` as wand. The wheel and `C` replace it. +- The input map and `_unhandled_input` must agree, or the conflict returns. + +## Alternatives considered + +- **Keep letter-key verbs on WASD:** Rejected because walk and wand already collide in the prototype. +- **Third-person or click-to-move:** Rejected; the body-camera overlay and the "you are the guard" framing depend on first person. +- **Hold-right-mouse-to-look, free cursor otherwise:** Rejected for this pass. Constant look is the default security-sim stance; the menu is the exception. +- **Never freeze walk:** Rejected because `A` / `D` cannot both strafe and step a menu highlight. diff --git a/docs/adr/0008-cone-focus-and-head-ring.md b/docs/adr/0008-cone-focus-and-head-ring.md new file mode 100644 index 0000000..ce6774c --- /dev/null +++ b/docs/adr/0008-cone-focus-and-head-ring.md @@ -0,0 +1,46 @@ +# ADR-0008: Cone focus and head ring + +- **Status:** Accepted +- **Date:** TBD + +## Context + +The prototype picks a person with an 8 m physics ray from the camera. The collider is a capsule. A few pixels off the body, or a bob during a walk, clears the target. The HUD then prints a name and hint, but nothing in the world shows who is selected. `Tab` already cycles people within 12 m as an aim-free path. + +The next pass needs the player to look at a person, know they have them, and only then open an action menu. A hairline ray is too brittle for that, and a HUD line is easy to miss in a crowd. + +## Decision + +Focus is a **cone**, not a ray. + +- People within about 10 m whose **head** projects inside a screen-space circle around the crosshair (about 90 px / 10 degrees) are candidates. +- If several heads sit in the circle, pick the one closest to the center; if tied, the nearer one. +- The person must be in front of the camera. A short ray to the head skips people fully hidden by the building. +- A few frames of grace keep walk-bob from dropping focus. +- `Tab` still cycles people inside 12 m and sets the same focused person. + +When someone is in focus, draw a **thin ring** around their head, attached to the avatar `Head` node, billboarded so it stays a circle on screen. Pale gold while idle; brighter and slightly larger while the action menu is open on that person. Optional `display_name` under the ring. One ring at a time. + +The world must not show the internal type name (`Trespasser`, `Violent Threat`). The debug overlay (`F`) can still print it. + +## Consequences + +### Positive + +- Looking near a person is enough; the player does not have to pin a capsule. +- The ring is visible in the scene, so the HUD is not the only focus cue. +- `Tab` remains the accessibility path and shares the same focused person. +- The action menu (ADR-0009) has a clear subject. + +### Negative + +- Two heads in the same circle can steal focus; center-distance is a heuristic, not a lock-on. +- Hats and protester signs can z-fight a 3D torus. A screen-space circle unprojected from the head is an allowed fallback. +- Occlusion rays can fail on unusual geometry; grace frames hide some of that. + +## Alternatives considered + +- **Keep the hairline ray:** Rejected because it drops people too easily and has no world mark. +- **Sticky lock-on until cancelled:** Rejected because the job is to watch a crowd, not duel one person. +- **Screen reticle only, no world ring:** Rejected; in a crowd the player needs a mark on the head, not only a HUD line. +- **Click a person to select, then act:** Rejected for default play. The mouse is captured for look (ADR-0007). Click opens the action menu on whoever is already in the cone. diff --git a/docs/adr/0009-radial-action-menu.md b/docs/adr/0009-radial-action-menu.md new file mode 100644 index 0000000..4c09bf8 --- /dev/null +++ b/docs/adr/0009-radial-action-menu.md @@ -0,0 +1,60 @@ +# ADR-0009: Radial action menu + +- **Status:** Accepted +- **Date:** TBD + +## Context + +NPC verbs in the prototype live on `Q`, `E`, `W`, `1`–`6`, and `R`. That is a cheat sheet. It also collides with walk keys (ADR-0007). The design wants the player to look at a person (ADR-0008) and then choose the lowest useful action, not memorize a number row. + +Inspection, wand screening, and radio already have their own panels. Emergency radio already has a hold-to-confirm on `R` because a misclick is costly. Those panels should stay. What is missing is a single, visible way to pick the verb. + +## Decision + +When someone is in focus, **left mouse button** or **Space** opens a round doughnut menu in the center of the screen. The click that opens it does not pick a slice. If nobody is in focus, do not open; keep the existing "aim first" log line. + +Slices, clockwise from the top, lowest escalation first: + +1. Raise hand (stop / wait) +2. Point away +3. Head shake +4. Polite direction +5. Inspect access +6. Wand (grey if the venue has no wand) +7. Formal warning +8. Block +9. Radio assist + +Emergency is **not** on the wheel. It stays on `R` with the existing hold-to-confirm panel. + +While the menu is open, walk and look freeze (ADR-0007) and the cursor is visible. Highlight follows the mouse angle over the ring, or steps with `A` / `D` or the left / right arrows, wrapping around. The center shows the focused person's `display_name`. + +**Confirm:** click a slice, or `Space` on the highlighted slice. The menu closes, the mouse is recaptured, and that action runs. Inspect, wand, and radio assist close the wheel and then open the existing panels. Walk and look stay frozen until that panel closes. + +**Cancel:** `Esc` or right mouse button. The menu hides. No action runs. No recorder event. Focus and the head ring stay. The mouse is recaptured. + +Number keys `1`–`6` may remain as shortcuts when the wheel is closed, so existing smoke tests still work. They must not fire while the wheel is open. + +## Consequences + +### Positive + +- The player sees the verbs in de-escalation order instead of recalling a number row. +- Mouse and keyboard both work (click, or arrows / `A`/`D` then Space). +- Cancel is explicit and leaves no incident noise. +- Emergency cannot be picked by a bad slice click. +- Inspect, wand, and radio keep the panels already tested. + +### Negative + +- Nine slices is dense on a 1280×720 view. Labels must stay short. +- Freeze-until-panel-closes stops the player sidestepping during an ID check. +- Keeping `1`–`6` as hidden shortcuts can confuse if the README leads with them. Document the wheel first. + +## Alternatives considered + +- **Hold-to-open, release-to-confirm (GTA / Destiny):** Rejected. The requested input is click or Space to open, then click or Space to confirm, with Esc / RMB to abort. That is a toggle, not a hold. +- **Vertical list / command menu:** Rejected because a round layout around a focused person matches the head ring and reads faster in a crowd. +- **One context-sensitive Use key:** Rejected; the game is about choosing among levels, not a single default verb. +- **Emergency on the wheel:** Rejected because a misclick would dispatch armed response. ADR-0002 already keeps that path rare and confirmed. +- **Drop number-key shortcuts immediately:** Deferred. They remain as a closed-menu fallback until the wheel is proven in play. diff --git a/docs/adr/0010-procedural-npc-presentation.md b/docs/adr/0010-procedural-npc-presentation.md new file mode 100644 index 0000000..6c2a99a --- /dev/null +++ b/docs/adr/0010-procedural-npc-presentation.md @@ -0,0 +1,53 @@ +# ADR-0010: Procedural NPC presentation + +- **Status:** Accepted +- **Date:** TBD + +## Context + +Avatars are `CharacterBody3D` capsules color-coded by `NpcType`, with a few props (camera, box, hat, sign, bag), height and brightness variation from a seed, a walk bob, and a head turn toward the guard when noticed. They work as markers. At conversation distance they do not read as people: no neck, no face direction, no shoes, no hair. + +The player is supposed to judge from clothes, props, and behavior, not from a label. The HUD currently prints the internal type (`Violent Threat`, `Trespasser`) next to the name. Putting that name on the body would make the judgment for them. + +A full Mixamo / GLB pipeline would also add animation imports, licenses, and a skeleton the rest of the sim does not need yet. AI textures in `assets/textures/ai/` are not sized or wired for clothing. + +## Decision + +Keep the procedural capsule rig for this pass. Do not add skinned meshes or Mixamo. + +Make the body read as a person at conversation distance: + +- Neck between torso and head. +- Shoes. +- Hair cap whose color comes from the visual seed. +- Two dark eye spheres so the head has a front. +- Named `Head` node (`Rig/Head`) as the attach point for the focus ring (ADR-0008) and the focus cone. +- Collision capsule scaled to the visible height so the cone hits the body the player sees. + +Keep archetype props, height / brightness variation, walk bob, and the notice head-turn. + +Do **not** put the internal type name on the body or as a world nametag. A focused `display_name` under the ring is allowed. The debug overlay (`F`) may still print type, intent, and FSM state. The HUD target line can keep the type during the prototype; removing it from the HUD is a later readability pass, not this one. + +AI clothing textures stay out unless a map is already the right size and is wired on purpose. + +## Consequences + +### Positive + +- People have a face direction the ring and the "noticed the guard" cue can use. +- No new asset toolchain, licenses, or animation retargeting. +- Head is a stable node other systems can find. +- The player is not told `Violent Threat` by a floating label. + +### Negative + +- Capsule people still look like a prototype at cinematic camera distance. +- Hair and eyes are spheres; they will not survive a close-up trailer. +- A later skinned-mesh pass will replace this rig and must keep the `Head` contract. + +## Alternatives considered + +- **Skinned Mixamo / GLB characters now:** Rejected. Cost and pipeline exceed this pass; the sim only needs a readable body and a head node. +- **Billboard sprites:** Rejected; first-person movement walks around people, and sprites fail at profile views. +- **Keep colored capsules as-is:** Rejected; the head ring and cone need a real head, and capsules do not read as people at two metres. +- **World labels with archetype names:** Rejected because they leak sim truth and skip observation. diff --git a/docs/architecture.md b/docs/architecture.md index 006bc7d..a9d255c 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -152,6 +152,10 @@ Architecture decisions are recorded as ADRs: - [ADR-0004: Local single-player first](adr/0004-local-single-player-first.md) - [ADR-0005: Event-based incident recording](adr/0005-event-based-incident-recording.md) - [ADR-0006: Godot 4 with GDScript](adr/0006-godot-4-gdscript.md) +- [ADR-0007: Locomotion and input layers](adr/0007-locomotion-and-input-layers.md) +- [ADR-0008: Cone focus and head ring](adr/0008-cone-focus-and-head-ring.md) +- [ADR-0009: Radial action menu](adr/0009-radial-action-menu.md) +- [ADR-0010: Procedural NPC presentation](adr/0010-procedural-npc-presentation.md) ## 10. Quality Requirements