EntityBehavior no longer holds a direct DijkstraMap reference. A new
PathProvider interface has three concrete implementations:
- DijkstraProvider: steps along a (possibly inverted) DijkstraMap. SEEK
descends a normal map toward roots; FLEE descends an inverted map away
from threats.
- AStarProvider: follows a pre-computed AStarPath step-by-step.
- TargetProvider: takes a single (x, y) target and picks the Chebyshev
neighbor closest to it each turn.
Entity.set_behavior() gains a pathfinder= kwarg accepting any of the above
(DijkstraMap, AStarPath, or (x, y) tuple). The old executeSeek/executeFlee
helpers collapse into a single executeProviderStep() that delegates to the
provider.
EntityBehavior.h forward-declares PathProvider so the header stays light.
EntityBehavior::reset() moves out of line to avoid pulling PathProvider
into the header.
New tests: tests/regression/issue_315_path_provider_test.py covers all three
providers driving SEEK, FLEE via inverted DijkstraMap, mid-run pathfinder
swap, and invalid-argument handling. grid_step_bench baseline refreshed
against the new provider dispatch path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase A (Python surface):
- New mcrfpy.Heuristic IntEnum: EUCLIDEAN, MANHATTAN, CHEBYSHEV, DIAGONAL, ZERO
- Grid.find_path() accepts heuristic= and weight= kwargs (weighted A*)
- Grid.get_dijkstra_map() accepts roots= (list of positions or DiscreteMap mask)
Phase B (FLEE primitives):
- DijkstraMap.invert() returns a new map with inverted distance field
- DijkstraMap.descent_step(pos) returns steepest-descent neighbor or None
DijkstraMap internally switched from the C++ TCODDijkstra wrapper to the C API
(TCOD_dijkstra_*) because multi-root compute and invert/get_descent are not
exposed on the wrapper. Single-root Dijkstra cache is preserved for backward
compatibility; multi-root and mask paths bypass the cache since cache keys
would be ill-defined.
New tests: heuristic_enum_test, find_path_heuristic_test, multi_root_dijkstra_test,
dijkstra_flee_test. Baseline JSONs for dijkstra_bench and gridview_render_bench
refreshed against the new implementation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds 6 benchmark scripts in tests/benchmarks/ covering all 5 scenarios from
Kanboard #37, plus a shared baseline helper:
grid_step_bench.py 100 ent / 100x100 grid / 1000 grid.step() rounds
mix of IDLE/NOISE8/SEEK/FLEE behaviors
fov_opt_bench.py 100 ent / 1000x1000 grid; entity.update_visibility()
(with DiscreteMap perspective writeback) vs bare
grid.compute_fov() (no writeback) across FOV
algorithms BASIC/SHADOW/SYMMETRIC_SHADOWCAST and
radii 8/16/32
spatial_hash_bench.py entities_in_radius() at radii (1,5,10,50) x
entity counts (100,1k,10k); compares against
naive O(n) baseline with hit-count validation
pathfinding_bench.py A* across grid sizes/densities/heuristics/weights,
plus with-vs-without `collide=` collision-label
comparison (0/10/100 blockers on 100x100)
gridview_render_bench.py 1/2/4 GridViews on shared grid; uses
automation.screenshot() to force real renders in
headless mode (mcrfpy.step alone is render-stubbed)
dijkstra_bench.py single-root, multi-root, mask, invert, descent
_baseline.py writes baseline JSON to baseline/phase5_2/
All scripts emit JSON to stdout and write a baseline copy under
tests/benchmarks/baseline/phase5_2/ for regression comparison. All run
headless; pure time.perf_counter() timing for compute benches, screenshot
wall-time for the render bench (start/end_benchmark would only capture the
no-op headless game loop, so direct timing is used).
Notable findings captured in baselines:
- spatial hash: 5x to >300x speedup over naive O(n), hits validated identical
- update_visibility: ~25-37 ms/entity perspective writeback overhead on
1000x1000 grid (full-grid demote+promote loop in UIEntity::updateVisibility)
dominates over the actual TCOD FOV cost (~3-24 ms). Worth a follow-up issue
for sparse perspective updating.
- gridview render: per-view cost scales near-linearly down (~78ms total for
1, 2, or 4 views) -- the multi-view system shares state efficiently.
Refs Kanboard #37.
Breaking API change: removes 4 camelCase function aliases from the mcrfpy
module. The snake_case equivalents (set_scale, find_all, get_metrics,
set_dev_console) remain and are the canonical API going forward.
- Removed setScale, findAll, getMetrics, setDevConsole from mcrfpyMethods[]
- Updated game scripts to use snake_case names
- Updated test scripts to use snake_case names
- Removed camelCase entries from type stubs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed custom __eq__/__ne__ that allowed comparing enums to legacy string
names (e.g., Key.ESCAPE == "Escape"). Removed _legacy_names dicts and
to_legacy_string() functions. Kept from_legacy_string() in PyKey.cpp as
it's used by C++ event dispatch. Updated ~50 Python test/demo/cookbook
files to use enum members instead of string comparisons. Also updates
grid.position -> grid.pos in files that had both types of changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add SpatialHash class for efficient spatial queries on entities:
- New SpatialHash.h/cpp with bucket-based spatial hashing
- Grid.entities_in_radius(x, y, radius) method for O(k) queries
- Automatic spatial hash updates on entity add/remove/move
Benchmark results at 2,000 entities:
- Single query: 16.2× faster (0.044ms → 0.003ms)
- N×N visibility: 104.8× faster (74ms → 1ms)
This enables efficient range queries for AI, visibility, and
collision detection without scanning all entities.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Benchmark suite measuring entity performance at scale:
- B1: Entity creation (measures allocation overhead)
- B2: Full iteration (measures cache locality)
- B3: Single range query (measures O(n) scan cost)
- B4: N×N visibility (the "what can everyone see" problem)
- B5: Movement churn (baseline for spatial index overhead)
Key findings at 2,000 entities on 100×100 grid:
- Creation: 75k entities/sec
- Range query: 0.05ms (O(n) - checks all entities)
- N×N visibility: 128ms total (O(n²))
- EntityCollection iteration 60× slower than direct iteration
Addresses #115, addresses #117🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds a sub-grid system where grids larger than 64x64 cells are automatically
divided into 64x64 chunks, each with its own RenderTexture for incremental
rendering. This significantly improves performance for large grids by:
- Only re-rendering dirty chunks when cells are modified
- Caching rendered chunk textures between frames
- Viewport culling at the chunk level (skip invisible chunks entirely)
Implementation details:
- GridChunk class manages individual 64x64 cell regions with dirty tracking
- ChunkManager organizes chunks and routes cell access appropriately
- UIGrid::at() method transparently routes through chunks for large grids
- UIGrid::render() uses chunk-based blitting for large grids
- Compile-time CHUNK_SIZE (64) and CHUNK_THRESHOLD (64) constants
- Small grids (<= 64x64) continue to use flat storage (no regression)
Benchmark results show ~2x improvement in base layer render time for 100x100
grids (0.45ms -> 0.22ms) due to chunk caching.
Note: Dynamic layers (#147) still use full-grid textures; extending chunk
system to layers is tracked separately as #150.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
compute_fov() was iterating through the entire grid to build a Python
list of visible cells, causing O(grid_size) performance instead of
O(radius²). On a 1000×1000 grid this was 15.76ms vs 0.48ms.
The fix returns None instead - users should use is_in_fov() to query
visibility, which is the pattern already used by existing code.
Performance: 33x speedup (15.76ms → 0.48ms on 1M cell grid)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major changes:
- Reorganized tests/ into unit/, integration/, regression/, benchmarks/, demo/
- Deleted 73 failing/outdated tests, kept 126 passing tests (100% pass rate)
- Created demo system with 6 feature screens (Caption, Frame, Primitives, Grid, Animation, Color)
- Updated .gitignore to track tests/ directory
- Updated CLAUDE.md with comprehensive testing guidelines and API quick reference
Demo system features:
- Interactive menu navigation (press 1-6 for demos, ESC to return)
- Headless screenshot generation for CI
- Per-feature demonstration screens with code examples
Testing infrastructure:
- tests/run_tests.py - unified test runner with timeout support
- tests/demo/demo_main.py - interactive/headless demo runner
- All tests are headless-compliant
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>