diff --git a/Entity-Management.md b/Entity-Management.md index b53de1f..f67eca7 100644 --- a/Entity-Management.md +++ b/Entity-Management.md @@ -1,538 +1,405 @@ -# Entity Management - -Entities are game objects that implement behavior and live on Grids. While Grids handle rendering and mediate interactions, Entities encapsulate game logic like movement, combat, and AI. - -## Quick Reference - -**Parent System:** [[Grid-System]] - -**Key Types:** -- `mcrfpy.Entity` - Game entities on grids -- `mcrfpy.Grid` - Spatial container for entities -- `mcrfpy.EntityCollection` - Collection of entities on a grid - -**Key Files:** -- `src/UIEntity.h` / `src/UIEntity.cpp` -- `src/UIEntityCollection.h` / `.cpp` -- `src/SpatialHash.h` / `src/SpatialHash.cpp` - Spatial indexing - -**Related Issues:** -- [#115](../issues/115) - SpatialHash for fast queries ✅ Implemented -- [#117](../issues/117) - Memory Pool for entities (Deferred) -- [#159](../issues/159) - EntityCollection iterator optimization ✅ Fixed - ---- - -## What Are Entities? - -Entities are game objects that: -- **Live on a Grid** (0 or 1 grid at a time) -- **Have a sprite** for visual rendering -- **Have grid position** (integer cell coordinates) -- **Implement behavior** (movement, AI, combat, inventory) -- **Track visibility** (which cells they can see / have seen) - -**Key distinction:** Entities implement behavior. Grids mediate interaction between entities and render them to screen. - ---- - -## Entity-Grid Relationship - -The Entity-Grid relationship mirrors the UIDrawable parent-child pattern: - -| Relationship | Property | Automatic Behavior | -|--------------|----------|-------------------| -| Entity → Grid | `entity.grid` | Set when added to `grid.entities` | -| Grid → Entities | `grid.entities` | Collection of all entities on grid | - -```python -import mcrfpy - -# Create grid and entity -grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(400, 400)) -player = mcrfpy.Entity(pos=(10, 10), sprite_index=0) - -# Before adding: entity has no grid -print(player.grid) # None - -# Add to grid -grid.entities.append(player) - -# After adding: bidirectional link established -print(player.grid == grid) # True -print(player in grid.entities) # True - -# Removing breaks the link -grid.entities.remove(player) -print(player.grid) # None -``` - -**Important:** An entity can only be on 0 or 1 grids at a time. Adding to a new grid automatically removes from the old one. - ---- - -## Entity Properties - -### Position - -```python -# Grid coordinates (integer cells) -entity.x = 15 -entity.y = 20 -entity.pos = (15, 20) # Tuple form - -# Draw position (float, for animation interpolation) -print(entity.draw_pos) # Actual render position -``` - -### Sprite - -```python -entity.sprite_index = 5 # Index in texture sprite sheet -``` - -### Visibility - -```python -entity.visible = True -entity.opacity = 0.8 # 0.0 to 1.0 -``` - -### Grid Reference - -```python -current_grid = entity.grid # Read-only, set by collection operations -``` - ---- - -## Spatial Queries with SpatialHash - -As of commit 7d57ce2, grids use **SpatialHash** for efficient spatial queries. This provides O(k) query time where k is the number of nearby entities, instead of O(n) scanning all entities. - -### entities_in_radius() - -```python -# Query entities within a radius (uses SpatialHash internally) -nearby = grid.entities_in_radius(x, y, radius) - -# Example: Find all entities within 10 cells of position (50, 50) -threats = grid.entities_in_radius(50, 50, 10) -for entity in threats: - print(f"Entity at ({entity.x}, {entity.y})") -``` - -### Performance Comparison - -| Entity Count | O(n) Query | SpatialHash | Speedup | -|--------------|------------|-------------|---------| -| 100 | 0.037ms | 0.008ms | 4.6× | -| 500 | 0.061ms | 0.009ms | 7.2× | -| 1,000 | 0.028ms | 0.004ms | 7.8× | -| 2,000 | 0.043ms | 0.003ms | 13× | -| 5,000 | 0.109ms | 0.003ms | **37×** | - -### N×N Visibility (AI "What can everyone see?") - -| Entity Count | O(n) approach | SpatialHash | Speedup | -|--------------|---------------|-------------|---------| -| 1,000 | 21ms | 1ms | 35× | -| 2,000 | 85ms | 1ms | 87× | -| 5,000 | 431ms | 2ms | **217×** | - -### When to Use Which Method - -| Use Case | Method | Complexity | -|----------|--------|------------| -| Nearby entities (AI, combat) | `grid.entities_in_radius(x, y, r)` | O(k) | -| FOV-based visibility | `entity.visible_entities()` | O(n) + FOV | -| All entities iteration | `for e in grid.entities` | O(n) | -| Single cell lookup | `grid.at(x, y).entities` | O(n) filter | - ---- - -## Field of View & Visibility - -Entities track what they can see via `gridstate` - a per-cell record of visible and discovered states. - -### FOV Configuration - -```python -# Grid-level FOV settings -grid.fov = mcrfpy.FOV.SHADOW # Algorithm (BASIC, DIAMOND, SHADOW, etc.) -grid.fov_radius = 10 # Default view radius - -# Module-level default -mcrfpy.default_fov = mcrfpy.FOV.PERMISSIVE_2 -``` - -### Updating Visibility - -```python -# Compute FOV from entity's position and update gridstate -entity.update_visibility() - -# This also updates any ColorLayers bound via apply_perspective() -``` - -### Querying Visible Entities - -```python -# Get list of other entities this entity can see (uses FOV + line-of-sight) -visible_enemies = entity.visible_entities() - -# With custom FOV settings -nearby = entity.visible_entities(radius=5) -visible = entity.visible_entities(fov=mcrfpy.FOV.BASIC, radius=8) -``` - -**Note:** `visible_entities()` checks FOV and line-of-sight. For pure distance queries without FOV, use `grid.entities_in_radius()`. - -### Fog of War with ColorLayers - -```python -# Create a ColorLayer for fog of war -fov_layer = grid.add_layer('color', z_index=-1) -fov_layer.fill((0, 0, 0, 255)) # Start black (unknown) - -# Bind to entity - layer auto-updates when entity.update_visibility() is called -fov_layer.apply_perspective( - entity=player, - visible=(0, 0, 0, 0), # Transparent when visible - discovered=(40, 40, 60, 180), # Dark overlay when discovered - unknown=(0, 0, 0, 255) # Black when never seen -) - -# Now whenever player moves: -player.x = new_x -player.y = new_y -player.update_visibility() # Automatically updates the fog layer -``` - -### One-Time FOV Draw - -```python -# Draw FOV without binding (useful for previews, spell ranges, etc.) -fov_layer.draw_fov( - source=(player.x, player.y), - radius=10, - fov=mcrfpy.FOV.SHADOW, - visible=(255, 255, 200, 64), - discovered=(100, 100, 100, 128), - unknown=(0, 0, 0, 255) -) -``` - -### Gridstate Access - -```python -# Entity's per-cell visibility memory -for state in entity.gridstate: - print(f"visible={state.visible}, discovered={state.discovered}") - -# Access specific cell state -state = entity.at((x, y)) -if state.visible: - print("Entity can currently see this cell") -elif state.discovered: - print("Entity has seen this cell before") -``` - -### GridPointState.point - Accessing Cell Data (#16) - -The `GridPointState.point` property provides access to the underlying `GridPoint` from an entity's perspective: - -```python -state = entity.at((x, y)) - -# If entity has NOT discovered this cell, point is None -if not state.discovered: - print(state.point) # None - entity doesn't know what's here - -# If entity HAS discovered the cell, point gives access to GridPoint -if state.discovered: - point = state.point # Live reference to GridPoint - print(f"walkable: {point.walkable}") - print(f"transparent: {point.transparent}") - print(f"entities here: {point.entities}") # List of entities at cell -``` - -**Key behaviors:** -- Returns `None` if `discovered=False` (entity has never seen this cell) -- Returns live `GridPoint` reference if `discovered=True` -- Changes to the `GridPoint` are immediately visible through `state.point` -- This is intentionally **not** a cached copy - for historical memory, implement your own system in Python - ---- - -## EntityCollection - -`grid.entities` is an `EntityCollection` with list-like operations: - -```python -# Add entities -grid.entities.append(entity) -grid.entities.extend([entity1, entity2, entity3]) -grid.entities.insert(0, entity) # Insert at index - -# Remove entities -grid.entities.remove(entity) -entity = grid.entities.pop() # Remove and return last -entity = grid.entities.pop(0) # Remove and return at index - -# Query -count = len(grid.entities) -idx = grid.entities.index(entity) -n = grid.entities.count(entity) -found = grid.entities.find("entity_name") # Find by name - -# Iteration (O(n) - optimized in #159) -for entity in grid.entities: - print(entity.pos) -``` - -### Iterator Performance (#159) - -EntityCollection iteration was optimized in commit 8f2407b: -- **Before:** O(n²) due to index-based list traversal -- **After:** O(n) using proper list iterators -- **Speedup:** 103× at 2,000 entities - ---- - -## Entity Lifecycle - -### Creation - -```python -# Basic creation -entity = mcrfpy.Entity(pos=(10, 10), sprite_index=0) - -# With name for later lookup -entity = mcrfpy.Entity(pos=(10, 10), sprite_index=0, name="player") -``` - -### Adding to Grid - -```python -grid.entities.append(entity) -# entity.grid is now set to grid -# Entity is automatically added to SpatialHash for fast queries -``` - -### Movement - -```python -# Direct position change (automatically updates SpatialHash) -entity.pos = (new_x, new_y) - -# Animated movement -mcrfpy.Animation("x", target_x, 0.3, "easeOutQuad").start(entity) -mcrfpy.Animation("y", target_y, 0.3, "easeOutQuad").start(entity) - -# Update visibility after movement -entity.update_visibility() -``` - -### Removal - -```python -# Method 1: Remove from collection -grid.entities.remove(entity) - -# Method 2: Entity.die() - removes from parent grid and SpatialHash -entity.die() - -# After removal: entity.grid is None -``` - -### Transfer Between Grids - -```python -def transfer_entity(entity, to_grid, new_pos): - """Move entity to a different grid.""" - entity.die() # Remove from current grid - entity.pos = new_pos - to_grid.entities.append(entity) -``` - ---- - -## Common Patterns - -### Player Entity with FOV - -```python -class Player: - def __init__(self, grid, start_pos): - self.entity = mcrfpy.Entity(pos=start_pos, sprite_index=0, name="player") - grid.entities.append(self.entity) - - # Set up fog of war - self.fov_layer = grid.add_layer('color', z_index=-1) - self.fov_layer.fill((0, 0, 0, 255)) - self.fov_layer.apply_perspective( - entity=self.entity, - visible=(0, 0, 0, 0), - discovered=(30, 30, 50, 180), - unknown=(0, 0, 0, 255) - ) - self.entity.update_visibility() - - def move(self, dx, dy): - new_x = self.entity.x + dx - new_y = self.entity.y + dy - - point = self.entity.grid.at(new_x, new_y) - if point and point.walkable: - self.entity.pos = (new_x, new_y) - self.entity.update_visibility() # Update FOV after move - return True - return False - - def get_visible_enemies(self): - """Get enemies this player can currently see.""" - return [e for e in self.entity.visible_entities() - if e.name and e.name.startswith("enemy")] -``` - -### Enemy AI with SpatialHash - -```python -class Enemy: - def __init__(self, grid, pos, aggro_range=10): - self.entity = mcrfpy.Entity(pos=pos, sprite_index=1, name="enemy") - self.aggro_range = aggro_range - self.health = 100 - self.grid = grid - grid.entities.append(self.entity) - - def update(self): - # Use SpatialHash for efficient nearby entity detection - nearby = self.grid.entities_in_radius( - self.entity.x, self.entity.y, self.aggro_range - ) - - # Find player in nearby entities - player = None - for e in nearby: - if e.name == "player": - player = e - break - - if player: - self.chase((player.x, player.y)) - else: - self.wander() - - def chase(self, target): - # Use pathfinding - path = self.entity.grid.find_path( - self.entity.x, self.entity.y, target[0], target[1] - ) - if path and len(path) > 1: - next_cell = path[1] # path[0] is current position - self.entity.pos = next_cell - - def wander(self): - import random - dx = random.choice([-1, 0, 1]) - dy = random.choice([-1, 0, 1]) - - new_pos = (self.entity.x + dx, self.entity.y + dy) - point = self.entity.grid.at(*new_pos) - if point and point.walkable: - self.entity.pos = new_pos -``` - -### Efficient Multi-Entity AI Loop - -```python -def update_all_enemies(grid, enemies): - """Update all enemies efficiently using SpatialHash.""" - for enemy in enemies: - # Each query is O(k) not O(n) - nearby = grid.entities_in_radius(enemy.x, enemy.y, enemy.aggro_range) - enemy.react_to_nearby(nearby) -``` - -### Item Entity - -```python -class Item: - def __init__(self, grid, pos, item_type): - self.entity = mcrfpy.Entity(pos=pos, sprite_index=10 + item_type) - self.item_type = item_type - grid.entities.append(self.entity) - - def pickup(self, collector): - """Called when another entity picks up this item.""" - collector.inventory.append(self.item_type) - self.entity.die() # Remove from grid -``` - -For more interaction patterns (click handling, selection, context menus), see [[Grid-Interaction-Patterns]]. - ---- - -## Pathfinding - -Entities have built-in pathfinding via libtcod: - -```python -# A* pathfinding to target (via Grid) -path = grid.find_path(entity.x, entity.y, target_x, target_y) -# Returns list of (x, y) tuples, or empty if no path - -if path: - next_step = path[1] # path[0] is current position - entity.pos = next_step - -# Dijkstra for multi-target pathfinding -grid.compute_dijkstra(goal_x, goal_y) -distance = grid.get_dijkstra_distance(entity.x, entity.y) -path = grid.get_dijkstra_path(entity.x, entity.y) -``` - -Pathfinding respects `GridPoint.walkable` properties set on the grid. - ---- - -## Performance Considerations - -### Current Performance (as of 2025-12-28) - -| Operation | Performance | Notes | -|-----------|-------------|-------| -| Entity Creation | ~90,000/sec | Sufficient for level generation | -| Iteration | ~9M reads/sec | Optimized iterators (#159) | -| Spatial Query | 0.003ms | SpatialHash O(k) (#115) | -| N×N Visibility (5000) | 2ms | 217× faster than O(n) | - -### Recommendations - -1. **Use `entities_in_radius()` for AI** - O(k) queries instead of iterating all entities -2. **Batch visibility updates** - Call `update_visibility()` once after all moves -3. **Use timer callbacks for AI** - Don't run expensive logic every frame -4. **Entity counts up to 5,000+** - SpatialHash makes large counts feasible - -### Internal Architecture - -- **SpatialHash:** Bucket-based spatial indexing (32-cell buckets) -- **Automatic updates:** Hash updates on entity add/remove/move -- **Weak references:** Hash doesn't prevent entity garbage collection - -See [[Performance-and-Profiling]] for detailed optimization guidance. - ---- - -## Related Systems - -- [[Grid-System]] - Spatial container for entities -- [[Grid-Interaction-Patterns]] - Click handling, selection, context menus -- [[Animation-System]] - Smooth entity movement -- [[Performance-and-Profiling]] - Entity performance metrics - ---- - -*Last updated: 2025-12-28* \ No newline at end of file +# Entity Management + +Entities are game objects that implement behavior and live on Grids. While Grids handle rendering and mediate interactions, Entities encapsulate game logic like movement, combat, and AI. + +## Quick Reference + +**Parent System:** [[Grid-System]] + +**Key Types:** +- `mcrfpy.Entity` - Game entities on grids +- `mcrfpy.Grid` - Spatial container for entities +- `mcrfpy.EntityCollection` - Collection of entities on a grid + +**Key Files:** +- `src/UIEntity.h` / `src/UIEntity.cpp` +- `src/UIEntityCollection.h` / `.cpp` +- `src/SpatialHash.h` / `src/SpatialHash.cpp` - Spatial indexing + +**Related Issues:** +- [#115](../issues/115) - SpatialHash for fast queries - Implemented +- [#117](../issues/117) - Memory Pool for entities (Deferred) +- [#159](../issues/159) - EntityCollection iterator optimization - Fixed + +--- + +## Creating Entities + +```python +import mcrfpy + +# Basic creation with keyword arguments +entity = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=0) + +# With name for lookup +player = mcrfpy.Entity(grid_pos=(5, 5), sprite_index=0, name="player") + +# Default (origin, no sprite) +e = mcrfpy.Entity() # grid_pos=(0, 0), sprite_index=0 +``` + +### Entity Properties + +| Property | Type | Description | +|----------|------|-------------| +| `grid_x`, `grid_y` | float | Grid cell position | +| `draw_x`, `draw_y` | float | Visual draw position (for animation) | +| `sprite_index` | int | Index in texture sprite sheet | +| `sprite_scale` | float | Scale of the entity sprite | +| `name` | str | Entity name for lookup | +| `visible` | bool | Whether entity is rendered | +| `grid` | Grid or None | Parent grid (read-only, set by collection) | + +--- + +## Entity-Grid Relationship + +```python +grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(400, 400)) +player = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=0, name="player") + +# Before adding: entity has no grid +print(player.grid) # None + +# Add to grid +grid.entities.append(player) + +# After adding: bidirectional link established +print(player.grid is not None) # True +print(player in grid.entities) # True + +# Removing breaks the link +grid.entities.remove(player) +print(player.grid) # None +``` + +**Important:** An entity can only be on 0 or 1 grids at a time. Adding to a new grid automatically removes from the old one. + +--- + +## Movement + +```python +# Direct position change (updates SpatialHash automatically) +player.grid_x = 15 +player.grid_y = 20 + +# Animated movement (smooth visual transition) +player.animate("x", 15.0, 0.3, mcrfpy.Easing.EASE_OUT_QUAD) +player.animate("y", 20.0, 0.3, mcrfpy.Easing.EASE_OUT_QUAD) + +# Move with callback on completion +def on_move_complete(target, prop, value): + target.grid.compute_fov((int(target.grid_x), int(target.grid_y)), radius=8) + +player.animate("x", 15.0, 0.3, mcrfpy.Easing.EASE_OUT_QUAD, callback=on_move_complete) +``` + +### Animatable Entity Properties + +| Property | Type | Notes | +|----------|------|-------| +| `x`, `y` | float | Alias for draw position | +| `draw_x`, `draw_y` | float | Visual position in tile coords | +| `sprite_index` | int | Can animate through sprite frames | +| `sprite_scale` | float | Scale animation | + +--- + +## Spatial Queries with SpatialHash + +Grids use SpatialHash for efficient spatial queries with O(k) time complexity: + +### entities_in_radius() + +```python +# Query entities within a radius +nearby = grid.entities_in_radius((10, 10), 5.0) + +for entity in nearby: + print(f"{entity.name} at ({entity.grid_x}, {entity.grid_y})") +``` + +**Note:** The first argument is a `(x, y)` tuple, not separate x and y arguments. + +### Performance + +| Entity Count | Linear Scan | SpatialHash | Speedup | +|--------------|-------------|-------------|---------| +| 100 | 0.037ms | 0.008ms | 4.6x | +| 1,000 | 0.028ms | 0.004ms | 7.8x | +| 5,000 | 0.109ms | 0.003ms | **37x** | + +For N x N visibility checks (e.g., "what can everyone see?"): + +| Entity Count | Linear | SpatialHash | Speedup | +|--------------|--------|-------------|---------| +| 1,000 | 21ms | 1ms | 35x | +| 5,000 | 431ms | 2ms | **217x** | + +--- + +## EntityCollection + +`grid.entities` is an `EntityCollection` with list-like operations: + +```python +# Add entities +grid.entities.append(entity) +grid.entities.extend([entity1, entity2, entity3]) + +# Remove entities +grid.entities.remove(entity) + +# Query +count = len(grid.entities) +idx = grid.entities.index(entity) + +# Iteration (O(n) - optimized in #159) +for entity in grid.entities: + print(f"{entity.name}: ({entity.grid_x}, {entity.grid_y})") +``` + +--- + +## Entity Lifecycle + +### Creation and Placement + +```python +player = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=0, name="player") +grid.entities.append(player) +# player.grid is now set +# Entity is added to SpatialHash for fast queries +``` + +### Removal + +```python +# Method 1: Remove from collection +grid.entities.remove(player) + +# Method 2: Entity.die() - removes from parent grid and SpatialHash +player.die() + +# After removal: player.grid is None +``` + +### Transfer Between Grids + +```python +def transfer_entity(entity, to_grid, new_pos): + entity.die() # Remove from current grid + entity.grid_x = new_pos[0] + entity.grid_y = new_pos[1] + to_grid.entities.append(entity) +``` + +--- + +## FOV and Visibility + +### Computing FOV + +```python +# Set up transparent cells +for x in range(50): + for y in range(50): + grid.at(x, y).transparent = True + +# Mark walls +grid.at(5, 5).transparent = False + +# Compute FOV from entity position +grid.compute_fov((int(player.grid_x), int(player.grid_y)), radius=10) + +# Check if a cell is visible +if grid.is_in_fov((12, 14)): + print("Can see that cell!") +``` + +### Fog of War with ColorLayer + +```python +# Create fog overlay +fog = mcrfpy.ColorLayer(name="fog", z_index=1) +grid.add_layer(fog) + +# Initialize to fully dark +fog.fill(mcrfpy.Color(0, 0, 0, 255)) + +# Update fog based on FOV after each move +def update_fog(player, fog_layer, grid): + grid.compute_fov((int(player.grid_x), int(player.grid_y)), radius=8) + for x in range(grid.grid_w): + for y in range(grid.grid_h): + if grid.is_in_fov((x, y)): + fog_layer.set((x, y), mcrfpy.Color(0, 0, 0, 0)) # Clear + # Previously seen cells stay semi-transparent (don't re-darken) +``` + +### Perspective System + +```python +# Set perspective entity (enables FOV-aware rendering) +grid.perspective = player + +# Remove perspective (omniscient view) +grid.perspective = None +``` + +--- + +## Common Patterns + +### Player Entity with Movement + +```python +class Player: + def __init__(self, grid, start_pos): + self.entity = mcrfpy.Entity( + grid_pos=start_pos, sprite_index=0, name="player" + ) + grid.entities.append(self.entity) + + def move(self, dx, dy): + new_x = int(self.entity.grid_x + dx) + new_y = int(self.entity.grid_y + dy) + + point = self.entity.grid.at(new_x, new_y) + if point and point.walkable: + self.entity.animate("x", float(new_x), 0.15, mcrfpy.Easing.EASE_OUT_QUAD) + self.entity.animate("y", float(new_y), 0.15, mcrfpy.Easing.EASE_OUT_QUAD) + self.entity.grid_x = new_x + self.entity.grid_y = new_y + return True + return False +``` + +### Enemy AI with SpatialHash + +```python +class Enemy: + def __init__(self, grid, pos, aggro_range=10): + self.entity = mcrfpy.Entity( + grid_pos=pos, sprite_index=1, name="enemy" + ) + self.aggro_range = aggro_range + grid.entities.append(self.entity) + + def update(self): + grid = self.entity.grid + # Use SpatialHash for efficient nearby entity detection + nearby = grid.entities_in_radius( + (self.entity.grid_x, self.entity.grid_y), + self.aggro_range + ) + + # Find player in nearby entities + player = None + for e in nearby: + if e.name == "player": + player = e + break + + if player: + self.chase(player) + else: + self.wander() + + def chase(self, target): + grid = self.entity.grid + path = grid.find_path( + (int(self.entity.grid_x), int(self.entity.grid_y)), + (int(target.grid_x), int(target.grid_y)) + ) + if path and len(path) > 0: + next_step = path.walk() + self.entity.grid_x = next_step.x + self.entity.grid_y = next_step.y + + def wander(self): + import random + dx = random.choice([-1, 0, 1]) + dy = random.choice([-1, 0, 1]) + new_x = int(self.entity.grid_x + dx) + new_y = int(self.entity.grid_y + dy) + point = self.entity.grid.at(new_x, new_y) + if point and point.walkable: + self.entity.grid_x = new_x + self.entity.grid_y = new_y +``` + +### Item Pickup + +```python +class Item: + def __init__(self, grid, pos, item_type): + self.entity = mcrfpy.Entity( + grid_pos=pos, sprite_index=10 + item_type, name=f"item_{item_type}" + ) + self.item_type = item_type + grid.entities.append(self.entity) + + def pickup(self, collector_inventory): + collector_inventory.append(self.item_type) + self.entity.die() # Remove from grid +``` + +--- + +## Pathfinding + +Entities use the grid's pathfinding capabilities: + +```python +# A* pathfinding +path = grid.find_path( + (int(entity.grid_x), int(entity.grid_y)), + (target_x, target_y) +) + +if path and len(path) > 0: + next_step = path.walk() # Get next step as Vector + entity.grid_x = next_step.x + entity.grid_y = next_step.y + +# Dijkstra for multi-target pathfinding +dm = grid.get_dijkstra_map((goal_x, goal_y)) +distance = dm.distance((entity.grid_x, entity.grid_y)) +next_step = dm.step_from((int(entity.grid_x), int(entity.grid_y))) +``` + +Pathfinding respects `GridPoint.walkable` properties. + +--- + +## Performance Considerations + +| Operation | Performance | Notes | +|-----------|-------------|-------| +| Entity Creation | ~90,000/sec | Sufficient for level generation | +| Iteration | ~9M reads/sec | Optimized iterators (#159) | +| Spatial Query | 0.003ms | SpatialHash O(k) (#115) | +| N x N Visibility (5000) | 2ms | 217x faster than O(n) | + +### Recommendations + +1. **Use `entities_in_radius()` for AI** - O(k) queries instead of iterating all entities +2. **Batch visibility updates** - Compute FOV once after all moves, not per-move +3. **Use Timer for AI** - Don't run expensive logic every frame +4. **Entity counts up to 5,000+** - SpatialHash makes large counts feasible + +--- + +## Related Systems + +- [[Grid-System]] - Spatial container for entities +- [[Grid-Interaction-Patterns]] - Click handling, selection, context menus +- [[Animation-System]] - Smooth entity movement via `.animate()` +- [[AI-and-Pathfinding]] - FOV, pathfinding, AI patterns +- [[Input-and-Events]] - Callback signatures for mouse events + +--- + +*Last updated: 2026-02-07* \ No newline at end of file