feat(simulation): Sprint 6 Touch — stance, tile presence, verbs, protocol v6
Implements the core Sprint 6: Touch systems across 5 tickets: - #449 ObserverSnapshot v6: add player_stance (MovementStance) and player_inventory (Vec<InventoryItem>) wire fields with serde defaults for backward compatibility. Bump PROTOCOL_VERSION 5→6. - #417 Stance system: Sprint/Walk/Careful/Crouch movement stance with tick-based speed (1/2/3/4 ticks per move), monologue rate multipliers, and PlayerMoveCooldown component. ToggleStanceUp/Down player actions. - #420 TilePresence: posture-layer collision system allowing same-tile occupancy for different layers (Standing/Prone/Seated/Fixture). Layer-based collision in validate_movement. - #421 ObjectType component: Readable/Container/Terminal/Door/Pickup/ Furniture types with Phase 1 verb sets computed from type + proximity. - #422 Phase 2 verb filter: KG-gated observer-side verb processing — POI priority flips, Confront injection at KnowsDetails+, contradiction marking, archetype-specific label relabeling (Smuggler/Detective). 217 unit tests + 17 integration tests passing. All MessagePack fixtures regenerated for v6 wire format. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+173
-4
@@ -15,7 +15,7 @@ pub use crate::simulation::time::{DayPhase, TickRate};
|
||||
/// negotiation is unnecessary. Client should reject snapshots with version !=
|
||||
/// PROTOCOL_VERSION. New fields use #[serde(default)] only during the migration
|
||||
/// period, then the default is removed once both sides are updated.
|
||||
pub const PROTOCOL_VERSION: u8 = 5;
|
||||
pub const PROTOCOL_VERSION: u8 = 6;
|
||||
|
||||
/// The ONLY data structure crossing the client-server boundary (D-020)
|
||||
/// Contains all information visible to the observer at a given tick.
|
||||
@@ -24,10 +24,11 @@ pub const PROTOCOL_VERSION: u8 = 5;
|
||||
/// v3 adds: relationship (D-033 entity color), observation (Visible/Remembered).
|
||||
/// v4 adds: nearby_interactions (D-060, #404 proximity + verbs[]).
|
||||
/// v5 adds: current_monologue (#414 internal monologue pipeline).
|
||||
/// v6 adds: player_stance (#449, D-053), player_inventory (#449, D-065).
|
||||
/// Future fields: ambient sound events, HUD state (D-020 expansion).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ObserverSnapshot {
|
||||
/// Protocol version for forward compatibility. Current: 4.
|
||||
/// Protocol version for forward compatibility. Current: 6.
|
||||
pub version: u8,
|
||||
/// Simulation tick when this snapshot was produced
|
||||
pub tick: u64,
|
||||
@@ -35,6 +36,15 @@ pub struct ObserverSnapshot {
|
||||
pub game_time: GameTime,
|
||||
/// Player character's facing direction for vision cone (D-015)
|
||||
pub player_facing: FacingDirection,
|
||||
/// Player's current movement stance for HUD display (#449, D-053).
|
||||
/// Defaults to Walk when stance component is absent.
|
||||
#[serde(default)]
|
||||
pub player_stance: MovementStance,
|
||||
/// Items in the player's inventory (#449, D-065).
|
||||
/// Visible only to this observer per D-010 info boundary.
|
||||
/// Empty when no CarriedBy component is present.
|
||||
#[serde(default)]
|
||||
pub player_inventory: Vec<InventoryItem>,
|
||||
/// All entities visible to the observer (filtered by LOS + vision cone)
|
||||
pub entities: Vec<VisibleEntity>,
|
||||
/// Tiles visible to the observer for fog rendering
|
||||
@@ -62,6 +72,81 @@ pub struct GameTime {
|
||||
pub tick_rate: TickRate,
|
||||
}
|
||||
|
||||
/// Player movement stance for tick-based movement speed (#449, D-053).
|
||||
/// Sprint/Walk/Careful/Crouch affect movement ticks, monologue rate, and
|
||||
/// interaction buffer availability. Wire format for ObserverSnapshot.
|
||||
/// v0.1 scope: Sprint/Walk/Careful/Crouch only (Prone deferred).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
|
||||
pub enum MovementStance {
|
||||
/// 1 tile/tick, monologue at 40%, interaction buffer suppressed (D-055)
|
||||
Sprint,
|
||||
/// 1 tile/2 ticks, monologue at 100% (default)
|
||||
#[default]
|
||||
Walk,
|
||||
/// 1 tile/3 ticks, monologue at 150%
|
||||
Careful,
|
||||
/// 1 tile/4 ticks, uses Prone/Seated posture layer (D-054)
|
||||
Crouch,
|
||||
}
|
||||
|
||||
impl MovementStance {
|
||||
/// Move one step up the stance ladder (toward Sprint).
|
||||
/// Returns self if already at the top.
|
||||
pub fn step_up(self) -> Self {
|
||||
match self {
|
||||
Self::Crouch => Self::Careful,
|
||||
Self::Careful => Self::Walk,
|
||||
Self::Walk => Self::Sprint,
|
||||
Self::Sprint => Self::Sprint,
|
||||
}
|
||||
}
|
||||
|
||||
/// Move one step down the stance ladder (toward Crouch).
|
||||
/// Returns self if already at the bottom.
|
||||
pub fn step_down(self) -> Self {
|
||||
match self {
|
||||
Self::Sprint => Self::Walk,
|
||||
Self::Walk => Self::Careful,
|
||||
Self::Careful => Self::Crouch,
|
||||
Self::Crouch => Self::Crouch,
|
||||
}
|
||||
}
|
||||
|
||||
/// Ticks per movement step for this stance.
|
||||
pub fn ticks_per_move(self) -> u32 {
|
||||
match self {
|
||||
Self::Sprint => 1,
|
||||
Self::Walk => 2,
|
||||
Self::Careful => 3,
|
||||
Self::Crouch => 4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Monologue rate multiplier as a percentage (100 = baseline).
|
||||
/// Sprint suppresses to 40%, Careful enhances to 150% (D-053).
|
||||
pub fn monologue_rate_percent(self) -> u32 {
|
||||
match self {
|
||||
Self::Sprint => 40,
|
||||
Self::Walk => 100,
|
||||
Self::Careful => 150,
|
||||
Self::Crouch => 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An item in the player's inventory, crossing the wire boundary (#449, D-065).
|
||||
/// Only items carried by the observer are included (D-010 info boundary).
|
||||
/// Slot positions map to a 3x3 grid (0-8), 9 slots universal.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InventoryItem {
|
||||
/// Wire-format entity identifier for the item
|
||||
pub item_id: u64,
|
||||
/// Display name for inventory UI
|
||||
pub name: String,
|
||||
/// Inventory slot index (0-8 for 3x3 grid)
|
||||
pub slot: u8,
|
||||
}
|
||||
|
||||
/// 8-directional facing direction, matching movement system.
|
||||
/// Used for vision cone computation (D-015) and snapshot wire format.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
|
||||
@@ -142,6 +227,43 @@ pub enum EntityKind {
|
||||
Terrain,
|
||||
}
|
||||
|
||||
/// Object type for D-057 Phase 1 verb computation (#421).
|
||||
///
|
||||
/// Determines the maximum possible verb set for an interactable world object.
|
||||
/// NPCs don't use ObjectType — they have their own verb logic (Talk/ExamineNpc).
|
||||
/// Phase 2 (#422) filters these verbs by the observer's knowledge graph.
|
||||
///
|
||||
/// Defined here in bridge::types because it appears on the wire in
|
||||
/// NearbyInteraction.object_type for Phase 2 context.
|
||||
/// VerbDef and verb_set() remain in simulation::interaction.
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ObjectType {
|
||||
/// Documents, manifests, notices — can be read
|
||||
Readable,
|
||||
/// Crates, lockers, cargo containers — can be opened and searched
|
||||
Container,
|
||||
/// Access terminals, comms panels — can be used
|
||||
Terminal,
|
||||
/// Doors, hatches, bulkheads — can be opened/closed
|
||||
Door,
|
||||
/// Small items that can be picked up (physical inventory, D-065)
|
||||
Pickup,
|
||||
/// Chairs, benches, consoles — can be sat at
|
||||
Furniture,
|
||||
}
|
||||
|
||||
/// Character archetype for Phase 2 verb filtering (#422) and monologue pool
|
||||
/// selection. Determines how the character perceives and labels interactions.
|
||||
/// v0.1: Smuggler and Detective (the two playable characters).
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
|
||||
pub enum CharacterArchetype {
|
||||
/// Smuggler character — sees Move/Stash on containers, physical manipulation verbs
|
||||
Smuggler,
|
||||
/// Detective character — sees Scan/Flag on containers, investigation verbs
|
||||
#[default]
|
||||
Detective,
|
||||
}
|
||||
|
||||
/// Semantic player actions, not raw key events (D-020)
|
||||
/// Timestamped for deterministic processing
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -174,6 +296,10 @@ pub enum PlayerAction {
|
||||
Unpause,
|
||||
/// Set tick rate: Full (1.0), Half (0.5), or Paused (0.0) per D-052
|
||||
SetTickRate(TickRate),
|
||||
/// Move one step up the stance ladder (toward Sprint) per D-053
|
||||
ToggleStanceUp,
|
||||
/// Move one step down the stance ladder (toward Crouch) per D-053
|
||||
ToggleStanceDown,
|
||||
}
|
||||
|
||||
/// Available interaction verbs for a nearby entity (D-060, #404)
|
||||
@@ -188,6 +314,15 @@ pub struct NearbyInteraction {
|
||||
pub distance: u32,
|
||||
/// Available verbs sorted by priority (index 0 = highest priority)
|
||||
pub verbs: Vec<VerbOption>,
|
||||
/// Object type for Phase 2 verb filter context (#422).
|
||||
/// None for NPCs and untyped objects. Enables archetype-specific
|
||||
/// label remapping (smuggler/detective see different labels for same verb).
|
||||
#[serde(default)]
|
||||
pub object_type: Option<ObjectType>,
|
||||
/// Whether the observer has contradicted knowledge about this entity (#422).
|
||||
/// Client may render a contradiction indicator (e.g., amber warning icon).
|
||||
#[serde(default)]
|
||||
pub contradicted: bool,
|
||||
}
|
||||
|
||||
/// A single available verb on a nearby entity
|
||||
@@ -203,14 +338,48 @@ pub struct VerbOption {
|
||||
pub available: bool,
|
||||
}
|
||||
|
||||
/// Verb types for the interaction system (D-060)
|
||||
/// Verb types for the interaction system (D-057, D-060)
|
||||
/// Only active verbs appear in verbs[]. Passive (Look, Overhear) and
|
||||
/// reactive (Monologue) verbs fire independently.
|
||||
///
|
||||
/// Phase 1 verbs (simulation, no KG): derived from ObjectType component (#421).
|
||||
/// Phase 2 verbs (observer, reads KG): filtered/augmented by #422.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum VerbKind {
|
||||
ExamineObject,
|
||||
// --- NPC verbs ---
|
||||
/// Observe an NPC (available at mid + close range)
|
||||
ExamineNpc,
|
||||
/// Talk to an NPC (close range only)
|
||||
Talk,
|
||||
|
||||
// --- Object verbs (D-057, per ObjectType) ---
|
||||
/// Generic observation — common to all object types
|
||||
Observe,
|
||||
/// Readable objects (manifests, logs, notices)
|
||||
Read,
|
||||
/// Container / Door — open it
|
||||
Open,
|
||||
/// Door — close it
|
||||
Close,
|
||||
/// Container — deeper search (distinct from Open)
|
||||
Search,
|
||||
/// Terminal — access logs, comms
|
||||
Use,
|
||||
/// Pickup items — physical inventory (D-065)
|
||||
Take,
|
||||
/// Furniture — sit/use
|
||||
Sit,
|
||||
|
||||
// --- Phase 2 verbs (observer, KG-gated, #422) ---
|
||||
/// Confront an NPC about known facts/contradictions.
|
||||
/// Phase 2 only: injected when observer has KnowsDetails+ confidence.
|
||||
/// Close range only. Opens confrontation dialogue (D-063).
|
||||
Confront,
|
||||
|
||||
// --- Legacy fallback ---
|
||||
/// Untyped object examination (entities without ObjectType component).
|
||||
/// Prefer ObjectType-derived verbs for new content.
|
||||
ExamineObject,
|
||||
}
|
||||
|
||||
/// Internal monologue event sent to the client for display (#414).
|
||||
|
||||
Reference in New Issue
Block a user