feat(client): resolve OQ-07 — insert-off suppresses verb labels (#522)
Option (a): cursor shape still changes (body orients to targets), but verb labels and interaction prompts are suppressed when insert_active is false. Amends D-056 and D-057 with resolution note. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,15 @@ var dialogue_active: bool = false
|
||||
var room_id: Variant = null # String room_id from snapshot, null in non-gauntlet mode
|
||||
var gauntlet_mode: bool = false # true when snapshot includes gauntlet_mode flag
|
||||
|
||||
# OQ-07 (#522): Insert active state — false suppresses verb labels (z-layer 6).
|
||||
# Cursor shape changes still fire when false (D-056 option a).
|
||||
# v0.1: always true (both detective and smuggler have inserts). Server may send false in future.
|
||||
var insert_active: bool = true
|
||||
|
||||
# #507: RNG seed for replay determinism — populated from snapshot "rng_seed" field.
|
||||
# Null in v0.1 (server does not yet send this field; protocol change required).
|
||||
var rng_seed: Variant = null
|
||||
|
||||
# v7 fields (#431, D-059/D-060)
|
||||
var pending_recognitions: Array = [] # [{entity_id, x, y, z, remaining_ticks, total_delay_ticks}]
|
||||
|
||||
@@ -132,6 +141,20 @@ func apply_snapshot(snapshot: Dictionary) -> void:
|
||||
else:
|
||||
room_id = null
|
||||
|
||||
# OQ-07 (#522): insert_active — defaults true (v0.1 always has insert).
|
||||
# Server may send false for characters without an insert in future sprints.
|
||||
if snapshot.has("insert_active") and snapshot.insert_active is bool:
|
||||
insert_active = snapshot.insert_active
|
||||
else:
|
||||
insert_active = true
|
||||
|
||||
# #507: rng_seed — server sends current RNG seed for replay determinism.
|
||||
# Field: "rng_seed" (u64 as integer). Null if server does not include it.
|
||||
if snapshot.has("rng_seed"):
|
||||
rng_seed = snapshot.rng_seed
|
||||
else:
|
||||
rng_seed = null
|
||||
|
||||
# v2: visible_tiles with visibility sectors
|
||||
# Derives visible_positions when not explicitly provided (real server mode)
|
||||
if snapshot.has("visible_tiles") and snapshot.visible_tiles is Array and snapshot.visible_tiles.size() > 0:
|
||||
|
||||
@@ -11,6 +11,9 @@ enum State { DEFAULT, ENTITY_HOVER, OBJECT_HOVER, WEAPON_AIM }
|
||||
var current_state: State = State.DEFAULT
|
||||
var hovered_entity_id: int = -1
|
||||
var weapon_mode_active: bool = false
|
||||
# OQ-07 (#522): when false, verb labels are suppressed (should_show_interactions → false).
|
||||
# Cursor shape transitions still fire — the character's body still orients to targets.
|
||||
var insert_active: bool = true
|
||||
|
||||
signal state_changed(new_state: State)
|
||||
signal hovered_entity_changed(entity_id: int)
|
||||
@@ -305,8 +308,15 @@ func get_z_layer() -> int:
|
||||
|
||||
|
||||
func should_show_interactions() -> bool:
|
||||
# OQ-07: insert off suppresses verb labels even though cursor shape still changes
|
||||
if not insert_active:
|
||||
return false
|
||||
return not weapon_mode_active or _shift_held
|
||||
|
||||
|
||||
func set_insert_active(active: bool) -> void:
|
||||
insert_active = active
|
||||
|
||||
|
||||
func get_interaction_range() -> int:
|
||||
return 2 # D-056: ~2 sim tiles
|
||||
|
||||
@@ -305,6 +305,46 @@ func test_cursor_changes_require_los() -> void:
|
||||
cursor.queue_free()
|
||||
|
||||
|
||||
# -- OQ-07: Insert-off behavior (D-056 amendment, #522) ----------------------
|
||||
# Option (a): cursor shape still changes, verb labels suppressed.
|
||||
|
||||
func test_insert_off_cursor_still_changes_shape() -> void:
|
||||
# OQ-07 option (a): cursor state machine still fires when insert is off.
|
||||
# The character's body orients toward targets even without insert data.
|
||||
var cursor = _make_cursor_or_skip()
|
||||
if cursor == null:
|
||||
return
|
||||
if cursor.has_method("set_insert_active") and cursor.has_method("set_hover_target"):
|
||||
cursor.set_insert_active(false)
|
||||
cursor.set_hover_target({"entity_id": 2, "kind": "Npc", "relationship": "Unknown"})
|
||||
assert_that(str(cursor.get_state())).is_equal("EntityHover")
|
||||
cursor.queue_free()
|
||||
|
||||
|
||||
func test_insert_off_suppresses_interactions() -> void:
|
||||
# OQ-07 option (a): with insert off, should_show_interactions() returns false.
|
||||
# Verb labels (z-layer 6) are suppressed — no actionable insert data.
|
||||
var cursor = _make_cursor_or_skip()
|
||||
if cursor == null:
|
||||
return
|
||||
if cursor.has_method("set_insert_active") and cursor.has_method("should_show_interactions"):
|
||||
cursor.set_insert_active(false)
|
||||
assert_that(cursor.should_show_interactions()).is_false()
|
||||
cursor.queue_free()
|
||||
|
||||
|
||||
func test_insert_on_restores_interaction_display() -> void:
|
||||
# Re-enabling insert allows interactions to show again.
|
||||
var cursor = _make_cursor_or_skip()
|
||||
if cursor == null:
|
||||
return
|
||||
if cursor.has_method("set_insert_active") and cursor.has_method("should_show_interactions"):
|
||||
cursor.set_insert_active(false)
|
||||
cursor.set_insert_active(true)
|
||||
assert_that(cursor.should_show_interactions()).is_true()
|
||||
cursor.queue_free()
|
||||
|
||||
|
||||
# -- Interaction range (D-056) ------------------------------------------------
|
||||
|
||||
func test_click_interaction_range() -> void:
|
||||
|
||||
@@ -13,6 +13,8 @@ extends PanelContainer
|
||||
var _is_showing: bool = false
|
||||
var _active_tween: Tween = null
|
||||
var _current_target_id: int = -1
|
||||
# OQ-07 (#522): when false, prompt is suppressed (z-layer 6 insert overlay only)
|
||||
var _insert_active: bool = true
|
||||
|
||||
const FADE_IN: float = 0.15
|
||||
const FADE_OUT: float = 0.15
|
||||
@@ -23,6 +25,11 @@ func _ready() -> void:
|
||||
_is_showing = false
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
# OQ-07: insert off means no verb labels (z-layer 6 insert overlay suppressed)
|
||||
if not _insert_active:
|
||||
if _is_showing:
|
||||
_hide_prompt()
|
||||
return
|
||||
var interactions: Array = GameState.nearby_interactions
|
||||
if interactions.size() > 0:
|
||||
_show_prompt(interactions[0])
|
||||
@@ -69,6 +76,14 @@ func _hide_prompt() -> void:
|
||||
func get_interaction_target() -> int:
|
||||
return _current_target_id
|
||||
|
||||
## OQ-07 (#522): insert off hides prompt (diegetic: no insert data on z-layer 6).
|
||||
## Cursor shape changes still fire on cursor_renderer.gd.
|
||||
func set_insert_active(active: bool) -> void:
|
||||
_insert_active = active
|
||||
if not active and _is_showing:
|
||||
_hide_prompt()
|
||||
|
||||
|
||||
## Returns the selected verb kind (v0.1: first verb on nearest, v0.2: radial selection).
|
||||
func get_selected_verb() -> String:
|
||||
var interactions: Array = GameState.nearby_interactions
|
||||
|
||||
@@ -212,6 +212,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight
|
||||
- **Source:** Control & Interaction Workshop (2026-02-13)
|
||||
- **Raised by:** Araminta (visual spec), Stig (UX rules + diegetic test), Ozzie (weapon suppression)
|
||||
- **Dissent:** None.
|
||||
- **OQ-07 resolution (2026-02-19, #522):** Insert-off behavior is **option (a): cursor shape still changes, verb labels suppressed.** The cursor state machine fires normally (entity hover → bracket shape, object hover → X-shape) — the character's body physically orients toward targets as a subconscious/spatial response. But the insert does not process these into actionable data: `should_show_interactions()` returns false when `insert_active == false`, and interaction labels (z-layer 6) are hidden via `set_insert_active(false)` on `InteractionList` and `InteractionPrompt`. `GameState.insert_active` is the source of truth (defaults true in v0.1; wired from snapshot field `insert_active`). Rationale: diegetically consistent — the body reacts to proximity; the insert reacts to commands.
|
||||
|
||||
### D-057: Entity interaction — vertical list, insert-styled
|
||||
- **Date:** 2026-02-13
|
||||
@@ -224,6 +225,7 @@ How the player observes and interacts with the world: camera, fog, line-of-sight
|
||||
- **Source:** Control & Interaction Workshop (2026-02-13)
|
||||
- **Raised by:** Stig (vertical list structure + diegetic test), Araminta (insert aesthetic), Dudley (two-phase verb computation), Nigel (character-archetype verb sets). Lead resolved: Stig's structure, Araminta's styling.
|
||||
- **Dissent:** Araminta argued for spoke radial (geometry transformation signals qualitative knowledge change — new spoke growing). Lead rejected: items moving under cursor when knowledge changes is a moving goalpost (bad UX while aiming at an option).
|
||||
- **OQ-07 resolution (2026-02-19, #522):** When `insert_active == false`, the interaction list hides completely (`set_insert_active(false)` → `_hide()`). Cursor shape changes still occur per D-056 OQ-07 — the list suppression is independent of cursor state. `GameState.insert_active` drives this at runtime, wired via `main.gd` on each snapshot.
|
||||
|
||||
### D-058: World menu — radial, 4 spokes
|
||||
- **Date:** 2026-02-13
|
||||
@@ -338,4 +340,4 @@ How the player observes and interacts with the world: camera, fog, line-of-sight
|
||||
|
||||
---
|
||||
|
||||
*29 decisions. Last updated: 2026-02-16*
|
||||
*29 decisions. Last updated: 2026-02-19 (OQ-07 resolved: D-056/D-057 amendment)*
|
||||
|
||||
Reference in New Issue
Block a user