Major milestone for issue #158 (Emscripten/WebAssembly build target):
- Python 3.14 successfully initializes and runs in WASM
- mcrfpy module loads and works correctly
- Game scripts execute with full level generation
- Entities (boulders, rats, cyclops, spawn points) placed correctly
Key changes:
- CMakeLists.txt: Add 2MB stack, Emscripten link options, preload files
- platform.h: Add WASM-specific implementations for executable paths
- HeadlessTypes.h: Make Texture/Font/Sound stubs return success
- CommandLineParser.cpp: Guard filesystem operations for WASM
- McRFPy_API.cpp: Add WASM path configuration, debug output
- game.py: Make 'code' module import optional (not available in WASM)
- wasm_stdlib/: Add minimal Python stdlib for WASM (~4MB)
Build with: emmake make (from build-emscripten/)
Test with: node mcrogueface.js
Next steps:
- Integrate VRSFML for actual WebGL rendering
- Create HTML page to host WASM build
- Test in actual browsers
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit enables McRogueFace to compile without SFML dependencies
when built with -DMCRF_HEADLESS, a prerequisite for Emscripten/WebAssembly
support.
Changes:
- Add src/platform/HeadlessTypes.h (~900 lines of SFML type stubs)
- Consolidate all SFML includes through src/Common.h (15 files fixed)
- Wrap ImGui-SFML with #ifndef MCRF_HEADLESS guards
- Disable debug console/explorer in headless builds
- Add comprehensive research document: docs/EMSCRIPTEN_RESEARCH.md
The headless build compiles successfully but uses stub implementations
that return failure/no-op. This proves the abstraction boundary is clean
and enables future work on alternative backends (VRSFML, Emscripten).
What still works in headless mode:
- Python interpreter and script execution
- libtcod integrations (pathfinding, FOV, noise, BSP, heightmaps)
- Timer system and scene management
- All game logic and data structures
Build commands:
Normal: make
Headless: cmake .. -DCMAKE_CXX_FLAGS="-DMCRF_HEADLESS" && make
Addresses #158
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove mcrfpy.libtcod submodule entirely
- Add mcrfpy.bresenham(start, end, include_start=True, include_end=True)
- Accepts tuples or Vector objects for positions
- Returns list of (x, y) tuples along the line
- compute_fov() was redundant with Grid.compute_fov()
- line() functionality now available as top-level bresenham()
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enables background Python threads to safely modify UI objects by
synchronizing with the render loop at frame boundaries.
Implementation:
- FrameLock class provides mutex/condvar synchronization
- GIL released during window.display() allowing background threads to run
- Safe window opens between frames for synchronized UI updates
- mcrfpy.lock() context manager blocks until safe window, then executes
- Main thread detection: lock() is a no-op when called from callbacks
or script initialization (already synchronized)
Usage:
import threading
import mcrfpy
def background_worker():
with mcrfpy.lock(): # Blocks until safe
player.x = new_x # Safe to modify UI
threading.Thread(target=background_worker).start()
The lock works transparently from any context - background threads get
actual synchronization, main thread calls (callbacks, init) get no-op.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implement the foundational HeightMap class for procedural generation:
- HeightMap(size, fill=0.0) constructor with libtcod backend
- Immutable size property after construction
- Scalar operations returning self for method chaining:
- fill(value), clear()
- add_constant(value), scale(factor)
- clamp(min=0.0, max=1.0), normalize(min=0.0, max=1.0)
Includes procedural generation spec document and unit tests.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
McRogueFace needs to accept callable objects (properties on C++ objects)
and also support subclassing (getattr on user objects). Only direct
properties were supported previously, now shadowing a callback by name
will allow custom objects to "just work".
- Added CallbackCache struct and is_python_subclass flag to UIDrawable.h
- Created metaclass for tracking class-level callback changes
- Updated all UI type init functions to detect subclasses
- Modified PyScene.cpp event dispatch to try subclass methods
Major Timer API improvements:
- Add `stopped` flag to Timer C++ class for proper state management
- Add `start()` method to restart stopped timers (preserves callback)
- Add `stop()` method that removes from engine but preserves callback
- Make `active` property read-write (True=start/resume, False=pause)
- Add `start=True` init parameter to create timers in stopped state
- Add `mcrfpy.timers` module-level collection (tuple of active timers)
- One-shot timers now set stopped=true instead of clearing callback
- Remove deprecated `setTimer()` and `delTimer()` module functions
Timer callbacks now receive (timer, runtime) instead of just (runtime).
Updated all tests to use new Timer API and callback signature.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace module-level audio functions with proper OOP API:
- mcrfpy.Sound: Wraps sf::SoundBuffer + sf::Sound for short effects
- mcrfpy.Music: Wraps sf::Music for streaming long tracks
- Both support: volume, loop, playing, duration, play/pause/stop
- Music adds position property for seeking
Add mcrfpy.keyboard singleton for real-time modifier state:
- shift, ctrl, alt, system properties (bool, read-only)
- Queries sf::Keyboard::isKeyPressed() directly
Add mcrfpy.__version__ = "1.0.0" for version identity
Remove old audio API entirely (no deprecation - unused in codebase):
- createSoundBuffer, loadMusic, playSound
- setMusicVolume, getMusicVolume, setSoundVolume, getSoundVolume
closes#66, closes#160, closes#164🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The benchmark API captures per-frame data from the game loop, which is
bypassed when using step()-based simulation control. This warning
informs users to use Python's time module for headless performance
measurement instead.
Also adds test_headless_benchmark.py which verifies:
- step() and screenshot() don't produce benchmark frames
- Wall-clock timing for headless operations
- Complex scene throughput measurement
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements Python-controlled simulation advancement for headless mode:
- Add mcrfpy.step(dt) to advance simulation by dt seconds
- step(None) advances to next scheduled event (timer/animation)
- Timers use simulation_time in headless mode for deterministic behavior
- automation.screenshot() now renders synchronously in headless mode
(captures current state, not previous frame)
This enables LLM agent orchestration (#156) by allowing:
- Set perspective, take screenshot, query LLM - all synchronous
- Deterministic simulation control without frame timing issues
- Event-driven advancement with step(None)
🤖 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>
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>
- 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>
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>
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>
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>
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.
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