Update Grid TCOD Integration

John McCardle 2026-02-07 23:48:48 +00:00
commit 449fd3bc63

@ -1,453 +1,453 @@
# Grid TCOD Integration # Grid TCOD Integration
## Overview ## Overview
McRogueFace integrates with libtcod for FOV (field of view), A* pathfinding, and Dijkstra maps. The integration automatically synchronizes each grid's walkability and transparency properties with an internal `TCODMap`. McRogueFace integrates with libtcod for FOV (field of view), A* pathfinding, and Dijkstra maps. The integration automatically synchronizes each grid's walkability and transparency properties with an internal `TCODMap`.
**Parent Page:** [[Grid-System]] **Parent Page:** [[Grid-System]]
**Related Pages:** **Related Pages:**
- [[AI-and-Pathfinding]] - Using FOV and pathfinding for game AI - [[AI-and-Pathfinding]] - Using FOV and pathfinding for game AI
- [[Grid-Rendering-Pipeline]] - How FOV affects rendering overlays - [[Grid-Rendering-Pipeline]] - How FOV affects rendering overlays
- [[Entity-Management]] - Entity perspective and gridstate - [[Entity-Management]] - Entity perspective and gridstate
**Key Files:** **Key Files:**
- `src/UIGrid.cpp` - TCODMap synchronization, FOV, pathfinding - `src/UIGrid.cpp` - TCODMap synchronization, FOV, pathfinding
- `src/UIGrid.h` - TCODMap, TCODPath, TCODDijkstra members - `src/UIGrid.h` - TCODMap, TCODPath, TCODDijkstra members
--- ---
## The World State Layer ## The World State Layer
### Cell Properties as World Physics ### Cell Properties as World Physics
Each grid cell (GridPoint) has properties that drive TCOD algorithms: Each grid cell (GridPoint) has properties that drive TCOD algorithms:
``` ```
Visual Layer (ColorLayer/TileLayer) - What's displayed (colors, sprites) Visual Layer (ColorLayer/TileLayer) - What's displayed (colors, sprites)
| |
World State Layer (GridPoint) - Physical properties (walkable, transparent) World State Layer (GridPoint) - Physical properties (walkable, transparent)
| |
Perspective Layer - Per-entity knowledge (FOV results) Perspective Layer - Per-entity knowledge (FOV results)
``` ```
```python ```python
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600)) grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600))
cell = grid.at(10, 10) cell = grid.at(10, 10)
cell.walkable = True # Affects pathfinding cell.walkable = True # Affects pathfinding
cell.transparent = True # Affects FOV cell.transparent = True # Affects FOV
cell.tilesprite = 0 # Visual tile index (legacy) cell.tilesprite = 0 # Visual tile index (legacy)
``` ```
**Automatic Synchronization:** When you set `cell.walkable` or `cell.transparent`, the internal TCODMap is automatically updated. There is no manual sync step required. **Automatic Synchronization:** When you set `cell.walkable` or `cell.transparent`, the internal TCODMap is automatically updated. There is no manual sync step required.
--- ---
## Field of View (FOV) ## Field of View (FOV)
### Computing FOV ### Computing FOV
FOV determines which cells are visible from a given position: FOV determines which cells are visible from a given position:
```python ```python
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600)) grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600))
# Make all cells transparent # Make all cells transparent
for x in range(50): for x in range(50):
for y in range(50): for y in range(50):
grid.at(x, y).transparent = True grid.at(x, y).transparent = True
grid.at(x, y).walkable = True grid.at(x, y).walkable = True
# Add some walls # Add some walls
for x in range(20, 30): for x in range(20, 30):
grid.at(x, 15).transparent = False grid.at(x, 15).transparent = False
grid.at(x, 15).walkable = False grid.at(x, 15).walkable = False
# Compute FOV from position with radius # Compute FOV from position with radius
grid.compute_fov((25, 25), radius=10) grid.compute_fov((25, 25), radius=10)
# Query visibility of specific cells # Query visibility of specific cells
if grid.is_in_fov((25, 25)): if grid.is_in_fov((25, 25)):
print("Origin is visible") print("Origin is visible")
if not grid.is_in_fov((25, 5)): if not grid.is_in_fov((25, 5)):
print("Behind wall is not visible") print("Behind wall is not visible")
``` ```
**API:** **API:**
- `grid.compute_fov((x, y), radius=N)` - Compute FOV from position - `grid.compute_fov((x, y), radius=N)` - Compute FOV from position
- `grid.is_in_fov((x, y))` - Query if cell is currently visible - `grid.is_in_fov((x, y))` - Query if cell is currently visible
### FOV with Fog Overlay ### FOV with Fog Overlay
Use a ColorLayer to visualize FOV: Use a ColorLayer to visualize FOV:
```python ```python
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[]) grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[])
# Create fog overlay above entities # Create fog overlay above entities
fog = mcrfpy.ColorLayer(name="fog", z_index=1) fog = mcrfpy.ColorLayer(name="fog", z_index=1)
grid.add_layer(fog) grid.add_layer(fog)
fog.fill(mcrfpy.Color(0, 0, 0, 255)) # Start fully hidden fog.fill(mcrfpy.Color(0, 0, 0, 255)) # Start fully hidden
# After computing FOV, reveal visible cells # After computing FOV, reveal visible cells
def update_fog(grid, fog, pos, radius=10): def update_fog(grid, fog, pos, radius=10):
grid.compute_fov(pos, radius=radius) grid.compute_fov(pos, radius=radius)
w, h = grid.grid_size w, h = grid.grid_size
for x in range(w): for x in range(w):
for y in range(h): for y in range(h):
if grid.is_in_fov((x, y)): if grid.is_in_fov((x, y)):
fog.set((x, y), mcrfpy.Color(0, 0, 0, 0)) # Visible fog.set((x, y), mcrfpy.Color(0, 0, 0, 0)) # Visible
else: else:
fog.set((x, y), mcrfpy.Color(0, 0, 0, 192)) # Dim fog.set((x, y), mcrfpy.Color(0, 0, 0, 192)) # Dim
update_fog(grid, fog, (25, 25)) update_fog(grid, fog, (25, 25))
``` ```
--- ---
## A* Pathfinding ## A* Pathfinding
### Finding Paths ### Finding Paths
Find the shortest path between two walkable cells: Find the shortest path between two walkable cells:
```python ```python
grid = mcrfpy.Grid(grid_size=(30, 30), pos=(0, 0), size=(400, 400)) grid = mcrfpy.Grid(grid_size=(30, 30), pos=(0, 0), size=(400, 400))
for x in range(30): for x in range(30):
for y in range(30): for y in range(30):
grid.at(x, y).walkable = True grid.at(x, y).walkable = True
# Find path - returns AStarPath object # Find path - returns AStarPath object
path = grid.find_path((5, 5), (25, 25)) path = grid.find_path((5, 5), (25, 25))
if path is not None and len(path) > 0: if path is not None and len(path) > 0:
# Walk the path (consumes next step) # Walk the path (consumes next step)
next_step = path.walk() next_step = path.walk()
print(f"Next step: ({next_step.x}, {next_step.y})") print(f"Next step: ({next_step.x}, {next_step.y})")
# Peek at next step without consuming # Peek at next step without consuming
upcoming = path.peek() upcoming = path.peek()
# Check remaining steps # Check remaining steps
print(f"Remaining: {path.remaining}") print(f"Remaining: {path.remaining}")
# Check endpoints # Check endpoints
print(f"From: {path.origin}") print(f"From: {path.origin}")
print(f"To: {path.destination}") print(f"To: {path.destination}")
``` ```
### AStarPath Object ### AStarPath Object
| Property/Method | Description | | Property/Method | Description |
|----------------|-------------| |----------------|-------------|
| `len(path)` | Total steps in path | | `len(path)` | Total steps in path |
| `path.walk()` | Get and consume next step (returns Vector) | | `path.walk()` | Get and consume next step (returns Vector) |
| `path.peek()` | View next step without consuming | | `path.peek()` | View next step without consuming |
| `path.remaining` | Steps remaining | | `path.remaining` | Steps remaining |
| `path.origin` | Start position (Vector) | | `path.origin` | Start position (Vector) |
| `path.destination` | End position (Vector) | | `path.destination` | End position (Vector) |
### Moving Entities Along Paths ### Moving Entities Along Paths
```python ```python
player = mcrfpy.Entity(grid_pos=(5, 5), sprite_index=0) player = mcrfpy.Entity(grid_pos=(5, 5), sprite_index=0)
grid.entities.append(player) grid.entities.append(player)
# Find path to target # Find path to target
path = grid.find_path( path = grid.find_path(
(int(player.grid_x), int(player.grid_y)), (int(player.grid_x), int(player.grid_y)),
(25, 25) (25, 25)
) )
if path and len(path) > 0: if path and len(path) > 0:
step = path.walk() step = path.walk()
player.grid_x = int(step.x) player.grid_x = int(step.x)
player.grid_y = int(step.y) player.grid_y = int(step.y)
``` ```
--- ---
## Dijkstra Maps ## Dijkstra Maps
### Computing Dijkstra Maps ### Computing Dijkstra Maps
Dijkstra maps compute distances from a goal to all reachable cells. Useful for multi-enemy AI where many entities path toward the same target: Dijkstra maps compute distances from a goal to all reachable cells. Useful for multi-enemy AI where many entities path toward the same target:
```python ```python
grid = mcrfpy.Grid(grid_size=(30, 30), pos=(0, 0), size=(400, 400)) grid = mcrfpy.Grid(grid_size=(30, 30), pos=(0, 0), size=(400, 400))
for x in range(30): for x in range(30):
for y in range(30): for y in range(30):
grid.at(x, y).walkable = True grid.at(x, y).walkable = True
# Create Dijkstra map from goal position # Create Dijkstra map from goal position
dm = grid.get_dijkstra_map((15, 15)) dm = grid.get_dijkstra_map((15, 15))
# Query distance from any cell to goal # Query distance from any cell to goal
d = dm.distance((0, 0)) d = dm.distance((0, 0))
print(f"Distance from (0,0) to goal: {d}") print(f"Distance from (0,0) to goal: {d}")
# Get full path from any cell to goal # Get full path from any cell to goal
path = dm.path_from((0, 0)) path = dm.path_from((0, 0))
print(f"Path length: {len(path)}") print(f"Path length: {len(path)}")
# Get just the next step toward goal # Get just the next step toward goal
next_step = dm.step_from((0, 0)) next_step = dm.step_from((0, 0))
print(f"Next step: ({next_step.x}, {next_step.y})") print(f"Next step: ({next_step.x}, {next_step.y})")
``` ```
### DijkstraMap Object ### DijkstraMap Object
| Method | Description | | Method | Description |
|--------|-------------| |--------|-------------|
| `dm.distance((x, y))` | Distance from cell to goal | | `dm.distance((x, y))` | Distance from cell to goal |
| `dm.path_from((x, y))` | Full path from cell to goal | | `dm.path_from((x, y))` | Full path from cell to goal |
| `dm.step_from((x, y))` | Next step from cell toward goal | | `dm.step_from((x, y))` | Next step from cell toward goal |
### Dijkstra vs A* ### Dijkstra vs A*
| Feature | A* (`find_path`) | Dijkstra (`get_dijkstra_map`) | | Feature | A* (`find_path`) | Dijkstra (`get_dijkstra_map`) |
|---------|-----------------|-------------------------------| |---------|-----------------|-------------------------------|
| **Goals** | Single target | Single target, query from anywhere | | **Goals** | Single target | Single target, query from anywhere |
| **Computation** | One path at a time | One map, unlimited queries | | **Computation** | One path at a time | One map, unlimited queries |
| **Use case** | Single entity, single target | Many entities, same target | | **Use case** | Single entity, single target | Many entities, same target |
| **Performance** | Fast per query | O(n) once, then O(1) per query | | **Performance** | Fast per query | O(n) once, then O(1) per query |
**Rule of thumb:** 1-5 entities -> A* per entity. 10+ entities with same goal -> Dijkstra map. **Rule of thumb:** 1-5 entities -> A* per entity. 10+ entities with same goal -> Dijkstra map.
--- ---
## Entity Perspective System ## Entity Perspective System
### Setting Grid Perspective ### Setting Grid Perspective
```python ```python
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600)) grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600))
player = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=0) player = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=0)
grid.entities.append(player) grid.entities.append(player)
# Assign perspective (property, not method) # Assign perspective (property, not method)
grid.perspective = player grid.perspective = player
# Grid rendering now uses player's FOV for visibility # Grid rendering now uses player's FOV for visibility
grid.compute_fov((int(player.grid_x), int(player.grid_y)), radius=10) grid.compute_fov((int(player.grid_x), int(player.grid_y)), radius=10)
``` ```
### FOV Update on Movement ### FOV Update on Movement
```python ```python
scene = mcrfpy.Scene("game") scene = mcrfpy.Scene("game")
grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[]) grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(800, 600), layers=[])
fog = mcrfpy.ColorLayer(name="fog", z_index=1) fog = mcrfpy.ColorLayer(name="fog", z_index=1)
grid.add_layer(fog) grid.add_layer(fog)
fog.fill(mcrfpy.Color(0, 0, 0, 255)) fog.fill(mcrfpy.Color(0, 0, 0, 255))
for x in range(50): for x in range(50):
for y in range(50): for y in range(50):
grid.at(x, y).transparent = True grid.at(x, y).transparent = True
grid.at(x, y).walkable = True grid.at(x, y).walkable = True
player = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=0) player = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=0)
grid.entities.append(player) grid.entities.append(player)
grid.perspective = player grid.perspective = player
scene.children.append(grid) scene.children.append(grid)
def update_fov(): def update_fov():
"""Call after player moves""" """Call after player moves"""
px, py = int(player.grid_x), int(player.grid_y) px, py = int(player.grid_x), int(player.grid_y)
grid.compute_fov((px, py), radius=10) grid.compute_fov((px, py), radius=10)
w, h = grid.grid_size w, h = grid.grid_size
for x in range(w): for x in range(w):
for y in range(h): for y in range(h):
if grid.is_in_fov((x, y)): if grid.is_in_fov((x, y)):
fog.set((x, y), mcrfpy.Color(0, 0, 0, 0)) fog.set((x, y), mcrfpy.Color(0, 0, 0, 0))
def on_key(key, action): def on_key(key, action):
if action != mcrfpy.InputState.PRESSED: if action != mcrfpy.InputState.PRESSED:
return return
dx, dy = 0, 0 dx, dy = 0, 0
if key == mcrfpy.Key.W: dy = -1 if key == mcrfpy.Key.W: dy = -1
elif key == mcrfpy.Key.S: dy = 1 elif key == mcrfpy.Key.S: dy = 1
elif key == mcrfpy.Key.A: dx = -1 elif key == mcrfpy.Key.A: dx = -1
elif key == mcrfpy.Key.D: dx = 1 elif key == mcrfpy.Key.D: dx = 1
if dx or dy: if dx or dy:
nx = int(player.grid_x) + dx nx = int(player.grid_x) + dx
ny = int(player.grid_y) + dy ny = int(player.grid_y) + dy
if grid.at(nx, ny).walkable: if grid.at(nx, ny).walkable:
player.grid_x = nx player.grid_x = nx
player.grid_y = ny player.grid_y = ny
update_fov() update_fov()
scene.on_key = on_key scene.on_key = on_key
update_fov() # Initial FOV update_fov() # Initial FOV
``` ```
--- ---
## Common Patterns ## Common Patterns
### Opening a Door ### Opening a Door
```python ```python
def open_door(grid, door_x, door_y): def open_door(grid, door_x, door_y):
"""Open door - update world state (auto-syncs to TCOD)""" """Open door - update world state (auto-syncs to TCOD)"""
cell = grid.at(door_x, door_y) cell = grid.at(door_x, door_y)
cell.walkable = True cell.walkable = True
cell.transparent = True cell.transparent = True
cell.tilesprite = 2 # Open door sprite cell.tilesprite = 2 # Open door sprite
# Recompute FOV if player nearby # Recompute FOV if player nearby
px, py = int(player.grid_x), int(player.grid_y) px, py = int(player.grid_x), int(player.grid_y)
grid.compute_fov((px, py), radius=10) grid.compute_fov((px, py), radius=10)
``` ```
### Dynamic Obstacle ### Dynamic Obstacle
```python ```python
def boulder_falls(grid, x, y): def boulder_falls(grid, x, y):
"""Boulder blocks cell""" """Boulder blocks cell"""
cell = grid.at(x, y) cell = grid.at(x, y)
cell.walkable = False cell.walkable = False
cell.transparent = False cell.transparent = False
cell.tilesprite = 3 # Boulder sprite cell.tilesprite = 3 # Boulder sprite
# TCOD map auto-updated - paths through this cell now invalid # TCOD map auto-updated - paths through this cell now invalid
``` ```
### Chase AI with Dijkstra ### Chase AI with Dijkstra
```python ```python
def update_enemies(grid, player, enemies): def update_enemies(grid, player, enemies):
"""Move all enemies toward player using Dijkstra map""" """Move all enemies toward player using Dijkstra map"""
px, py = int(player.grid_x), int(player.grid_y) px, py = int(player.grid_x), int(player.grid_y)
dm = grid.get_dijkstra_map((px, py)) dm = grid.get_dijkstra_map((px, py))
for enemy in enemies: for enemy in enemies:
ex, ey = int(enemy.grid_x), int(enemy.grid_y) ex, ey = int(enemy.grid_x), int(enemy.grid_y)
next_step = dm.step_from((ex, ey)) next_step = dm.step_from((ex, ey))
if next_step is not None: if next_step is not None:
enemy.grid_x = int(next_step.x) enemy.grid_x = int(next_step.x)
enemy.grid_y = int(next_step.y) enemy.grid_y = int(next_step.y)
``` ```
### Spatial Queries ### Spatial Queries
```python ```python
# Find entities near a position # Find entities near a position
nearby = grid.entities_in_radius((int(enemy.grid_x), int(enemy.grid_y)), 5.0) nearby = grid.entities_in_radius((int(enemy.grid_x), int(enemy.grid_y)), 5.0)
for entity in nearby: for entity in nearby:
print(f"Nearby: {entity.name}") print(f"Nearby: {entity.name}")
``` ```
--- ---
## Performance Considerations ## Performance Considerations
### FOV Cost ### FOV Cost
FOV computation time scales with radius and grid size. Only compute when the entity moves: FOV computation time scales with radius and grid size. Only compute when the entity moves:
```python ```python
last_pos = [None] last_pos = [None]
def update_fov_if_moved(): def update_fov_if_moved():
px, py = int(player.grid_x), int(player.grid_y) px, py = int(player.grid_x), int(player.grid_y)
if last_pos[0] != (px, py): if last_pos[0] != (px, py):
grid.compute_fov((px, py), radius=10) grid.compute_fov((px, py), radius=10)
last_pos[0] = (px, py) last_pos[0] = (px, py)
``` ```
### Pathfinding Cost ### Pathfinding Cost
- Limit search distance for distant targets - Limit search distance for distant targets
- Use Dijkstra maps for many entities with same goal - Use Dijkstra maps for many entities with same goal
- Cache paths and recompute only when grid changes - Cache paths and recompute only when grid changes
### Cell Property Changes ### Cell Property Changes
Setting `walkable` or `transparent` auto-syncs to TCOD. For bulk changes, set all properties first, then compute FOV/paths: Setting `walkable` or `transparent` auto-syncs to TCOD. For bulk changes, set all properties first, then compute FOV/paths:
```python ```python
# Set many cells, then compute once # Set many cells, then compute once
for x in range(100): for x in range(100):
for y in range(100): for y in range(100):
grid.at(x, y).walkable = compute_walkable(x, y) grid.at(x, y).walkable = compute_walkable(x, y)
# Single FOV computation after all changes # Single FOV computation after all changes
grid.compute_fov((px, py), radius=10) grid.compute_fov((px, py), radius=10)
``` ```
--- ---
## Troubleshooting ## Troubleshooting
### Issue: Pathfinding Returns None ### Issue: Pathfinding Returns None
**Causes:** **Causes:**
1. Target is unreachable (blocked by walls) 1. Target is unreachable (blocked by walls)
2. Start or end position is non-walkable 2. Start or end position is non-walkable
**Debug:** **Debug:**
```python ```python
path = grid.find_path((x1, y1), (x2, y2)) path = grid.find_path((x1, y1), (x2, y2))
if path is None or len(path) == 0: if path is None or len(path) == 0:
print(f"Start walkable: {grid.at(x1, y1).walkable}") print(f"Start walkable: {grid.at(x1, y1).walkable}")
print(f"End walkable: {grid.at(x2, y2).walkable}") print(f"End walkable: {grid.at(x2, y2).walkable}")
``` ```
### Issue: FOV Doesn't Match Expected ### Issue: FOV Doesn't Match Expected
**Cause:** Cell `transparent` property not set correctly. **Cause:** Cell `transparent` property not set correctly.
**Fix:** Ensure walls have `transparent = False`: **Fix:** Ensure walls have `transparent = False`:
```python ```python
cell = grid.at(x, y) cell = grid.at(x, y)
cell.walkable = False cell.walkable = False
cell.transparent = False # Must set both for walls cell.transparent = False # Must set both for walls
``` ```
### Issue: Entity Can See Through Glass ### Issue: Entity Can See Through Glass
Glass cells should block movement but allow sight: Glass cells should block movement but allow sight:
```python ```python
glass = grid.at(x, y) glass = grid.at(x, y)
glass.walkable = False # Can't walk through glass.walkable = False # Can't walk through
glass.transparent = True # CAN see through glass.transparent = True # CAN see through
``` ```
--- ---
## API Quick Reference ## API Quick Reference
**FOV:** **FOV:**
- `grid.compute_fov((x, y), radius=N)` - Compute FOV from position - `grid.compute_fov((x, y), radius=N)` - Compute FOV from position
- `grid.is_in_fov((x, y))` - Check if cell is visible - `grid.is_in_fov((x, y))` - Check if cell is visible
**A* Pathfinding:** **A* Pathfinding:**
- `grid.find_path((x1, y1), (x2, y2))` - Returns AStarPath object - `grid.find_path((x1, y1), (x2, y2))` - Returns AStarPath object
**Dijkstra Maps:** **Dijkstra Maps:**
- `grid.get_dijkstra_map((x, y))` - Returns DijkstraMap object - `grid.get_dijkstra_map((x, y))` - Returns DijkstraMap object
- `dm.distance((x, y))` - Distance to goal - `dm.distance((x, y))` - Distance to goal
- `dm.path_from((x, y))` - Full path to goal - `dm.path_from((x, y))` - Full path to goal
- `dm.step_from((x, y))` - Next step toward goal - `dm.step_from((x, y))` - Next step toward goal
**Spatial Queries:** **Spatial Queries:**
- `grid.entities_in_radius((x, y), radius)` - Find nearby entities - `grid.entities_in_radius((x, y), radius)` - Find nearby entities
**Perspective:** **Perspective:**
- `grid.perspective = entity` - Set FOV perspective entity - `grid.perspective = entity` - Set FOV perspective entity
**Cell Properties:** **Cell Properties:**
- `cell.walkable` - Bool, affects pathfinding - `cell.walkable` - Bool, affects pathfinding
- `cell.transparent` - Bool, affects FOV - `cell.transparent` - Bool, affects FOV
--- ---
**Navigation:** **Navigation:**
- [[Grid-System]] - Parent page - [[Grid-System]] - Parent page
- [[AI-and-Pathfinding]] - Using FOV and pathfinding for game AI - [[AI-and-Pathfinding]] - Using FOV and pathfinding for game AI
- [[Grid-Rendering-Pipeline]] - FOV overlay rendering - [[Grid-Rendering-Pipeline]] - FOV overlay rendering
- [[Entity-Management]] - Entity gridstate and perspective - [[Entity-Management]] - Entity gridstate and perspective