NEO-25: route E/R interact via main _unhandled_key_input for reliable input

pull/48/head
VinPropane 2026-04-24 20:44:43 -04:00
parent 0e72d98177
commit 06d9104157
3 changed files with 34 additions and 13 deletions

View File

@ -91,7 +91,7 @@ On boot, **`InteractablesCatalogClient`** **`GET`s** **`/game/world/interactable
1. Run the game server and client as in NEO-7.
2. **F5** in Godot: after catalog load, two interactable sites appear (center + **+X / Z** floor, anchor **(12, 0.5, 6)** — clear of the gray **Obstacle** near **(6, 1, 5)**). Default spawn **(-5, 0.9, -5)** is out of range of the **center** node (3 m); markers near **origin** stay **dim** until you **WASD** within horizontal **3** m of **(0, 0.5, 0)** on X/Z — then that pair **brightens**.
3. Press **E** (**`interact`**): Output **`allowed=true`** when the **terminal** pair glows; **`allowed=false`** / **`out_of_range`** when dim. Walk toward **(12, 6)** until the **second** pair glows; press **R** (**`interact_secondary`**) for **`allowed=true`** on the resource node.
4. Interaction uses **`_input`** (and key fallbacks) so keys register in the embedded **Game** dock; click the game view if the editor had focus elsewhere.
4. **E** / **R** are handled in **`main.gd`** **`_unhandled_key_input`** and forwarded to **`InteractionRequestClient`** (same pattern as dev keys) so they still register in the embedded **Game** dock; click the game view if the editor had focus elsewhere.
Full checklist: [`docs/manual-qa/NEO-25.md`](../manual-qa/NEO-25.md).

View File

@ -2,7 +2,9 @@ extends Node
## NS-18 / NEO-25: POST InteractionRequest; prints server allow/deny to Output (prototype).
## [kbd]E[/kbd] → [code]prototype_terminal[/code]; [kbd]R[/kbd] → [code]prototype_resource_node_alpha[/code]
## (stable contract ids — must match server registry).
## (stable contract ids — must match server registry). Key handling is delegated from
## [code]main.gd[/code] [method Node._unhandled_key_input] (see [method post_interact_terminal] /
## [method post_interact_resource]) so the embedded Game dock still receives E/R reliably.
const ID_TERMINAL := "prototype_terminal"
const ID_RESOURCE_NODE := "prototype_resource_node_alpha"
@ -28,17 +30,12 @@ func _ready() -> void:
_http.request_completed.connect(_on_request_completed)
func _input(event: InputEvent) -> void:
if event.is_action_pressed("interact"):
_post_interact(ID_TERMINAL)
elif event.is_action_pressed("interact_secondary"):
_post_interact(ID_RESOURCE_NODE)
elif event is InputEventKey:
var k := event as InputEventKey
if k.pressed and not k.echo and (k.physical_keycode == KEY_E or k.keycode == KEY_E):
_post_interact(ID_TERMINAL)
elif k.pressed and not k.echo and (k.physical_keycode == KEY_R or k.keycode == KEY_R):
_post_interact(ID_RESOURCE_NODE)
func post_interact_terminal() -> void:
_post_interact(ID_TERMINAL)
func post_interact_resource() -> void:
_post_interact(ID_RESOURCE_NODE)
func _base_root() -> String:

View File

@ -77,6 +77,7 @@ var _dev_obstacle_smoke: Node3D
@onready var _player_pos_label: Label = $UICanvas/PlayerPositionLabel
@onready var _target_lock_label: Label = $UICanvas/TargetLockLabel
@onready var _target_client: Node = $TargetSelectionClient
@onready var _interaction_client: Node = $InteractionRequestClient
@onready var _floor: StaticBody3D = $World/NavigationRegion3D/Floor
@ -279,11 +280,34 @@ func _on_move_rejected(reason_code: String) -> void:
## **F9** / **F10** are debugger shortcuts in the embedded game; use the Input Map action.
## For a true **freed-node** occluder test, reload the scene or remove the body in the editor.
func _unhandled_key_input(event: InputEvent) -> void:
# NEO-25: route interact keys here — `InteractionRequestClient._input` is unreliable in the
# embedded Game dock / focus order; `_unhandled_key_input` on the scene root still fires.
if event.is_action_pressed("interact"):
_forward_interact_post("post_interact_terminal")
return
if event.is_action_pressed("interact_secondary"):
_forward_interact_post("post_interact_resource")
return
if event is InputEventKey:
var k := event as InputEventKey
if k.pressed and not k.echo and (k.physical_keycode == KEY_E or k.keycode == KEY_E):
_forward_interact_post("post_interact_terminal")
return
if k.pressed and not k.echo and (k.physical_keycode == KEY_R or k.keycode == KEY_R):
_forward_interact_post("post_interact_resource")
return
if not event.is_action_pressed("dev_toggle_occluder_obstacle"):
return
call_deferred("_dev_toggle_obstacle_smoke_deferred")
func _forward_interact_post(method: String) -> void:
if not is_instance_valid(_interaction_client):
return
if _interaction_client.has_method(method):
_interaction_client.call(method)
func _dev_toggle_obstacle_smoke_deferred() -> void:
var obstacle := _dev_obstacle_smoke
if obstacle == null or not is_instance_valid(obstacle):