- Remove color, color_overlay, tilesprite, tile_overlay, uisprite from
UIGridPoint - these are now accessed through named layers
- Keep only walkable and transparent as protected GridPoint properties
- Update isProtectedLayerName() to only protect walkable/transparent
- Fix default layer z-index to -1 (below entities) instead of 0
- Remove dead rendering code from GridChunk (layers handle rendering)
- Update cos_level.py demo to use explicit layer definitions
- Update UITestScene.cpp to use layer API instead of GridPoint properties
Part of #150 - Grid layer system migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add `layers` dict parameter to Grid constructor for explicit layer definitions
- `layers={"ground": "color", "terrain": "tile"}` creates named layers
- `layers={}` creates empty grid (entities + pathfinding only)
- Default creates single TileLayer named "tilesprite" for backward compat
- Implement dynamic GridPoint property access via layer names
- `grid.at(x,y).layer_name = value` routes to corresponding layer
- Protected names (walkable, transparent, etc.) still use GridPoint
- Remove base layer rendering from UIGrid::render()
- Layers are now the sole source of grid rendering
- Old chunk_manager remains for GridPoint data access
- FOV overlay unchanged
- Update test to use explicit `layers={}` parameter
🤖 Generated with [Claude Code](https://claude.com/claude-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>
Implement per-layer dirty tracking and RenderTexture caching for
ColorLayer and TileLayer. Each layer now maintains its own cached
texture and only re-renders when content changes.
Key changes:
- Add dirty flag, cached_texture, and cached_sprite to GridLayer base
- Implement renderToTexture() for both ColorLayer and TileLayer
- Mark layers dirty on: set(), fill(), resize(), texture change
- Viewport changes (center/zoom) just blit cached texture portion
- Fallback to direct rendering if texture creation fails
- Add regression test with performance benchmarks
Expected performance improvement: Static layers render once, then
viewport panning/zooming only requires texture blitting instead of
re-rendering all cells.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements ColorLayer and TileLayer classes with z_index ordering:
- ColorLayer: stores RGBA color per cell for overlays, fog of war, etc.
- TileLayer: stores sprite index per cell with optional texture
- z_index < 0: renders below entities
- z_index >= 0: renders above entities
Python API:
- grid.add_layer(type, z_index, texture) - create layer
- grid.remove_layer(layer) - remove layer
- grid.layers - list of layers sorted by z_index
- grid.layer(z_index) - get layer by z_index
- layer.at(x,y) / layer.set(x,y,value) - cell access
- layer.fill(value) - fill entire layer
Layers are allocated separately from UIGridPoint, reducing memory
for grids that don't need all features. Base grid retains walkable/
transparent arrays for TCOD pathfinding.
🤖 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>
- Add cache_subtree property on Frame for opt-in RenderTexture caching
- Add PyTexture::from_rendered() factory for runtime texture creation
- Add snapshot= parameter to Sprite for creating sprites from Frame content
- Implement content_dirty vs composite_dirty distinction:
- markContentDirty(): content changed, invalidate self and ancestors
- markCompositeDirty(): position changed, ancestors need recomposite only
- Update all UIDrawable position setters to use markCompositeDirty()
- Add quick exit workaround for cleanup segfaults
Benchmark: deep_nesting_cached is 3.7x faster (0.09ms vs 0.35ms)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Track actual work time separately from frame time to determine
system load percentage:
- work_time_ms: Time spent doing actual work before display()
- sleep_time = frame_time_ms - work_time_ms
This allows calculating load percentage:
load% = (work_time / frame_time) * 100
Example at 60fps with light load:
- frame_time: 16.67ms, work_time: 2ms
- load: 12%, sleep: 14.67ms
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add Python API for capturing performance data to JSON files:
- mcrfpy.start_benchmark() - start capturing frame data
- mcrfpy.end_benchmark() - stop and return filename
- mcrfpy.log_benchmark(msg) - add log message to current frame
The benchmark system captures per-frame data including:
- Frame timing (frame_time_ms, fps, timestamp)
- Detailed timing breakdown (grid_render, entity_render, python, animation, fov)
- Draw call and element counts
- User log messages attached to frames
Output JSON format supports analysis tools and includes:
- Benchmark metadata (PID, timestamps, duration, total frames)
- Full frame-by-frame metrics array
Also refactors ProfilingMetrics from nested GameEngine struct to
top-level struct for easier forward declaration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements multiple mouse event improvements for UI elements:
- Mouse enter/exit events (#140): on_enter, on_exit callbacks and
hovered property for all UIDrawable types (Frame, Caption, Sprite, Grid)
- Headless click events (#111): Track simulated mouse position for
automation testing in headless mode
- Mouse move events (#141): on_move callback fires continuously while
mouse is within element bounds
- Grid cell events (#142): on_cell_enter, on_cell_exit, on_cell_click
callbacks with cell coordinates (x, y), plus hovered_cell property
Includes comprehensive tests for all new functionality.
Closes#140, closes#111, closes#141, closes#142🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
C++ additions:
- get_global_bounds(): returns bounds in screen coordinates
- contains_point(x, y): hit test using global bounds
Python properties (on all UIDrawable types):
- bounds: (x, y, w, h) tuple in local coordinates
- global_bounds: (x, y, w, h) tuple in screen coordinates
These enable the mouse event system (#140, #141, #142) by providing
a way to determine which drawable is under the mouse cursor.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Breaking change: callback property standardized to on_* pattern.
- `drawable.click` → `drawable.on_click`
Updated all C++ bindings (8 files) and Python test usages.
Note: src/scripts changes tracked separately (in .gitignore).
This establishes the naming pattern for future callbacks:
on_click, on_enter, on_exit, on_move, on_key, etc.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
UIEntity now has a `.grid` property with getter/setter:
- entity.grid # Get current grid (or None)
- entity.grid = grid # Move to new grid (auto-removes from old)
- entity.grid = None # Remove from current grid
Also fixes UIEntityCollection.append() to properly implement the
documented "single grid only" behavior - entities are now correctly
removed from their old grid when appended to a new one.
This matches the parent property pattern used for UIDrawables.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Instead of separate getParent()/setParent()/removeFromParent() methods,
the parent property now supports the Pythonic getter/setter pattern:
- child.parent # Get parent (or None)
- child.parent = f # Set parent (adds to f.children)
- child.parent = None # Remove from parent
This matches the existing pattern used by the click property callback.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Parent-Child UI System (#122):
- Add parent weak_ptr to UIDrawable for hierarchy tracking
- Add setParent(), getParent(), removeFromParent() methods
- UICollection now tracks owner and sets parent on append/insert
- Auto-remove from old parent when adding to new collection
Global Position Property (#102):
- Add get_global_position() that walks up parent chain
- Expose as read-only 'global_position' property on all UI types
- Add UIDRAWABLE_PARENT_GETSETTERS macro for consistent bindings
Dirty Flag System (#116):
- Modify markDirty() to propagate up the parent chain
- Add isDirty() and clearDirty() methods for render optimization
Scene as Drawable (#118):
- Add position, visible, opacity properties to Scene
- Add setProperty()/getProperty() for animation support
- Apply scene transformations in PyScene::render()
- Fix lifecycle callbacks to clear errors when methods don't exist
- Add GameEngine::getScene() public accessor
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Set sys.executable in PyConfig for subprocess/pip calls
- Detect sibling venv/ directory and prepend site-packages to sys.path
- Add mcrf_venv.py reference implementation for bootstrapping pip
- Supports both Linux (lib/python3.14/site-packages) and Windows (Lib/site-packages)
Usage: ./mcrogueface -m pip install numpy
Or via Python: mcrf_venv.pip_install("numpy")
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Integrates Dear ImGui for an in-game debug console that replaces the
blocking Python REPL. Press ~ (grave/tilde) to toggle the console.
Features:
- Python code execution without blocking the game loop
- Output capture with color coding (yellow=input, red=errors, gray=output)
- Expression results show repr() automatically
- Command history navigation with up/down arrows
- Word wrapping for long output lines
- Auto-scroll that doesn't fight manual scrolling
- mcrfpy.setDevConsole(bool) API to disable for shipping
Technical changes:
- Update imgui submodule to v1.89.9 (stable)
- Update imgui-sfml submodule to 2.6.x branch (SFML 2.x compatible)
- Add ImGui sources to CMakeLists.txt with OpenGL dependency
- Integrate ImGui lifecycle into GameEngine
- Add ImGuiConsole class for console overlay
closes#36, closes#65, closes#75🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Both branches fixed the --exec double-execution bug with complementary approaches:
- origin/master: Added executeStartupScripts() method for cleaner separation
- HEAD: Avoided engine recreation to preserve state
This merge keeps the best of both: executeStartupScripts() called on the
existing engine without recreation.
Also accepts deletion of flaky test_viewport_visual.py from origin/master.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace deprecated Python C API calls with modern PyConfig-based initialization:
- PySys_SetArgvEx() -> PyConfig.argv (deprecated since 3.11)
- Py_InspectFlag -> PyConfig.inspect (deprecated since 3.12)
Fix critical memory safety bugs discovered during migration:
- PyColor::from_arg() and PyVector::from_arg() now return new references
instead of borrowed references, preventing use-after-free when callers
call Py_DECREF on the result
- GameEngine::testTimers() now holds a local shared_ptr copy during
callback execution, preventing use-after-free when timer callbacks
call delTimer() on themselves
Fix double script execution bug with --exec flag:
- Scripts were running twice because GameEngine constructor executed them,
then main.cpp deleted and recreated the engine
- Now reuses existing engine and just sets auto_exit_after_exec flag
Update test syntax to use keyword arguments for Frame/Caption constructors.
Test results: 127/130 passing (97.7%)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Scripts passed to --exec were executing twice because GameEngine
constructor ran scripts, and main.cpp created two GameEngine instances.
- Move exec_scripts from constructor to new executeStartupScripts() method
- Call executeStartupScripts() once after final engine setup in main.cpp
- Remove double-execution workarounds from tests
- Delete duplicate test_viewport_visual.py (flaky due to race condition)
- Fix test constructor syntax and callback signatures
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
By default, McRogueFace now exits with code 1 on the first unhandled
exception in timer, click, key, or animation callbacks. This prevents
repeated exception output that wastes resources in AI-driven development.
Changes:
- Add exit_on_exception config flag (default: true)
- Add --continue-after-exceptions CLI flag to preserve old behavior
- Update exception handlers in Timer, PyCallable, and Animation
- Signal game loop via McRFPy_API atomic flags
- Return proper exit code from main()
Before: Timer exceptions repeated 1000+ times until timeout
After: Single traceback, clean exit with code 1
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Breaking change: UICollection.remove() now takes a value (element) instead
of an index, matching Python's list.remove() behavior.
New methods added to both UICollection and EntityCollection:
- pop([index]) -> element: Remove and return element at index (default: last)
- insert(index, element): Insert element at position
Semantic fixes:
- remove(element): Now removes first occurrence of element (was: remove by index)
- All methods now have docstrings documenting behavior
Note on z_index sorting: The collections are sorted by z_index before each
render. Using index-based operations (pop, insert) with non-default z_index
values may produce unexpected results. Use name-based .find() for stable
element access when z_index sorting is in use.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements name-based search for UI elements and entities:
- Exact match returns single element or None
- Wildcard patterns (prefix*, *suffix, *contains*) return list
- Recursive search for nested Frame children (UICollection only)
API:
ui.find("player_frame") # exact match
ui.find("enemy*") # starts with
ui.find("*_button", recursive=True) # recursive search
grid.entities.find("*goblin*") # entity search
Closes#41, closes#40🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements issue #130 with:
- Basic utilities: distance, angle_between, normalize_angle, lerp, clamp
- Grid algorithms: bresenham_circle, bresenham_line, filled_circle
- OrbitalBody class with recursive positioning (star -> planet -> moon)
- OrbitingShip class for relative ship positioning on orbit rings
- Pathfinding helpers: nearest_orbit_entry, optimal_exit_heading,
is_viable_waypoint, line_of_sight_blocked
- Comprehensive test suite (25+ tests)
Designed for Pinships turn-based space roguelike with:
- Discrete time steps (planets move in whole grid squares)
- Deterministic position projection
- Free orbital movement while in orbit
- Support for nested orbits (moons of moons)
closes#130🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Grid now supports a `children` collection for arbitrary UIDrawable elements
(speech bubbles, effects, highlights, path visualization, etc.) that
automatically transform with the grid's camera (pan/zoom).
Key features:
- Children positioned in grid-world pixel coordinates
- Render after entities, before FOV overlay (proper z-ordering)
- Sorted by z_index, culled when outside visible region
- Click detection transforms through grid camera
- Automatically clipped to grid boundaries via RenderTexture
Python API:
grid.children.append(caption) # Speech bubble follows grid camera
grid.children.append(circle) # Highlight indicator
Closes#132🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement new UIDrawable-derived classes for vector graphics:
- UILine: Thick line segments using sf::ConvexShape for proper thickness
- Properties: start, end, color, thickness
- Supports click detection along the line
- UICircle: Filled and outlined circles using sf::CircleShape
- Properties: radius, center, fill_color, outline_color, outline
- Full property system for animations
- UIArc: Arc segments for orbital paths and partial circles
- Properties: center, radius, start_angle, end_angle, color, thickness
- Uses sf::VertexArray with TriangleStrip for smooth rendering
- Supports arbitrary angle spans including negative (reverse) arcs
All primitives integrate with the Python API through mcrfpy module:
- Added to PyObjectsEnum for type identification
- Full getter/setter support for all properties
- Added to UICollection for scene management
- Support for visibility, opacity, z_index, name, and click handling
closes#128, closes#129🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Closes#127
Previously, `./mcrogueface --headless --exec <script>` would hang
indefinitely after the script completed because the game loop ran
continuously. This required external timeouts and explicit mcrfpy.exit()
calls in every automation script.
This commit adds automatic exit detection for headless+exec mode:
- Added `auto_exit_after_exec` flag to McRogueFaceConfig
- Set flag automatically when both --headless and --exec are present
- Game loop exits when no timers remain (timers.empty())
Benefits:
- Documentation generation scripts work without explicit exit calls
- Testing scripts don't need timeout wrappers
- Clean process termination for automation
- Backward compatible (scripts with mcrfpy.exit() continue working)
Changes:
- src/McRogueFaceConfig.h: Add auto_exit_after_exec flag
- src/main.cpp: Set flag and recreate engine with modified config
- src/GameEngine.cpp: Check timers.empty() in game loop
- ROADMAP.md: Mark Phase 7 as complete (2025-10-30)
- CLAUDE.md: Add instruction about closing issues with commit messages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Converted all module-level functions in McRFPy_API.cpp to use the MCRF_*
documentation macro system:
Audio functions (7):
- createSoundBuffer, loadMusic, setMusicVolume, setSoundVolume
- playSound, getMusicVolume, getSoundVolume
Scene functions (5):
- sceneUI, currentScene, setScene, createScene, keypressScene
Timer functions (2):
- setTimer, delTimer
Utility functions (5):
- exit, setScale, find, findAll, getMetrics
Each function now uses:
- MCRF_SIG for signatures
- MCRF_DESC for descriptions
- MCRF_ARG for parameters
- MCRF_RETURNS for return values
- MCRF_RAISES for exceptions
- MCRF_NOTE for additional details
Phase 4 assessment: PyCallable.cpp and PythonObjectCache.cpp contain only
internal C++ implementation with no Python API to document.
All conversions tested and verified with test_phase3_docs.py.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed module-level docstring in PyModuleDef where double-backslash newlines
(\\n) were appearing as literal "\n" text in help(mcrfpy) output.
Changed from escaped newlines (\\n) to actual newlines (\n) so the C compiler
interprets them correctly.
Before: help(mcrfpy) showed "McRogueFace Python API\\n\\nCore game..."
After: help(mcrfpy) shows proper formatting with line breaks
The issue was in the PyDoc_STR() macro call - it doesn't interpret escape
sequences, so the string literal itself needs to have proper newlines.
Fixes critical issue discovered in code review where PyDrawable property
docstrings were being overridden by child classes, making enhanced documentation
invisible to users.
Updated files:
- src/UIBase.h: UIDRAWABLE_GETSETTERS macro (visible, opacity)
- src/UIFrame.cpp: click and z_index properties
- src/UISprite.cpp: click and z_index properties
- src/UICaption.cpp: click and z_index properties
- src/UIGrid.cpp: click and z_index properties
All four UI class hierarchies (Frame, Sprite, Caption, Grid) now expose
consistent, enhanced property documentation to Python users.
Verification:
- tools/test_child_class_docstrings.py: All 16 property tests pass
- All 4 properties (click, z_index, visible, opacity) match across all 4 classes
Related: #92 (Inline C++ documentation system)
All Drawable properties (click, z_index, visible, opacity) now
use MCRF_PROPERTY with enhanced descriptions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Converts get_bounds, move, and resize to MCRF_METHOD.
These are inherited by all UI classes (Frame, Caption, Sprite, Grid).
Updated both PyDrawable.cpp and UIBase.h (UIDRAWABLE_METHODS macro).
All method docstrings now include complete Args, Returns, and Note sections.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Properties x and y now use MCRF_PROPERTY for consistency.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
All Vector methods now use MCRF_METHOD macros with complete
documentation including Args, Returns, and Notes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The normalize() method's implementation returns a zero vector when
called on a zero-magnitude vector, rather than raising ValueError as
the documentation claimed. Updated the MCRF_RAISES to MCRF_NOTE to
accurately describe the actual behavior.
Also added test coverage in tools/test_vector_docs.py to verify the
normalize() docstring contains the correct Note section.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Converts magnitude, normalize, and dot methods to MCRF_METHOD macro.
Docstrings now include complete Args/Returns/Raises sections.
Addresses issue #92.
Adds C++ preprocessor macros for consistent API documentation.
Addresses issue #92.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previously, EntityCollection.remove() required an integer index, which was
inconsistent with Python's list.remove(item) behavior and the broader
Python ecosystem conventions.
Changes:
- remove() now accepts Entity object directly instead of index
- Searches collection by comparing C++ shared_ptr identity
- Raises ValueError if entity not found in collection
- More Pythonic API matching Python's list.remove() semantics
This aligns with Issue #73 and improves API consistency across the
collection system.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements comprehensive entity interaction system:
- Entity class hierarchy inheriting from mcrfpy.Entity
- Non-blocking movement animations with destination tracking
- Bump interactions (combat when hitting enemies, pushing boulders)
- Step-on interactions (buttons that open doors)
- Basic enemy AI with line-of-sight pursuit
- Concurrent animation system (enemies move while player moves)
Also fixes C++ animation system to support Python subclasses:
- Changed PyAnimation::start() to use PyObject_IsInstance instead of strcmp
- Now properly supports inherited entity classes
- Animation system works with any subclass of Frame, Caption, Sprite, Grid, or Entity
This completes the core gameplay mechanics needed for roguelike development.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major improvements to the Field of View (FOV) system:
1. Added thread safety with mutex protection
- Added mutable std::mutex fov_mutex to UIGrid class
- Protected computeFOV() and isInFOV() with lock_guard
- Minimal overhead for current single-threaded operation
- Ready for future multi-threading requirements
2. Enhanced compute_fov() API to return visible cells
- Changed return type from void to List[Tuple[int, int, bool, bool]]
- Returns (x, y, visible, discovered) for all visible cells
- Maintains backward compatibility by still updating internal FOV state
- Allows FOV queries without affecting entity states
3. Fixed Part 4 tutorial visibility rendering
- Added required entity.update_visibility() calls after compute_fov()
- Fixed black grid issue in perspective rendering
- Updated hallway generation to use L-shaped corridors
The architecture now properly separates concerns while maintaining
performance and preparing for future enhancements. Each entity can
have independent FOV calculations without race conditions.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major refactor of the perspective system to use entity references instead of indices:
- Replaced `int perspective` with `std::weak_ptr<UIEntity> perspective_entity`
- Added `bool perspective_enabled` flag for explicit control
- Direct entity assignment: `grid.perspective = player`
- Automatic cleanup when entity is destroyed (weak_ptr becomes invalid)
- No issues with collection reordering or entity removal
- PythonObjectCache integration preserves Python derived classes
API changes:
- Old: `grid.perspective = 0` (index), `-1` for omniscient
- New: `grid.perspective = entity` (object), `None` to clear
- New: `grid.perspective_enabled` controls rendering mode
Three rendering states:
1. `perspective_enabled = False`: Omniscient view (default)
2. `perspective_enabled = True` with valid entity: Entity's FOV
3. `perspective_enabled = True` with invalid entity: All black
Also includes:
- Part 3: Procedural dungeon generation with libtcod.line()
- Part 4: Field of view with entity perspective switching
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Author: John McCardle <mccardle.john@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>
commit dc47f2474c7b2642d368f9772894aed857527807
the UIEntity rant
commit 673ca8e1b089ea670257fc04ae1a676ed95a40ed
I forget when these tests were written, but I want them in the squash merge
commit 70c71565c684fa96e222179271ecb13a156d80ad
Fix UI object segfault by switching from managed to manual weakref management
The UI types (Frame, Caption, Sprite, Grid, Entity) were using
Py_TPFLAGS_MANAGED_WEAKREF while also trying to manually create weakrefs
for the PythonObjectCache. This is fundamentally incompatible - when
Python manages weakrefs internally, PyWeakref_NewRef() cannot access the
weakref list properly, causing segfaults.
Changed all UI types to use manual weakref management (like PyTimer):
- Restored weakreflist field in all UI type structures
- Removed Py_TPFLAGS_MANAGED_WEAKREF from all UI type flags
- Added tp_weaklistoffset for all UI types in module initialization
- Initialize weakreflist=NULL in tp_new and init methods
- Call PyObject_ClearWeakRefs() in dealloc functions
This allows the PythonObjectCache to continue working correctly,
maintaining Python object identity for C++ objects across the boundary.
Fixes segfault when creating UI objects (e.g., Caption, Grid) that was
preventing tutorial scripts from running.
This is the bulk of the required behavior for Issue #126.
that issure isn't ready for closure yet; several other sub-issues left.
closes#110
mention issue #109 - resolves some __init__ related nuisances
commit 3dce3ec539ae99e32d869007bf3f49d03e4e2f89
Refactor timer system for cleaner architecture and enhanced functionality
Major improvements to the timer system:
- Unified all timer logic in the Timer class (C++)
- Removed PyTimerCallable subclass, now using PyCallable directly
- Timer objects are now passed to callbacks as first argument
- Added 'once' parameter for one-shot timers that auto-stop
- Implemented proper PythonObjectCache integration with weakref support
API enhancements:
- New callback signature: callback(timer, runtime) instead of just (runtime)
- Timer objects expose: name, interval, remaining, paused, active, once properties
- Methods: pause(), resume(), cancel(), restart()
- Comprehensive documentation with examples
- Enhanced repr showing timer state (active/paused/once/remaining time)
This cleanup follows the UIEntity/PyUIEntity pattern and makes the timer
system more Pythonic while maintaining backward compatibility through
the legacy setTimer/delTimer API.
closes#121
commit 145834cfc31b8dabc4cb3591b9cb4ed99fc8b964
Implement Python object cache to preserve derived types in collections
Add a global cache system that maintains weak references to Python objects,
ensuring that derived Python classes maintain their identity when stored in
and retrieved from C++ collections.
Key changes:
- Add PythonObjectCache singleton with serial number system
- Each cacheable object (UIDrawable, UIEntity, Timer, Animation) gets unique ID
- Cache stores weak references to prevent circular reference memory leaks
- Update all UI type definitions to support weak references (Py_TPFLAGS_MANAGED_WEAKREF)
- Enable subclassing for all UI types (Py_TPFLAGS_BASETYPE)
- Collections check cache before creating new Python wrappers
- Register objects in cache during __init__ methods
- Clean up cache entries in C++ destructors
This ensures that Python code like:
```python
class MyFrame(mcrfpy.Frame):
def __init__(self):
super().__init__()
self.custom_data = "preserved"
frame = MyFrame()
scene.ui.append(frame)
retrieved = scene.ui[0] # Same MyFrame instance with custom_data intact
```
Works correctly, with retrieved maintaining the derived type and custom attributes.
Closes#112
commit c5e7e8e298
Update test demos for new Python API and entity system
- Update all text input demos to use new Entity constructor signature
- Fix pathfinding showcase to work with new entity position handling
- Remove entity_waypoints tracking in favor of simplified movement
- Delete obsolete exhaustive_api_demo.py (superseded by newer demos)
- Adjust entity creation calls to match Entity((x, y), texture, sprite_index) pattern
commit 6d29652ae7
Update animation demo suite with crash fixes and improvements
- Add warnings about AnimationManager segfault bug in sizzle_reel_final.py
- Create sizzle_reel_final_fixed.py that works around the crash by hiding objects instead of removing them
- Increase font sizes for better visibility in demos
- Extend demo durations for better showcase of animations
- Remove debug prints from animation_sizzle_reel_working.py
- Minor cleanup and improvements to all animation demos
commit a010e5fa96
Update game scripts for new Python API
- Convert entity position access from tuple to x/y properties
- Update caption size property to font_size
- Fix grid boundary checks to use grid_size instead of exceptions
- Clean up demo timer on menu exit to prevent callbacks
These changes adapt the game scripts to work with the new standardized
Python API constructors and property names.
commit 9c8d6c4591
Fix click event z-order handling in PyScene
Changed click detection to properly respect z-index by:
- Sorting ui_elements in-place when needed (same as render order)
- Using reverse iterators to check highest z-index elements first
- This ensures top-most elements receive clicks before lower ones
commit dcd1b0ca33
Add roguelike tutorial implementation files
Implement Parts 0-2 of the classic roguelike tutorial adapted for McRogueFace:
- Part 0: Basic grid setup and tile rendering
- Part 1: Drawing '@' symbol and basic movement
- Part 1b: Variant with sprite-based player
- Part 2: Entity system and NPC implementation with three movement variants:
- part_2.py: Standard implementation
- part_2-naive.py: Naive movement approach
- part_2-onemovequeued.py: Queued movement system
Includes tutorial assets:
- tutorial2.png: Tileset for dungeon tiles
- tutorial_hero.png: Player sprite sheet
commit 6813fb5129
Standardize Python API constructors and remove PyArgHelpers
- Remove PyArgHelpers.h and all macro-based argument parsing
- Convert all UI class constructors to use PyArg_ParseTupleAndKeywords
- Standardize constructor signatures across UICaption, UIEntity, UIFrame, UIGrid, and UISprite
- Replace PYARGHELPER_SINGLE/MULTI macros with explicit argument parsing
- Improve error messages and argument validation
- Maintain backward compatibility with existing Python code
This change improves code maintainability and consistency across the Python API.
commit 6f67fbb51e
Fix animation callback crashes from iterator invalidation (#119)
Resolved segfaults caused by creating new animations from within
animation callbacks. The issue was iterator invalidation in
AnimationManager::update() when callbacks modified the active
animations vector.
Changes:
- Add deferred animation queue to AnimationManager
- New animations created during update are queued and added after
- Set isUpdating flag to track when in update loop
- Properly handle Animation destructor during callback execution
- Add clearCallback() method for safe cleanup scenarios
This fixes the "free(): invalid pointer" and "malloc(): unaligned
fastbin chunk detected" errors that occurred with rapid animation
creation in callbacks.
commit eb88c7b3aa
Add animation completion callbacks (#119)
Implement callbacks that fire when animations complete, enabling direct
causality between animation end and game state changes. This eliminates
race conditions from parallel timer workarounds.
- Add optional callback parameter to Animation constructor
- Callbacks execute synchronously when animation completes
- Proper Python reference counting with GIL safety
- Callbacks receive (anim, target) parameters (currently None)
- Exception handling prevents crashes from Python errors
Example usage:
```python
def on_complete(anim, target):
player_moving = False
anim = mcrfpy.Animation("x", 300.0, 1.0, "easeOut", callback=on_complete)
anim.start(player)
```
closes#119
commit 9fb428dd01
Update ROADMAP with GitHub issue numbers (#111-#125)
Added issue numbers from GitHub tracker to roadmap items:
- #111: Grid Click Events Broken in Headless
- #112: Object Splitting Bug (Python type preservation)
- #113: Batch Operations for Grid
- #114: CellView API
- #115: SpatialHash Implementation
- #116: Dirty Flag System
- #117: Memory Pool for Entities
- #118: Scene as Drawable
- #119: Animation Completion Callbacks
- #120: Animation Property Locking
- #121: Timer Object System
- #122: Parent-Child UI System
- #123: Grid Subgrid System
- #124: Grid Point Animation
- #125: GitHub Issues Automation
Also updated existing references:
- #101/#110: Constructor standardization
- #109: Vector class indexing
Note: Tutorial-specific items and Python-implementable features
(input queue, collision reservation) are not tracked as engine issues.
commit 062e4dadc4
Fix animation segfaults with RAII weak_ptr implementation
Resolved two critical segmentation faults in AnimationManager:
1. Race condition when creating multiple animations in timer callbacks
2. Exit crash when animations outlive their target objects
Changes:
- Replace raw pointers with std::weak_ptr for automatic target invalidation
- Add Animation::complete() to jump animations to final value
- Add Animation::hasValidTarget() to check if target still exists
- Update AnimationManager to auto-remove invalid animations
- Add AnimationManager::clear() call to GameEngine::cleanup()
- Update Python bindings to pass shared_ptr instead of raw pointers
This ensures animations can never reference destroyed objects, following
proper RAII principles. Tested with sizzle_reel_final.py and stress
tests creating/destroying hundreds of animated objects.
commit 98fc49a978
Directory structure cleanup and organization overhaul