Refactors the main game loop to support both:
- Desktop: traditional blocking while(running) loop
- Browser: emscripten_set_main_loop_arg() callback (build-time conditional)
Changes:
- Add doFrame() method containing single-frame update logic
- Add isRunning() accessor for Emscripten callback
- run() now conditionally uses #ifdef __EMSCRIPTEN__ for loop selection
- Add emscriptenMainLoopCallback() static function
This is a prerequisite for Emscripten builds - browsers require
cooperative multitasking with callback-based frame updates.
Both normal and headless builds verified working.
Contributes to #158
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>
When a Grid has a perspective entity set (typically the player), the API
now respects field-of-view by default. Only entities visible to the
perspective entity are returned in /scene responses.
Changes:
- serialize_grid() filters entities using grid.is_in_fov()
- Added ?omniscient=true query param to bypass FOV filtering
- Response includes perspective info with hidden_entities count
- Updated README with perspective documentation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Implements a general-purpose HTTP API that exposes McRogueFace games
to external clients (LLMs, accessibility tools, Twitch integrations,
testing harnesses).
API endpoints:
- GET /scene - Full scene graph with all UI elements
- GET /affordances - Interactive elements with semantic labels
- GET /screenshot - PNG screenshot (binary or base64)
- GET /metadata - Game metadata for LLM context
- GET /wait - Long-poll for state changes
- POST /input - Inject clicks, keys, or affordance clicks
Key features:
- Automatic affordance detection from Frame+Caption+on_click patterns
- Label extraction from caption text with fallback to element.name
- Thread-safe scene access via mcrfpy.lock()
- Fuzzy label matching for click_affordance
- Full input injection via mcrfpy.automation
Usage: from api import start_server; start_server(8765)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add convertDrawableToPython() and convertEntityToPython() helper functions
- Add animationValueToPython() to convert AnimationValue to Python objects
- Rewrite triggerCallback() to pass meaningful data:
- target: The animated Frame/Sprite/Grid/Entity/etc.
- property: String property name like "x", "opacity", "fill_color"
- final_value: float, int, tuple (for colors/vectors), or string
- Update test_animation_callback_simple.py for new signature
closes#229
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ensureRenderTextureSize() helper that creates/resizes renderTexture to match game resolution
- Add renderTextureSize tracking member to detect when resize is needed
- Call helper in constructor and at start of render() to handle resolution changes
- Clamp maximum size to 4096x4096 (SFML texture limits)
- Only recreate texture when size actually changes (performance optimization)
closes#228
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Proof of concept for shader support on UIFrame:
- Add shader and shader_enabled members to UIFrame
- Add initializeTestShader() with hardcoded wave/glow fragment shader
- Add shader_enabled Python property for toggling
- Apply shader when drawing RenderTexture sprite
- Auto-update time uniform for animated effects
Also fixes position corruption when toggling RenderTexture usage:
- Standard rendering path now uses `position` as source of truth
- Prevents box position from staying at (0,0) after texture render
Test files:
- tests/shader_poc_test.py: Visual test of 6 render variants
- tests/shader_toggle_test.py: Regression test for position bug
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix#223: Use `position` instead of `box.getPosition()` for render_sprite
positioning. The box was being set to (0,0) for texture rendering and
never restored, causing frames to render at wrong positions.
- Fix#224: Add disableRenderTexture() method and call it when toggling
clip_children or cache_subtree off. This properly cleans up the texture
and prevents stale rendering.
- Fix#225: Improve dirty propagation in markContentDirty() to propagate
to parent even when already dirty, if the parent was cleared (rendered)
since last propagation. Prevents child changes from being invisible.
- Fix#226: Add fallback to standard rendering when RenderTexture can't
be created (e.g., zero-size frame). Prevents inconsistent state.
Closes#223, closes#224, closes#225, closes#226
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Game script updates (src/scripts/):
- Migrate Sound/Music API: createSoundBuffer() -> Sound() objects
- Migrate Scene API: sceneUI("name") -> scene.children
- Migrate Timer API: setTimer/delTimer -> Timer objects with stop()
- Fix callback signatures: (x,y,btn,event) -> (pos,btn,action) with Vector
- Fix grid_size unpacking: now returns Vector, use .x/.y with int()
Segfault fix (src/PyTimer.cpp):
- Remove direct map erase in PyTimer::stop() that caused iterator
invalidation when timer.stop() was called from within a callback
- Now just marks timer as stopped; testTimers() handles safe removal
The game now starts and runs without crashes.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
New Features:
- Scene Explorer window (F4) displays hierarchical tree of all scenes
- Shows UIDrawable hierarchy with type, name, and visibility status
- Click scene name to switch active scene
- Double-click drawables to toggle visibility
- Displays Python repr() for cached objects, enabling custom class debugging
- Entity display within Grid nodes
Bug Fixes:
- Fix PythonObjectCache re-registration: when retrieving objects from
collections, newly created Python wrappers are now re-registered in
the cache. Previously, inline-created objects (e.g.,
scene.children.append(Frame(...))) would lose their cache entry when
the temporary Python object was GC'd, causing repeated wrapper
allocation on each access.
- Fix console focus stealing: removed aggressive focus reclaim that
caused title bar flashing when clicking in Scene Explorer
Infrastructure:
- Add GameEngine::getSceneNames() to expose scene list for explorer
- Scene Explorer uses same enabled flag as console (ImGuiConsole::isEnabled())
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add tests/README.md documenting test structure and usage
- Move issue_*_test.py files to tests/regression/ (9 files)
- Move loose test_*.py files to tests/unit/ (18 files)
- tests/ root now contains only pytest infrastructure
Addresses #166
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>
UIDrawables placed in a Grid's children collection now have:
- grid_pos: Position in tile coordinates (get/set)
- grid_size: Size in tile coordinates (get/set)
Raises RuntimeError if accessed when parent is not a Grid.
UIGrid only gets grid_pos (grid_size conflicts with existing property).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- MouseButton enum (LEFT, RIGHT, MIDDLE, X1, X2) instead of "left", "right", etc.
- InputState enum (PRESSED, RELEASED) instead of "start", "end"
- Includes fallback to strings if enum creation fails
- Added proper reference counting for args tuple
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- #212: Add GRID_MAX (8192) validation to Grid, ColorLayer, TileLayer
- #213: Validate color components are in 0-255 range
- #214: Add null pointer checks before HeightMap operations
- #216: Change entities_in_radius(x, y, radius) to (pos, radius)
- #217: Fix Entity __repr__ to show actual draw_pos float values
Closes#212, closes#213, closes#214, closes#216, closes#217
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tests/demo/:
- cookbook_showcase.py: Interactive demo of cookbook recipes
- tutorial_showcase.py: Visual walkthrough of tutorial content
- tutorial_screenshots.py: Automated screenshot generation
- new_features_showcase.py: Demo of modern API features
- procgen_showcase.py: Procedural generation examples
- simple_showcase.py: Minimal working examples
Created during docs modernization to verify cookbook examples work.
🤖 Generated with Claude Code (https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Comprehensive guide to headless mode and mcrfpy.step() testing:
- Time control with step() (seconds, not milliseconds)
- Timer behavior and callback signatures
- Screenshot automation
- Test pattern comparison table
- LLM agent integration patterns
- Best practices for deterministic testing
Based on Frick's draft, updated with patterns from test refactoring.
🤖 Generated with Claude Code (https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Converts tests from Timer-based async patterns to step()-based sync
patterns, eliminating timeout issues in headless testing.
Refactored tests:
- simple_timer_screenshot_test.py
- test_animation_callback_simple.py
- test_animation_property_locking.py
- test_animation_raii.py
- test_animation_removal.py
- test_timer_callback.py
Also updates KNOWN_ISSUES.md with comprehensive documentation on
the step()-based testing pattern including examples and best practices.
🤖 Generated with Claude Code (https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- margin returns 0 when unset (effective default)
- horiz_margin/vert_margin return -1 (sentinel for unset)
🤖 Generated with Claude Code (https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>