Commit graph

258 commits

Author SHA1 Message Date
ff46043023 Add Game-to-API Bridge for external client integration
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>
2026-01-29 23:08:26 -05:00
b47132b052 Add MouseButton.MIDDLE, SCROLL_UP, SCROLL_DOWN support
- Register middle mouse button in PyScene (was missing, events were dropped)
- Add SCROLL_UP (10) and SCROLL_DOWN (11) to MouseButton enum
- Update button string-to-enum conversion in PyCallable and PyScene
- Legacy string comparisons work: MouseButton.SCROLL_UP == "wheel_up"

closes #231, closes #232

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 23:22:16 -05:00
5a1948699e Update documentation for API changes #229, #230, #184
CLAUDE.md updates:
- Fix Python version 3.12 -> 3.14
- Update keypressScene -> scene.on_key pattern
- Add API examples for new callback signatures
- Document animation callbacks (target, prop, value)
- Document hover callbacks (position-only)
- Document enum types (Key, MouseButton, InputState)

stubs/mcrfpy.pyi updates:
- Add Key, MouseButton, InputState, Easing enum classes
- Fix Drawable hover callback signatures per #230
- Fix Grid cell callback signatures per #230
- Fix Scene.on_key signature to use enums per #184
- Update Animation class with correct callback signature per #229
- Add deprecation notes to keypressScene, setTimer, delTimer

Regenerated docs:
- API_REFERENCE_DYNAMIC.md
- api_reference_dynamic.html
- mcrfpy.3 man page

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 19:20:04 -05:00
55f6ea9502 Add cookbook examples with updated callback signatures for #229, #230
Cookbook structure:
- lib/: Reusable component library (Button, StatBar, AnimationChain, etc.)
- primitives/: Demo apps for individual components
- features/: Demo apps for complex features (animation chaining, shaders)
- apps/: Complete mini-applications (calculator, dialogue system)
- automation/: Screenshot capture utilities

API signature updates applied:
- on_enter/on_exit/on_move callbacks now only receive (pos) per #230
- on_cell_enter/on_cell_exit callbacks only receive (cell_pos) per #230
- Animation chain library uses Timer-based sequencing (unaffected by #229)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 18:58:25 -05:00
2daebc84b5 Simplify on_enter/on_exit callbacks to position-only signature
BREAKING CHANGE: Hover callbacks now take only (pos) instead of (pos, button, action)

- Add PyHoverCallable class for on_enter/on_exit/on_move callbacks (position-only)
- Add PyCellHoverCallable class for on_cell_enter/on_cell_exit callbacks
- Change UIDrawable member types from PyClickCallable to PyHoverCallable
- Update PyScene::do_mouse_hover() to call hover callbacks with only position
- Add tryCallPythonMethod overload for position-only subclass method calls
- Update UIGrid::fireCellEnter/fireCellExit to use position-only signature
- Update all tests for new callback signatures

New callback signatures:
| Callback       | Old                      | New        |
|----------------|--------------------------|------------|
| on_enter       | (pos, button, action)    | (pos)      |
| on_exit        | (pos, button, action)    | (pos)      |
| on_move        | (pos, button, action)    | (pos)      |
| on_cell_enter  | (cell_pos, button, action)| (cell_pos)|
| on_cell_exit   | (cell_pos, button, action)| (cell_pos)|
| on_click       | unchanged                | unchanged  |
| on_cell_click  | unchanged                | unchanged  |

closes #230

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 17:36:02 -05:00
e14f3cb9fc Animation callbacks now pass (target, property, value) instead of (None, None)
- 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>
2026-01-28 17:35:47 -05:00
214037892e Fix UIGrid RenderTexture sizing - use game resolution instead of hard-coded 1080p
- 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>
2026-01-28 17:35:34 -05:00
d12bfd224c cell, scene callbacks support for derived classes + enum args 2026-01-27 22:38:37 -05:00
c7cf3f0e5b standardize mouse callback signature on derived classes 2026-01-27 20:42:50 -05:00
86bfebefcb Fix: Derivable drawable types participate in garbage collector cycle detection 2026-01-27 13:21:10 -05:00
16b5508233 Fix borrowed reference return in some callbacks 2026-01-27 10:43:10 -05:00
da434dcc64 Rotation 2026-01-25 23:20:52 -05:00
486087b9cb Shaders 2026-01-25 21:04:01 -05:00
41d551e6e1 Shader POC: Add shader_enabled property to UIFrame (#106)
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>
2026-01-24 20:28:53 -05:00
475fe94148 Version bump: 0.2.2-prerelease-7drl2026 (9a241c9) -> 0.2.3-prerelease-7drl2026 2026-01-23 22:07:53 -05:00
a3a0618524 rebuild docs 2026-01-23 20:49:11 -05:00
f30e5bb8a1 libtcod experiments. Following feature branch API 2026-01-23 20:48:46 -05:00
3fea6418ff Fix UIFrame RenderTexture positioning and toggling issues
- 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>
2026-01-22 22:54:50 -05:00
c23da11d7d Modernize Crypt of Sokoban demo game and fix timer segfault
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>
2026-01-21 23:47:46 -05:00
5e45ab015c Add ImGui Scene Explorer (F4) for runtime object inspection (#136)
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>
2026-01-21 23:26:33 -05:00
4be2502a10 Fix #161: Update Grid, GridPoint, GridPointState stubs to match current API
- Grid: Update constructor (pos, size, grid_size, texture, ...) and add all
  current properties (zoom, center, layers, FOV, cell events, etc.)
- Grid: Add all methods (find_path, compute_fov, add_layer, entities_in_radius, etc.)
- GridPoint: Replace incorrect properties (texture_index, solid, color) with
  actual API (walkable, transparent, entities, grid_pos)
- GridPointState: Replace incorrect properties with actual API (visible, discovered, point)
- Add missing types: ColorLayer, TileLayer, FOV, AStarPath, DijkstraMap,
  HeightMap, NoiseSource, BSP

Closes #161

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 21:47:26 -05:00
165db91b8d Organize test suite: add README, move loose tests to proper directories
- 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>
2026-01-21 21:34:22 -05:00
a4217b49d7 README.md updates: closes #168 2026-01-21 21:34:13 -05:00
0207595db0 imgui console: use JetBrains (redistributable font); use font size, not pixel scaling 2026-01-20 21:59:13 -05:00
4ead2f25fe Fix #215: Replace mcrfpy.libtcod with mcrfpy.bresenham()
- 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>
2026-01-20 00:10:13 -05:00
257e52327b Fix #219: Add threading support with mcrfpy.lock() context manager
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>
2026-01-19 23:37:49 -05:00
14a6520593 Fix #221: Add grid_pos and grid_size properties for Grid children
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>
2026-01-19 22:23:47 -05:00
6c5992f1c1 Fix #222: on_click callbacks now receive enum types instead of strings
- 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>
2026-01-19 22:03:06 -05:00
ff8e220ee0 Sync heightmap API with libtcod/libtcod #175 convolution feature branch
Rename TCOD_heightmap_kernel_transform_hm -> TCOD_heightmap_kernel_transform_out

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 21:49:31 -05:00
39a12028a0 using custom libtcod-headless 2.2.2 feature branch: fixes to convolution, gradient method 2026-01-19 14:10:07 -05:00
09fa4f4665 emove register_keyboard(callable) from scene - all callbacks are standard attributes now. on_resize now returns a vector 2026-01-17 23:38:24 -05:00
9a241c99d7 Version bump: 0.2.1-prerelease-7drl2026 (baa7ee3) -> 0.2.2-prerelease-7drl2026 2026-01-17 10:38:26 -05:00
baa7ee354b Add input validation and fix Entity repr
- #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>
2026-01-16 19:18:27 -05:00
Frick
a1b692bb1f Add cookbook and tutorial showcase demos
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>
2026-01-15 04:06:24 +00:00
Frick
23afae69ad Add API verification test suite and documentation
tests/docs/:
- API_FINDINGS.md: Comprehensive migration guide from deprecated to modern API
- test_*.py: 9 executable tests verifying actual runtime behavior
- screenshots/: Visual verification of working examples

tests/conftest.py:
- Add 'docs' and 'demo' to pytest collection paths

Key findings documented:
- Entity uses grid_pos= not pos=
- Scene API: Scene() + activate() replaces createScene/setScene
- scene.children replaces sceneUI()
- scene.on_key replaces keypressScene()
- mcrfpy.current_scene (property) replaces currentScene() (function)
- Timer callback signature: (timer, runtime)
- Opacity animation does NOT work on Frame (documented bug)

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-15 04:05:32 +00:00
Frick
be450286f8 Refactor 11 more tests to mcrfpy.step() pattern
Converted from Timer-based async to step()-based sync:
- test_simple_callback.py
- test_empty_animation_manager.py
- test_frame_clipping.py
- test_frame_clipping_advanced.py
- test_grid_children.py
- test_color_helpers.py
- test_no_arg_constructors.py
- test_properties_quick.py
- test_simple_drawable.py
- test_python_object_cache.py
- WORKING_automation_test_example.py

Only 4 tests remain with Timer-based patterns (2 are headless detection
tests that may require special handling).

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Frack <frack@goblincorps.dev>
Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 03:09:47 +00:00
Frick
bb86cece2b Add headless-automation.md explanation document
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>
2026-01-14 03:04:48 +00:00
Frick
4528ece0a7 Refactor timing tests to use mcrfpy.step() for synchronous execution
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>
2026-01-14 02:56:21 +00:00
Frick
f063d0af0c Fix alignment_test.py margin default expectations
- 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>
2026-01-14 02:02:08 +00:00
Frick
4579be2791 Test suite modernization: pytest wrapper and runner fixes
- Add LD_LIBRARY_PATH auto-configuration in run_tests.py
- Add --timeout and --quiet command-line flags
- Create pytest wrapper (conftest.py, test_mcrogueface.py) for IDE integration
- Configure pytest.ini to avoid importing mcrfpy modules
- Document known issues: 120/179 passing, 40 timeouts, 19 failures

🤖 Generated with Claude Code (https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 01:54:31 +00:00
65b5ecc5c7 Stubs definition update 2026-01-13 20:41:38 -05:00
b22dfe9524 Djikstra to Heightmap: convert pathfinding data into a heightmap for use in procedural generation processes 2026-01-13 20:41:23 -05:00
4bf590749c Alignment: reactive or automatically calculated repositioning of UIDrawables on their parent 2026-01-13 20:40:34 -05:00
73230989ad Cookbook: draft docs 2026-01-13 19:42:37 -05:00
8628ac164b BSP: add room adjacency graph for corridor generation (closes #210)
New features:
- bsp.adjacency[i] returns tuple of neighbor leaf indices
- bsp.get_leaf(index) returns BSPNode by leaf index (O(1) lookup)
- node.leaf_index returns this leaf's index (0..n-1) or None
- node.adjacent_tiles[j] returns tuple of Vector wall tiles bordering neighbor j

Implementation details:
- Lazy-computed adjacency cache with generation-based invalidation
- O(n²) pairwise adjacency check on first access
- Wall tiles computed per-direction (not symmetric) for correct perspective
- Supports 'in' operator: `5 in leaf.adjacent_tiles`

Code review fixes applied:
- split_once now increments generation to invalidate cache
- Wall tile cache uses (self, neighbor) key, not symmetric
- Added sq_contains for 'in' operator support
- Documented wall tile semantics (tiles on THIS leaf's boundary)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 23:43:57 -05:00
5a86602789 HeightMap - kernel_transform (#198) 2026-01-12 21:42:34 -05:00
2b12d1fc70 Update to combination operations (#194) - allowing targeted, partial regions on source or target 2026-01-12 20:56:39 -05:00
e5d0eb4847 Noise, combination, and sampling: first pass at #207, #208, #194, #209 2026-01-12 19:01:20 -05:00
6caf3dcd05 BSP: add safety features and API improvements (closes #202, #203, #204, #205, #206)
Safety improvements:
- Generation counter detects stale BSPNode references after clear()/split_recursive()
- GRID_MAX validation prevents oversized BSP trees
- Depth parameter capped at 16 to prevent resource exhaustion
- Iterator checks generation to detect invalidation during mutation

API improvements:
- Changed constructor from bounds=((x,y),(w,h)) to pos=(x,y), size=(w,h)
- Added pos and size properties alongside bounds
- BSPNode __eq__ compares underlying pointers for identity
- BSP __iter__ as shorthand for leaves()
- BSP __len__ returns leaf count

Tests:
- Added tests for stale node detection, GRID_MAX validation, depth cap
- Added tests for __len__, __iter__, and BSPNode equality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 07:59:31 -05:00
8699bba9e6 BSP: add Binary Space Partitioning for procedural dungeon generation
Implements #202, #203, #204, #205; partially implements #206:
- BSP class: core tree structure with bounds, split_once, split_recursive, clear
- BSPNode class: lightweight node reference with bounds, level, is_leaf,
  split_horizontal, split_position; navigation via left/right/parent/sibling;
  contains() and center() methods
- Traversal enum: PRE_ORDER, IN_ORDER, POST_ORDER, LEVEL_ORDER, INVERTED_LEVEL_ORDER
- BSP iteration: leaves() for leaf nodes only, traverse(order) for all nodes
- BSP query: find(pos) returns deepest node containing position
- BSP.to_heightmap(): converts BSP to HeightMap with select, shrink, value options

Note: #206's BSPMap subclass deferred - to_heightmap returns plain HeightMap.
The HeightMap already has all necessary operations (inverse, threshold, etc.)
for procedural generation workflows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 07:02:54 -05:00