feat(simulation): integrate knowledge graph into observer snapshot (#366)
VisibleEntity now carries relationship state (D-033 entity color) and observation type (Visible vs Remembered). compute_observer_snapshot queries the player's KnowledgeGraph to overlay relationship data on visible entities and include remembered (not-in-LOS) entities as fog ghosts at their last known position. Regenerated msgpack fixtures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -89,6 +89,8 @@ pub fn generate_snapshot(
|
||||
z,
|
||||
kind,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub use crate::knowledge::types::{EntityVisibility, KnowledgeConfidence, RelationshipState};
|
||||
pub use crate::simulation::time::DayPhase;
|
||||
|
||||
/// The ONLY data structure crossing the client-server boundary (D-020)
|
||||
@@ -95,6 +96,14 @@ pub struct VisibleEntity {
|
||||
pub kind: EntityKind,
|
||||
/// Which vision cone sector this entity falls in (D-015)
|
||||
pub visibility: VisibilitySector,
|
||||
/// Relationship state for D-033 entity color rendering.
|
||||
/// Unknown for entities not yet in the knowledge graph.
|
||||
#[serde(default)]
|
||||
pub relationship: RelationshipState,
|
||||
/// Whether this entity is currently visible or remembered from knowledge.
|
||||
/// Visible = in LOS right now. Remembered = known but not in LOS.
|
||||
#[serde(default)]
|
||||
pub observation: EntityVisibility,
|
||||
}
|
||||
|
||||
/// Category of visible entity
|
||||
|
||||
@@ -205,7 +205,7 @@ impl Default for DecayThresholds {
|
||||
|
||||
/// How an entity appears in the observer snapshot.
|
||||
/// Extends VisibleEntity for knowledge-based rendering.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum EntityVisibility {
|
||||
/// Currently in line of sight.
|
||||
Visible,
|
||||
@@ -215,3 +215,9 @@ pub enum EntityVisibility {
|
||||
age_ticks: u64,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for EntityVisibility {
|
||||
fn default() -> Self {
|
||||
Self::Visible
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use bevy_ecs::prelude::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::bridge::types::*;
|
||||
use crate::knowledge::{EntityRegistry, KnowledgeGraph};
|
||||
use crate::perception::shadowcast::compute_fov;
|
||||
use crate::perception::vision_cone::{apply_vision_cone, Facing, VisionConeConfig};
|
||||
use crate::simulation::movement::{PlayerCharacter, TilePosition, WalkabilityMap};
|
||||
@@ -20,7 +21,8 @@ use crate::simulation::time::SimulationTime;
|
||||
pub fn compute_observer_snapshot(
|
||||
time: Res<SimulationTime>,
|
||||
walkability: Res<WalkabilityMap>,
|
||||
observer_query: Query<(&TilePosition, Option<&Facing>), With<PlayerCharacter>>,
|
||||
registry: Res<EntityRegistry>,
|
||||
observer_query: Query<(&TilePosition, Option<&Facing>, &KnowledgeGraph), With<PlayerCharacter>>,
|
||||
all_entities: Query<(
|
||||
Entity,
|
||||
&TilePosition,
|
||||
@@ -29,7 +31,7 @@ pub fn compute_observer_snapshot(
|
||||
)>,
|
||||
mut buffer: ResMut<SnapshotBuffer>,
|
||||
) {
|
||||
let Ok((observer_pos, facing_opt)) = observer_query.single() else {
|
||||
let Ok((observer_pos, facing_opt, observer_kg)) = observer_query.single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -74,8 +76,9 @@ pub fn compute_observer_snapshot(
|
||||
.map(|&(x, y, sector)| ((x, y), sector))
|
||||
.collect();
|
||||
|
||||
// Step 5: Filter entities by visibility
|
||||
// Step 5: Filter entities by visibility, overlay knowledge
|
||||
let mut entities = Vec::new();
|
||||
let mut visible_entity_bits: HashSet<u64> = HashSet::new();
|
||||
for (entity, pos, is_player, is_npc) in all_entities.iter() {
|
||||
// Different z-level: not visible
|
||||
if pos.z != z {
|
||||
@@ -101,17 +104,72 @@ pub fn compute_observer_snapshot(
|
||||
.copied()
|
||||
.unwrap_or(VisibilitySector::Peripheral);
|
||||
|
||||
// Look up relationship from knowledge graph (D-033 entity color)
|
||||
let relationship = if is_player.is_some() {
|
||||
RelationshipState::Known // Self
|
||||
} else if let Some(stable_id) = registry.to_stable(entity) {
|
||||
observer_kg.relationship_with(&stable_id)
|
||||
} else {
|
||||
RelationshipState::Unknown
|
||||
};
|
||||
|
||||
visible_entity_bits.insert(entity.to_bits());
|
||||
entities.push(VisibleEntity {
|
||||
entity_id: entity.to_bits(), // Temporary: use entity.to_bits() until #362 StableEntityId
|
||||
entity_id: entity.to_bits(),
|
||||
x: rx,
|
||||
y: ry,
|
||||
z: rz,
|
||||
kind,
|
||||
visibility: sector,
|
||||
relationship,
|
||||
observation: EntityVisibility::Visible,
|
||||
});
|
||||
}
|
||||
|
||||
// Step 6: Build GameTime from SimulationTime
|
||||
// Step 6: Add remembered entities from knowledge graph (#366)
|
||||
// Entities the observer knows about but can't currently see.
|
||||
for (stable_id, knowledge) in observer_kg.known_entities_iter() {
|
||||
// Skip if currently visible (already in the entity list)
|
||||
if let Some(entity) = registry.to_entity(stable_id) {
|
||||
if visible_entity_bits.contains(&entity.to_bits()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip if no known position (never directly observed)
|
||||
let Some(position) = knowledge.last_known_position else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// Direct confidence means the entity should be in LOS — if it isn't,
|
||||
// that's a transient data inconsistency. Skip rather than show a ghost.
|
||||
if knowledge.confidence == KnowledgeConfidence::Direct {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (rx, ry, rz) = position.to_render_coords();
|
||||
let age_ticks = time.tick.saturating_sub(knowledge.last_observed_tick);
|
||||
let entity_id = registry
|
||||
.to_entity(stable_id)
|
||||
.map(|e| e.to_bits())
|
||||
.unwrap_or(stable_id.0);
|
||||
|
||||
entities.push(VisibleEntity {
|
||||
entity_id,
|
||||
x: rx,
|
||||
y: ry,
|
||||
z: rz,
|
||||
kind: EntityKind::Npc, // Remembered entities are NPCs (only NPCs are tracked)
|
||||
visibility: VisibilitySector::Forward, // Not meaningful for remembered entities
|
||||
relationship: knowledge.relationship,
|
||||
observation: EntityVisibility::Remembered {
|
||||
confidence: knowledge.confidence,
|
||||
age_ticks,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Step 7: Build GameTime from SimulationTime
|
||||
let game_time = GameTime {
|
||||
day: time.day(),
|
||||
time_of_day: time.time_of_day_minutes(),
|
||||
@@ -120,13 +178,14 @@ pub fn compute_observer_snapshot(
|
||||
};
|
||||
|
||||
tracing::trace!(
|
||||
"compute_observer_snapshot: tick={}, entities={}, tiles={}",
|
||||
"compute_observer_snapshot: tick={}, visible={}, remembered={}, tiles={}",
|
||||
time.tick,
|
||||
entities.len(),
|
||||
visible_entity_bits.len(),
|
||||
entities.len() - visible_entity_bits.len(),
|
||||
visible_tiles.len(),
|
||||
);
|
||||
|
||||
// Step 7: Assemble v2 snapshot
|
||||
// Step 8: Assemble snapshot
|
||||
buffer.snapshot = Some(ObserverSnapshot {
|
||||
version: 2,
|
||||
tick: time.tick,
|
||||
@@ -140,15 +199,17 @@ pub fn compute_observer_snapshot(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::knowledge::{EntityRegistry, KnowledgeGraph};
|
||||
use crate::perception::vision_cone::Facing;
|
||||
use bevy_ecs::world::World;
|
||||
|
||||
/// Helper: set up a test world with player and walkability map
|
||||
/// Helper: set up a test world with player, walkability map, and knowledge resources
|
||||
fn setup_world(width: i32, height: i32) -> World {
|
||||
let mut world = World::new();
|
||||
world.insert_resource(SimulationTime::default());
|
||||
world.insert_resource(WalkabilityMap::new(width, height, 1));
|
||||
world.init_resource::<SnapshotBuffer>();
|
||||
world.init_resource::<EntityRegistry>();
|
||||
world
|
||||
}
|
||||
|
||||
@@ -159,6 +220,7 @@ mod tests {
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing::default(),
|
||||
KnowledgeGraph::new(),
|
||||
));
|
||||
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
@@ -170,6 +232,7 @@ mod tests {
|
||||
assert_eq!(snapshot.version, 2);
|
||||
assert_eq!(snapshot.entities.len(), 1);
|
||||
assert!(matches!(snapshot.entities[0].kind, EntityKind::Player));
|
||||
assert_eq!(snapshot.entities[0].observation, EntityVisibility::Visible);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -179,6 +242,7 @@ mod tests {
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing(FacingDirection::North),
|
||||
KnowledgeGraph::new(),
|
||||
));
|
||||
// NPC directly north of player (in forward cone)
|
||||
world.spawn((crate::npc::Npc, TilePosition::new(16, 14, 0)));
|
||||
@@ -196,6 +260,7 @@ mod tests {
|
||||
.find(|e| matches!(e.kind, EntityKind::Npc))
|
||||
.expect("NPC should be visible");
|
||||
assert_eq!(npc.visibility, VisibilitySector::Forward);
|
||||
assert_eq!(npc.observation, EntityVisibility::Visible);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -205,6 +270,7 @@ mod tests {
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing(FacingDirection::North),
|
||||
KnowledgeGraph::new(),
|
||||
));
|
||||
// Wall between player and NPC
|
||||
let mut walkability = world.resource_mut::<WalkabilityMap>();
|
||||
@@ -234,6 +300,7 @@ mod tests {
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing(FacingDirection::North),
|
||||
KnowledgeGraph::new(),
|
||||
));
|
||||
// NPC far behind player (south, in blind spot)
|
||||
world.spawn((crate::npc::Npc, TilePosition::new(16, 26, 0)));
|
||||
@@ -259,6 +326,7 @@ mod tests {
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing::default(),
|
||||
KnowledgeGraph::new(),
|
||||
));
|
||||
// NPC on different z-level
|
||||
world.spawn((crate::npc::Npc, TilePosition::new(16, 14, 1)));
|
||||
@@ -288,6 +356,7 @@ mod tests {
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing::default(),
|
||||
KnowledgeGraph::new(),
|
||||
));
|
||||
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
@@ -311,6 +380,7 @@ mod tests {
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing::default(),
|
||||
KnowledgeGraph::new(),
|
||||
));
|
||||
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
@@ -330,4 +400,153 @@ mod tests {
|
||||
.any(|t| t.x == 16 && t.y == 16 && t.z == 0);
|
||||
assert!(has_observer_tile, "observer tile should be visible");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visible_npc_has_relationship_from_knowledge() {
|
||||
let mut world = setup_world(32, 32);
|
||||
let mut registry = EntityRegistry::new(0);
|
||||
|
||||
let npc = world
|
||||
.spawn((crate::npc::Npc, TilePosition::new(16, 14, 0)))
|
||||
.id();
|
||||
let npc_sid = registry.register(npc);
|
||||
|
||||
// Player knows NPC is hostile
|
||||
let mut kg = KnowledgeGraph::new();
|
||||
kg.observe_entity(npc_sid, TilePosition::new(16, 14, 0), 50);
|
||||
kg.set_relationship(&npc_sid, RelationshipState::Hostile);
|
||||
|
||||
world.spawn((
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing(FacingDirection::North),
|
||||
kg,
|
||||
));
|
||||
|
||||
world.insert_resource(registry);
|
||||
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
schedule.add_systems(compute_observer_snapshot);
|
||||
schedule.run(&mut world);
|
||||
|
||||
let buffer = world.resource::<SnapshotBuffer>();
|
||||
let snapshot = buffer.snapshot.as_ref().unwrap();
|
||||
let npc_entity = snapshot
|
||||
.entities
|
||||
.iter()
|
||||
.find(|e| matches!(e.kind, EntityKind::Npc))
|
||||
.expect("NPC should be visible");
|
||||
assert_eq!(npc_entity.relationship, RelationshipState::Hostile);
|
||||
assert_eq!(npc_entity.observation, EntityVisibility::Visible);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remembered_entity_appears_as_ghost() {
|
||||
let mut world = setup_world(32, 32);
|
||||
let mut registry = EntityRegistry::new(0);
|
||||
|
||||
// NPC exists but is behind a wall (not visible)
|
||||
let npc = world
|
||||
.spawn((crate::npc::Npc, TilePosition::new(16, 10, 0)))
|
||||
.id();
|
||||
let npc_sid = registry.register(npc);
|
||||
|
||||
// Player previously saw NPC, now it left LOS (KnowsDetails)
|
||||
let mut kg = KnowledgeGraph::new();
|
||||
kg.observe_entity(npc_sid, TilePosition::new(16, 14, 0), 50);
|
||||
kg.observe_entity_leaving_los(&npc_sid, 60);
|
||||
kg.set_relationship(&npc_sid, RelationshipState::PersonOfInterest);
|
||||
|
||||
let player = world
|
||||
.spawn((
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing(FacingDirection::North),
|
||||
kg,
|
||||
))
|
||||
.id();
|
||||
registry.register(player);
|
||||
|
||||
// Wall blocks LOS to NPC's actual position
|
||||
let mut walkability = world.resource_mut::<WalkabilityMap>();
|
||||
walkability.set_walkable(&TilePosition::new(16, 12, 0), false);
|
||||
|
||||
world.insert_resource(registry);
|
||||
world.insert_resource(SimulationTime { tick: 100, paused: false });
|
||||
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
schedule.add_systems(compute_observer_snapshot);
|
||||
schedule.run(&mut world);
|
||||
|
||||
let buffer = world.resource::<SnapshotBuffer>();
|
||||
let snapshot = buffer.snapshot.as_ref().unwrap();
|
||||
|
||||
// Should have player (visible) + NPC (remembered)
|
||||
let remembered: Vec<_> = snapshot
|
||||
.entities
|
||||
.iter()
|
||||
.filter(|e| matches!(e.observation, EntityVisibility::Remembered { .. }))
|
||||
.collect();
|
||||
assert_eq!(remembered.len(), 1, "should have one remembered entity");
|
||||
assert_eq!(remembered[0].relationship, RelationshipState::PersonOfInterest);
|
||||
|
||||
// Remembered entity should be at last_known_position (16, 14) not actual (16, 10)
|
||||
assert_eq!(remembered[0].x, 16.5);
|
||||
assert_eq!(remembered[0].y, 14.5);
|
||||
|
||||
if let EntityVisibility::Remembered { confidence, age_ticks } = &remembered[0].observation {
|
||||
assert_eq!(*confidence, KnowledgeConfidence::KnowsDetails);
|
||||
assert_eq!(*age_ticks, 50); // tick 100 - last_observed 50
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_confidence_not_shown_as_remembered() {
|
||||
let mut world = setup_world(32, 32);
|
||||
let mut registry = EntityRegistry::new(0);
|
||||
|
||||
// NPC exists but not in LOS
|
||||
let npc = world
|
||||
.spawn((crate::npc::Npc, TilePosition::new(16, 10, 0)))
|
||||
.id();
|
||||
let npc_sid = registry.register(npc);
|
||||
|
||||
// Knowledge still shows Direct (transient inconsistency)
|
||||
let mut kg = KnowledgeGraph::new();
|
||||
kg.observe_entity(npc_sid, TilePosition::new(16, 14, 0), 50);
|
||||
// Still Direct — don't show as ghost
|
||||
|
||||
let player = world
|
||||
.spawn((
|
||||
PlayerCharacter,
|
||||
TilePosition::new(16, 16, 0),
|
||||
Facing(FacingDirection::North),
|
||||
kg,
|
||||
))
|
||||
.id();
|
||||
registry.register(player);
|
||||
|
||||
// Wall blocks actual NPC position
|
||||
let mut walkability = world.resource_mut::<WalkabilityMap>();
|
||||
walkability.set_walkable(&TilePosition::new(16, 12, 0), false);
|
||||
|
||||
world.insert_resource(registry);
|
||||
|
||||
let mut schedule = bevy_ecs::schedule::Schedule::default();
|
||||
schedule.add_systems(compute_observer_snapshot);
|
||||
schedule.run(&mut world);
|
||||
|
||||
let buffer = world.resource::<SnapshotBuffer>();
|
||||
let snapshot = buffer.snapshot.as_ref().unwrap();
|
||||
|
||||
let remembered: Vec<_> = snapshot
|
||||
.entities
|
||||
.iter()
|
||||
.filter(|e| matches!(e.observation, EntityVisibility::Remembered { .. }))
|
||||
.collect();
|
||||
assert!(
|
||||
remembered.is_empty(),
|
||||
"Direct-confidence entities should not appear as remembered ghosts"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ fn snapshot_roundtrip_over_unix_socket() {
|
||||
z: 0,
|
||||
kind: EntityKind::Npc,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
}],
|
||||
visible_tiles: vec![],
|
||||
};
|
||||
|
||||
@@ -35,6 +35,8 @@ fn snapshot_roundtrip_over_tcp() {
|
||||
z: 0,
|
||||
kind: EntityKind::Npc,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
}],
|
||||
visible_tiles: vec![],
|
||||
};
|
||||
|
||||
@@ -45,6 +45,8 @@ fn generate_msgpack_fixtures() {
|
||||
z: 0,
|
||||
kind: EntityKind::Npc,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
}],
|
||||
);
|
||||
write_fixture(
|
||||
@@ -86,6 +88,8 @@ fn generate_msgpack_fixtures() {
|
||||
z: 0,
|
||||
kind: EntityKind::Player,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
}],
|
||||
);
|
||||
write_fixture(
|
||||
@@ -104,6 +108,8 @@ fn generate_msgpack_fixtures() {
|
||||
z: 0,
|
||||
kind: EntityKind::Player,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Known,
|
||||
observation: EntityVisibility::Visible,
|
||||
},
|
||||
VisibleEntity {
|
||||
entity_id: 2,
|
||||
@@ -112,6 +118,8 @@ fn generate_msgpack_fixtures() {
|
||||
z: 0,
|
||||
kind: EntityKind::Npc,
|
||||
visibility: VisibilitySector::Peripheral,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
},
|
||||
VisibleEntity {
|
||||
entity_id: 3,
|
||||
@@ -120,6 +128,8 @@ fn generate_msgpack_fixtures() {
|
||||
z: 1,
|
||||
kind: EntityKind::Object,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
},
|
||||
VisibleEntity {
|
||||
entity_id: 4,
|
||||
@@ -128,6 +138,8 @@ fn generate_msgpack_fixtures() {
|
||||
z: -1,
|
||||
kind: EntityKind::Terrain,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
},
|
||||
],
|
||||
);
|
||||
@@ -154,6 +166,8 @@ fn generate_msgpack_fixtures() {
|
||||
z: 0,
|
||||
kind: EntityKind::Player,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
}],
|
||||
visible_tiles: vec![
|
||||
VisibleTile {
|
||||
|
||||
@@ -32,6 +32,8 @@ fn observer_snapshot_roundtrip() {
|
||||
z: 0,
|
||||
kind: EntityKind::Npc,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
}],
|
||||
);
|
||||
|
||||
@@ -158,6 +160,8 @@ fn all_entity_kind_variants_roundtrip() {
|
||||
z: 0,
|
||||
kind,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
};
|
||||
let snapshot = test_snapshot(0, vec![entity]);
|
||||
let bytes = rmp_serde::to_vec_named(&snapshot).expect("serialize");
|
||||
@@ -190,6 +194,8 @@ fn snapshot_v2_fields_roundtrip() {
|
||||
z: 0,
|
||||
kind: EntityKind::Player,
|
||||
visibility: VisibilitySector::Forward,
|
||||
relationship: RelationshipState::Unknown,
|
||||
observation: EntityVisibility::Visible,
|
||||
}],
|
||||
visible_tiles: vec![
|
||||
VisibleTile {
|
||||
|
||||
Reference in New Issue
Block a user