grid.step() — turn manager for entity behaviors #301

Closed
opened 2026-03-15 00:31:43 +00:00 by john · 1 comment
Owner

Summary

Add a step() method to Grid that processes one round of turn-based entity actions. This is the orchestrator that evaluates behaviors, checks triggers, and calls step() callbacks.

Python API

grid.step()                # process one full round of turns
grid.step(n=5)             # process 5 rounds
grid.step(turn_order=2)    # process only entities with turn_order == 2
grid.step(n=3, turn_order=1)  # 3 rounds of turn_order 1 entities only

Entity turn properties

entity.turn_order = 1     # int, default 1. Lower values go first within a round.
                          # 0 = skip (quick way to exclude default-constructed entities)
entity.move_speed = 0.15  # float seconds — animation duration for movement (visual only)

Turn processing algorithm

grid.step(n=1, turn_order=None):
    for round in range(n):
        collect distinct turn_order values from all entities (ascending, skip 0)
        if turn_order argument given: filter to only that value
        
        for each turn_order value:
            for each entity with this turn_order:
                if entity.behavior is IDLE: skip
                
                # 1. Check TARGET trigger
                if entity.target_label is set:
                    spatial hash query: any target-labeled entities within sight_radius?
                    if candidates exist:
                        compute FOV for entity (bounded to sight_radius)
                        if target-labeled entity is visible:
                            call step(TARGET, spotted_entity)
                            continue to next entity
                
                # 2. Execute behavior's per-turn action
                result = behavior.execute(entity)
                # result is one of: MOVED, DONE, BLOCKED, NO_ACTION
                
                # 3. Handle movement
                if result == MOVED:
                    update entity.cell_pos instantly
                    update spatial hash
                    queue animation: entity.animate("x"/"y", ..., move_speed)
                
                # 4. Handle triggers
                if result == DONE or result == BLOCKED:
                    call entity.step(trigger, data)
                    if no new behavior assigned: revert to default_behavior
        
        # End of round: invalidate Dijkstra caches with collision labels

Turn order as entity sets

Developers can use turn_order values as logical entity groups:

# Player always goes first
player.turn_order = 1

# Fast enemies
for e in fast_enemies:
    e.turn_order = 2

# Slow enemies
for e in slow_enemies:
    e.turn_order = 3

# Process selectively with real-time delays between groups
def process_turns(timer, runtime):
    grid.step(turn_order=1)  # player
    # ... could schedule next group with a short delay timer
    grid.step(turn_order=2)  # fast enemies
    grid.step(turn_order=3)  # slow enemies

Movement animation

When a behavior produces a move, the turn system:

  1. Updates entity.cell_pos instantly (logical move)
  2. Updates spatial hash instantly
  3. Queues animation: entity.animate("x", float(new_cell_x), entity.move_speed) (and y)

The turn system does NOT wait for animations. All logical moves happen instantly; animations are cosmetic. Developers who don't want animations can set entity.move_speed = 0 or simply not use the animation system.

Thread safety

grid.step() should hold the grid's FOV mutex for the entire step to prevent concurrent modification. No other thread should modify entity positions or grid state during a step.

Headless mode

grid.step() works independently of mcrfpy.step(). In headless mode, grid.step() processes turns without requiring the game loop. Animations are queued but not rendered.

Dependencies

  • #295 (cell_pos)
  • #296 (entity labels — for collision and TARGET trigger)
  • #297 (mcrfpy.Behavior enum)
  • #298 (mcrfpy.Trigger enum)
  • #299 (step() callback)
  • #300 (behavior data struct and primitives)

Files likely affected

  • src/UIGrid.h/cpp — add step() method and Python binding
  • src/EntityBehavior.h/cpp — behavior execution logic (called by grid.step)
  • src/UIEntity.h/cppturn_order, move_speed, target_label, sight_radius properties
## Summary Add a `step()` method to Grid that processes one round of turn-based entity actions. This is the orchestrator that evaluates behaviors, checks triggers, and calls `step()` callbacks. ## Python API ```python grid.step() # process one full round of turns grid.step(n=5) # process 5 rounds grid.step(turn_order=2) # process only entities with turn_order == 2 grid.step(n=3, turn_order=1) # 3 rounds of turn_order 1 entities only ``` ### Entity turn properties ```python entity.turn_order = 1 # int, default 1. Lower values go first within a round. # 0 = skip (quick way to exclude default-constructed entities) entity.move_speed = 0.15 # float seconds — animation duration for movement (visual only) ``` ## Turn processing algorithm ``` grid.step(n=1, turn_order=None): for round in range(n): collect distinct turn_order values from all entities (ascending, skip 0) if turn_order argument given: filter to only that value for each turn_order value: for each entity with this turn_order: if entity.behavior is IDLE: skip # 1. Check TARGET trigger if entity.target_label is set: spatial hash query: any target-labeled entities within sight_radius? if candidates exist: compute FOV for entity (bounded to sight_radius) if target-labeled entity is visible: call step(TARGET, spotted_entity) continue to next entity # 2. Execute behavior's per-turn action result = behavior.execute(entity) # result is one of: MOVED, DONE, BLOCKED, NO_ACTION # 3. Handle movement if result == MOVED: update entity.cell_pos instantly update spatial hash queue animation: entity.animate("x"/"y", ..., move_speed) # 4. Handle triggers if result == DONE or result == BLOCKED: call entity.step(trigger, data) if no new behavior assigned: revert to default_behavior # End of round: invalidate Dijkstra caches with collision labels ``` ## Turn order as entity sets Developers can use `turn_order` values as logical entity groups: ```python # Player always goes first player.turn_order = 1 # Fast enemies for e in fast_enemies: e.turn_order = 2 # Slow enemies for e in slow_enemies: e.turn_order = 3 # Process selectively with real-time delays between groups def process_turns(timer, runtime): grid.step(turn_order=1) # player # ... could schedule next group with a short delay timer grid.step(turn_order=2) # fast enemies grid.step(turn_order=3) # slow enemies ``` ## Movement animation When a behavior produces a move, the turn system: 1. Updates `entity.cell_pos` instantly (logical move) 2. Updates spatial hash instantly 3. Queues animation: `entity.animate("x", float(new_cell_x), entity.move_speed)` (and y) The turn system does NOT wait for animations. All logical moves happen instantly; animations are cosmetic. Developers who don't want animations can set `entity.move_speed = 0` or simply not use the animation system. ## Thread safety `grid.step()` should hold the grid's FOV mutex for the entire step to prevent concurrent modification. No other thread should modify entity positions or grid state during a step. ## Headless mode `grid.step()` works independently of `mcrfpy.step()`. In headless mode, `grid.step()` processes turns without requiring the game loop. Animations are queued but not rendered. ## Dependencies - #295 (`cell_pos`) - #296 (entity labels — for collision and TARGET trigger) - #297 (`mcrfpy.Behavior` enum) - #298 (`mcrfpy.Trigger` enum) - #299 (`step()` callback) - #300 (behavior data struct and primitives) ## Files likely affected - `src/UIGrid.h/cpp` — add `step()` method and Python binding - `src/EntityBehavior.h/cpp` — behavior execution logic (called by grid.step) - `src/UIEntity.h/cpp` — `turn_order`, `move_speed`, `target_label`, `sight_radius` properties
Author
Owner

Roadmap context

Part of the Grid & Entity Overhaul Roadmap (docs/GRID_ENTITY_OVERHAUL_ROADMAP.md), Phase 3c (after behavior struct #300 and pathfinding collision #302).

This is the orchestrator — it ties together behaviors, triggers, collision, and FOV into the grid.step() method. The turn_order parameter allows devs to use turn groups as entity sets ("teams", speed categories) with real-time delays between groups.

## Roadmap context Part of the Grid & Entity Overhaul Roadmap (`docs/GRID_ENTITY_OVERHAUL_ROADMAP.md`), **Phase 3c** (after behavior struct #300 and pathfinding collision #302). This is the orchestrator — it ties together behaviors, triggers, collision, and FOV into the `grid.step()` method. The `turn_order` parameter allows devs to use turn groups as entity sets ("teams", speed categories) with real-time delays between groups.
john closed this issue 2026-03-16 11:21:02 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
john/McRogueFace#301
No description provided.