input.rs 2,739 → 858 lines: per-domain action handlers moved to their owning modules (inventory, movement, stance, examine, follow, interaction, save_io, settings, bridge::debug, economy, vision_cone, bookmark, test_world reset + new teleport.rs); input.rs keeps the queue, the thin dispatch table, and pause/cooldown glue. All 9 type_complexity allows dissolved via one PlayerInputQuery alias. dialogue.rs → dialogue/ directory module: selection (631), response (1,473), confrontation (714), mod.rs (226, shared session components + re-exports — public paths preserved). Documented seam deviation: process_walk_away lives with confrontation (D-064/D-063 share the same world-response shape). Mechanical, zero behavior change: determinism + golden_suite byte-identical (independently re-verified); 1,504 lib tests unchanged — 23 input tests moved with their subjects, 53 dialogue tests redistributed, zero deleted. System scheduling registrations untouched (input_plugin.rs 0-line diff). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
226 lines
8.1 KiB
Rust
226 lines
8.1 KiB
Rust
//! TeleportToHub QA action (#491) — Gauntlet-only return-to-hub verb.
|
|
//!
|
|
//! Moved out of simulation::input with the dispatcher split (T-1062): the
|
|
//! action only exists on the Gauntlet test world, so it lives with it.
|
|
|
|
use bevy_ecs::prelude::*;
|
|
|
|
use crate::simulation::input::PlayerInputQuery;
|
|
|
|
/// Handle TeleportToHub: move player to hub spawn, clear interaction state (#491).
|
|
///
|
|
/// Gauntlet-only action. On non-Gauntlet maps (feature disabled), logs a warning
|
|
/// and returns. On Gauntlet maps, moves the player to HUB.spawn and removes
|
|
/// dialogue, monologue, and interaction markers to prevent stale state.
|
|
///
|
|
/// Does NOT affect: room state, inventory, game time, knowledge graph.
|
|
pub fn handle_teleport_to_hub(player_query: &mut PlayerInputQuery, commands: &mut Commands) {
|
|
#[cfg(not(feature = "gauntlet"))]
|
|
{
|
|
tracing::warn!("TeleportToHub rejected: not a Gauntlet map");
|
|
return;
|
|
}
|
|
|
|
#[cfg(feature = "gauntlet")]
|
|
{
|
|
let Ok((player_entity, _, _, _)) = player_query.single() else {
|
|
return;
|
|
};
|
|
|
|
let hub_spawn = crate::test_world::constants::HUB.spawn;
|
|
|
|
// Move player to hub spawn
|
|
commands.entity(player_entity).insert(hub_spawn);
|
|
|
|
// Clear any pending movement
|
|
commands
|
|
.entity(player_entity)
|
|
.remove::<crate::simulation::movement::MoveIntent>();
|
|
|
|
// Clear dialogue/interaction markers (including mid-confrontation state)
|
|
commands
|
|
.entity(player_entity)
|
|
.remove::<crate::simulation::dialogue::TalkRequest>()
|
|
.remove::<crate::simulation::dialogue::ActiveDialogue>()
|
|
.remove::<crate::simulation::dialogue::WalkAwayRequest>()
|
|
.remove::<crate::simulation::dialogue::ConfrontationDelivered>()
|
|
.remove::<crate::simulation::dialogue::DialogueResponseRequest>();
|
|
|
|
tracing::info!(
|
|
x = hub_spawn.x,
|
|
y = hub_spawn.y,
|
|
z = hub_spawn.z,
|
|
"TeleportToHub: player moved to hub spawn"
|
|
);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Tests (#491; moved from input.rs, T-1062)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[cfg(all(test, feature = "gauntlet"))]
|
|
mod tests {
|
|
use crate::bridge::types::{PlayerAction, PlayerInput};
|
|
use crate::simulation::input::{process_player_input, InputQueue};
|
|
use crate::simulation::movement::{MoveIntent, PlayerCharacter, TilePosition};
|
|
use crate::simulation::time::{SimulationTime, TickRate};
|
|
|
|
#[test]
|
|
fn teleport_to_hub_moves_player() {
|
|
// #491: TeleportToHub moves player to hub spawn position.
|
|
let mut world = bevy_ecs::world::World::new();
|
|
world.insert_resource(InputQueue::default());
|
|
world.insert_resource(SimulationTime::default());
|
|
world.init_resource::<crate::knowledge::EntityRegistry>();
|
|
|
|
// Spawn player at a non-hub position
|
|
let player = world
|
|
.spawn((PlayerCharacter, TilePosition::new(84, 58, 0)))
|
|
.id();
|
|
|
|
world.resource_mut::<InputQueue>().push(PlayerInput {
|
|
tick: 0,
|
|
action: PlayerAction::TeleportToHub,
|
|
});
|
|
|
|
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
|
schedule.add_systems(process_player_input);
|
|
schedule.run(&mut world);
|
|
|
|
let pos = world
|
|
.get::<TilePosition>(player)
|
|
.expect("player has position");
|
|
let hub_spawn = crate::test_world::constants::HUB.spawn;
|
|
assert_eq!(pos.x, hub_spawn.x, "player x at hub spawn");
|
|
assert_eq!(pos.y, hub_spawn.y, "player y at hub spawn");
|
|
assert_eq!(pos.z, hub_spawn.z, "player z at hub spawn");
|
|
}
|
|
|
|
#[test]
|
|
fn teleport_to_hub_clears_dialogue_markers() {
|
|
// #491: TeleportToHub removes ActiveDialogue, TalkRequest,
|
|
// WalkAwayRequest, and ConfrontationDelivered.
|
|
let mut world = bevy_ecs::world::World::new();
|
|
world.insert_resource(InputQueue::default());
|
|
world.insert_resource(SimulationTime::default());
|
|
world.init_resource::<crate::knowledge::EntityRegistry>();
|
|
|
|
// Spawn a fake NPC target
|
|
let npc = world.spawn(TilePosition::new(10, 10, 0)).id();
|
|
|
|
// Spawn player with active dialogue state + mid-confrontation marker
|
|
let player = world
|
|
.spawn((
|
|
PlayerCharacter,
|
|
TilePosition::new(84, 58, 0),
|
|
crate::simulation::dialogue::TalkRequest { target: npc },
|
|
crate::simulation::dialogue::ActiveDialogue {
|
|
target: npc,
|
|
interaction_type: crate::knowledge::events::InteractionType::Talk,
|
|
started_tick: 0,
|
|
},
|
|
crate::simulation::dialogue::WalkAwayRequest,
|
|
crate::simulation::dialogue::ConfrontationDelivered { target: npc },
|
|
))
|
|
.id();
|
|
|
|
world.resource_mut::<InputQueue>().push(PlayerInput {
|
|
tick: 0,
|
|
action: PlayerAction::TeleportToHub,
|
|
});
|
|
|
|
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
|
schedule.add_systems(process_player_input);
|
|
schedule.run(&mut world);
|
|
|
|
assert!(
|
|
world
|
|
.get::<crate::simulation::dialogue::TalkRequest>(player)
|
|
.is_none(),
|
|
"TalkRequest cleared after teleport"
|
|
);
|
|
assert!(
|
|
world
|
|
.get::<crate::simulation::dialogue::ActiveDialogue>(player)
|
|
.is_none(),
|
|
"ActiveDialogue cleared after teleport"
|
|
);
|
|
assert!(
|
|
world
|
|
.get::<crate::simulation::dialogue::WalkAwayRequest>(player)
|
|
.is_none(),
|
|
"WalkAwayRequest cleared after teleport"
|
|
);
|
|
assert!(
|
|
world
|
|
.get::<crate::simulation::dialogue::ConfrontationDelivered>(player)
|
|
.is_none(),
|
|
"ConfrontationDelivered cleared after teleport"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn teleport_to_hub_clears_move_intent() {
|
|
// #491: TeleportToHub removes any pending MoveIntent.
|
|
let mut world = bevy_ecs::world::World::new();
|
|
world.insert_resource(InputQueue::default());
|
|
world.insert_resource(SimulationTime::default());
|
|
world.init_resource::<crate::knowledge::EntityRegistry>();
|
|
|
|
let player = world
|
|
.spawn((
|
|
PlayerCharacter,
|
|
TilePosition::new(84, 58, 0),
|
|
MoveIntent {
|
|
target: TilePosition::new(85, 58, 0),
|
|
},
|
|
))
|
|
.id();
|
|
|
|
world.resource_mut::<InputQueue>().push(PlayerInput {
|
|
tick: 0,
|
|
action: PlayerAction::TeleportToHub,
|
|
});
|
|
|
|
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
|
schedule.add_systems(process_player_input);
|
|
schedule.run(&mut world);
|
|
|
|
assert!(
|
|
world.get::<MoveIntent>(player).is_none(),
|
|
"MoveIntent cleared after teleport"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn teleport_to_hub_allowed_while_paused() {
|
|
// #491: TeleportToHub is a QA action — allowed even when paused.
|
|
let mut world = bevy_ecs::world::World::new();
|
|
world.insert_resource(InputQueue::default());
|
|
let mut time = SimulationTime::default();
|
|
time.tick_rate = TickRate::Paused;
|
|
world.insert_resource(time);
|
|
world.init_resource::<crate::knowledge::EntityRegistry>();
|
|
|
|
let player = world
|
|
.spawn((PlayerCharacter, TilePosition::new(84, 58, 0)))
|
|
.id();
|
|
|
|
world.resource_mut::<InputQueue>().push(PlayerInput {
|
|
tick: 0,
|
|
action: PlayerAction::TeleportToHub,
|
|
});
|
|
|
|
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
|
schedule.add_systems(process_player_input);
|
|
schedule.run(&mut world);
|
|
|
|
let pos = world
|
|
.get::<TilePosition>(player)
|
|
.expect("player has position");
|
|
let hub_spawn = crate::test_world::constants::HUB.spawn;
|
|
assert_eq!(pos.x, hub_spawn.x, "teleport works while paused");
|
|
}
|
|
}
|