Commit graph

81 commits

Author SHA1 Message Date
109bc21d90 Grid/GridView API unification: mcrfpy.Grid now returns GridView, closes #252
mcrfpy.Grid() now creates a GridView that internally owns a GridData (UIGrid).
The old UIGrid type is renamed to _GridData (internal). Attribute access on Grid
delegates to the underlying UIGrid via tp_getattro/tp_setattro, so all existing
Grid properties (grid_w, grid_h, entities, cells, layers, etc.) work transparently.

Key changes:
- GridView init has two modes: factory (Grid(grid_size=...)) and explicit view
  (Grid(grid=existing_grid, ...)) for future multi-view support
- Entity.grid getter returns GridView wrapper via owning_view back-reference
- Entity.grid setter accepts GridView objects
- GridLayer set_grid handles GridView (extracts underlying UIGrid)
- UIDrawable::removeFromParent handles UIGRIDVIEW type correctly
- UIFrame children init accepts GridView objects
- Animation system supports GridView (center, zoom, shader.* properties)
- PythonObjectCache registration preserves subclass identity
- All 263 tests pass (100%)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 04:34:11 -04:00
4b13e5f5db Phase 4.2: Add GridView UIDrawable type (addresses #252)
GridView is a new UIDrawable that renders a GridData object independently.
Multiple GridViews can reference the same Grid for split-screen, minimap,
or different camera/zoom perspectives on shared grid state.

- New files: UIGridView.h/cpp with full rendering pipeline (copied from
  UIGrid::render, adapted to use grid_data pointer)
- Add UIGRIDVIEW to PyObjectsEnum
- Add UIGRIDVIEW cases to all switch(derived_type()) sites:
  Animation.cpp, UICollection.cpp, UIDrawable.cpp, McRFPy_API.cpp,
  ImGuiSceneExplorer.cpp
- Python type: mcrfpy.GridView(grid=, pos=, size=, zoom=, fill_color=)
- Properties: grid, center, zoom, fill_color, texture
- Register type with metaclass for callback support

All 258 existing tests pass. New GridView test suite added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 08:41:44 -04:00
2f1e472245 Phase 2: Entity data model extensions for behavior system
- Add Behavior enum (IDLE..FLEE, 11 values) and Trigger enum (DONE,
  BLOCKED, TARGET) as runtime IntEnum classes (closes #297, closes #298)
- Add entity label system: labels property (frozenset), add_label(),
  remove_label(), has_label(), constructor kwarg (closes #296)
- Add cell_pos integer logical position decoupled from float draw_pos;
  grid_pos now aliases cell_pos; SpatialHash::updateCell() for cell-based
  bucket management; FOV/visibility uses cell_position (closes #295)
- Add step callback and default_behavior properties to Entity for
  grid.step() turn management (closes #299)
- Update updateVisibility, visible_entities, ColorLayer::updatePerspective
  to use cell_position instead of float position

BREAKING: grid_pos no longer derives from float x/y position. Use
cell_pos/grid_pos for logical position, draw_pos for render position.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 22:05:06 -04:00
71eb01c950 Replace PyObject_GetAttrString with direct type references
Replace ~230 occurrences of PyObject_GetAttrString(McRFPy_API::mcrf_module, "TypeName")
with direct &mcrfpydef::PyXxxType references across 32 source files.

Each PyObject_GetAttrString call returns a new reference. When used inline
in PyObject_IsInstance(), that reference was immediately leaked. When used
for tp_alloc, the reference required careful Py_DECREF management that was
often missing on error paths.

Direct type references are compile-time constants that never need reference
counting, eliminating ~230 potential leak sites and removing ~100 lines of
Py_DECREF/Py_XDECREF cleanup code.

Also adds extractDrawable() helper in UICollection.cpp to replace repeated
8-way type-check-and-extract chains with a single function call.

Closes #267, closes #268

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 23:18:42 -05:00
e2d3e56968 Cross-platform persistent save directory (IDBFS on WASM, filesystem on desktop)
Game code uses standard Python file I/O to mcrfpy.save_dir with no platform
branching. On WASM, builtins.open() is monkeypatched so writes to /save/
auto-sync IDBFS on close, making persistence transparent.

API: mcrfpy.save_dir (str), mcrfpy._sync_storage() (auto-called on WASM)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 20:42:44 -05:00
97dbec9106 Add SoundBuffer type: procedural audio, sfxr synthesis, DSP effects
New SoundBuffer Python type enables procedural audio generation:
- Tone synthesis (sine, square, saw, triangle, noise) with ADSR envelopes
- sfxr retro sound effect engine (7 presets, 24 params, mutation, seeding)
- DSP effects chain: pitch_shift, low/high pass, echo, reverb,
  distortion, bit_crush, normalize, reverse, slice
- Composition: concat (with crossfade overlap) and mix
- Sound() now accepts SoundBuffer or filename string
- Sound gains pitch property and play_varied() method
- Platform stubs for HeadlessTypes and SDL2Types (loadFromSamples, pitch)
- Interactive demo: sfxr clone UI + Animalese speech synthesizer
- 62 unit tests across 6 test files (all passing)

Refs #251

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 18:58:11 -05:00
bb72040396 Migrate static PyTypeObject to inline, delete PyTypeCache workarounds
All 27 PyTypeObject declarations in namespace mcrfpydef headers changed
from `static` to `inline` (C++17), ensuring a single global instance
across translation units. This fixes the root cause of stale-type-pointer
segfaults where only the McRFPy_API.cpp copy was PyType_Ready'd.

Replaced ~20 PyTypeCache call sites and 2 PyRAII::PyTypeRef lookups with
direct &mcrfpydef::Type references. Deleted PyTypeCache.h/.cpp,
PyObjectUtils.h, and PyRAII.h (all were workarounds for the static bug).

228/228 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 20:58:09 -05:00
6fdf7279ce Shade (merchant-shade.itch.io) entity animation tests 2026-02-16 20:19:39 -05:00
de7778b147 LDtk import support 2026-02-07 11:34:38 -05:00
322beeaf78 add __ne__ support to enum types for input 2026-02-06 21:43:52 -05:00
992ea781cb Voxel functionality extension 2026-02-05 12:52:18 -05:00
3e6b6a5847 voxel example 2026-02-05 10:49:31 -05:00
b85f225789 billboards 2026-02-04 20:47:51 -05:00
544c44ca31 glTF model loading 2026-02-04 19:35:48 -05:00
f4c9db8436 3D entities 2026-02-04 17:45:12 -05:00
63008bdefd pathfinding on heightmap 2026-02-04 16:36:21 -05:00
e277663ba0 3D viewport, milestone 1 2026-02-04 13:33:14 -05:00
d8fec5fea0 DiscreteMap class - mask for operations or uint8 tile data 2026-02-03 20:36:42 -05:00
2fb29a102e Animation and Scene clean up functions. Playground build target 2026-02-01 21:17:29 -05:00
3b27401f29 Remove debugging output 2026-02-01 16:40:23 -05:00
c5cc022aa2 Add SDL2+OpenGL ES 2 renderer backend for Emscripten/WebGL
Implements Phase 1 of renderer abstraction plan:
- SDL2Types.h: SFML-compatible type stubs in sf:: namespace
- SDL2Renderer.h/cpp: OpenGL ES 2 rendering implementation
- EmscriptenStubs.cpp: Stubs for missing POSIX functions (wait3, wait4, wcsftime)

Build system changes:
- Add MCRF_SDL2 compile-time backend selection
- Add Emscripten SDL2 link options (-sUSE_SDL=2, -sFULL_ES2=1)
- Fix LONG_BIT mismatch for Emscripten in pyport.h

Code changes for SDL2/headless compatibility:
- Guard ImGui includes with !MCRF_HEADLESS && !MCRF_SDL2
- Defer GL shader initialization until after context creation

Current status: Python runs in browser, rendering WIP (canvas sizing issues)

Build commands:
  emcmake cmake -DMCRF_SDL2=ON -B build-emscripten
  emmake make -C build-emscripten

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 11:13:15 -05:00
8c3128e29c WASM Python integration milestone - game.py runs in browser
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>
2026-01-31 05:15:11 -05:00
7621ae35bb Add MCRF_HEADLESS compile-time build option for #158
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>
2026-01-30 23:09:07 -05:00
486087b9cb Shaders 2026-01-25 21:04:01 -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
4bf590749c Alignment: reactive or automatically calculated repositioning of UIDrawables on their parent 2026-01-13 20:40:34 -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
e5d0eb4847 Noise, combination, and sampling: first pass at #207, #208, #194, #209 2026-01-12 19:01:20 -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
c095be4b73 HeightMap: core class with scalar operations (closes #193)
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>
2026-01-11 20:07:55 -05:00
b32f5af28c UIGridPathfinding: clear and separate A-star and Djikstra path systems 2026-01-10 22:09:45 -05:00
9eacedc624 Input Enums instead of strings. 2026-01-10 21:31:20 -05:00
d6ef29f3cd Grid code quality improvements
* Grid [x, y] subscript - convenience for `.at()`
* Extract UIEntityCollection - cleanup of UIGrid.cpp
* Thread-safe type cache - PyTypeCache
* Exception-safe extend() - validate before modify
2026-01-10 08:37:31 -05:00
a77ac6c501 Monkey Patch support + Robust callback tracking
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
2026-01-09 21:37:23 -05:00
1438044c6a mingw toolchain and final fixes for Windows. Closes #162 2026-01-08 21:16:27 -05:00
75127ac9d1 mcrfpy.Mouse: a new class built for symmetry with mcrfpy.Keyboard. Closes #186 2026-01-06 21:39:01 -05:00
b0b17f4633 timer fixes: timers managed by engine can run in the background. Closes #180 2026-01-06 20:13:51 -05:00
2c20455003 support for Scene object as parent, from Python: closes #183 2026-01-06 14:04:53 -05:00
f9b6cdef1c Python API improvements: Vectors, bounds, window singleton, hidden types
- #177: GridPoint.grid_pos property returns (x, y) tuple
- #179: Grid.grid_size returns Vector instead of tuple
- #181: Grid.center returns Vector instead of tuple
- #182: Caption.size/w/h read-only properties for text dimensions
- #184: mcrfpy.window singleton for window access
- #185: Removed get_bounds() method, use .bounds property instead
- #188: bounds/global_bounds return (pos, size) as pair of Vectors
- #189: Hide internal types from module namespace (iterators, collections)

Also fixed critical bug: Changed static PyTypeObject to inline in headers
to ensure single instance across translation units (was causing segfaults).

Closes #177, closes #179, closes #181, closes #182, closes #184, closes #185, closes #188, closes #189

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 23:00:48 -05:00
d878c8684d Easing functions as enum 2026-01-04 12:59:28 -05:00
5d41292bf6 Timer refactor: stopwatch-like semantics, mcrfpy.timers collection closes #173
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>
2026-01-03 22:09:18 -05:00
fc95fc2844 scene transitions via Scene object 2026-01-03 13:53:18 -05:00
d7e34a3f72 Remove old scene management methods 2026-01-03 11:01:42 -05:00
c025cd7da3 feat: Add Sound/Music classes, keyboard state, version (#66, #160, #164)
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>
2025-12-29 16:24:27 -05:00
71c91e19a5 feat: Add consistent Scene API with module-level properties (closes #151)
Replaces module-level scene functions with more Pythonic OO interface:

Scene class changes:
- Add `scene.children` property (replaces get_ui() method)
- Add `scene.on_key` getter/setter (matches on_click pattern)
- Remove get_ui() method

Module-level properties:
- Add `mcrfpy.current_scene` (getter returns Scene, setter activates)
- Add `mcrfpy.scenes` (read-only tuple of all Scene objects)

Implementation uses custom module type (McRFPyModuleType) inheriting
from PyModule_Type with tp_setattro for property assignment support.

New usage:
  scene = mcrfpy.Scene("game")
  mcrfpy.current_scene = scene
  scene.on_key = handler
  ui = scene.children

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-22 22:15:03 -05:00
f2f8d6422f Add warning when starting benchmark in headless mode
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>
2025-12-01 22:20:19 -05:00
60ffa68d04 feat: Add mcrfpy.step() and synchronous screenshot for headless mode (closes #153)
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>
2025-12-01 21:56:47 -05:00
018e73590f feat: Implement FOV enum and layer draw_fov for #114 and #113
Phase 1 - FOV Enum System:
- Create PyFOV.h/cpp with mcrfpy.FOV IntEnum (BASIC, DIAMOND, SHADOW, etc.)
- Add mcrfpy.default_fov module property initialized to FOV.BASIC
- Add grid.fov and grid.fov_radius properties for per-grid defaults
- Remove deprecated module-level FOV_* constants (breaking change)

Phase 2 - Layer Operations:
- Implement ColorLayer.fill_rect(pos, size, color) for rectangle fills
- Implement TileLayer.fill_rect(pos, size, index) for tile rectangle fills
- Implement ColorLayer.draw_fov(source, radius, fov, visible, discovered, unknown)
  to paint FOV-based visibility on color layers using parent grid's TCOD map

The FOV enum uses Python's IntEnum for type safety while maintaining
backward compatibility with integer values. Tests updated to use new API.

Addresses #114 (FOV enum), #113 (layer operations)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 15:18:10 -05:00
4b05a95efe feat: Add dynamic layer system for Grid (closes #147)
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>
2025-11-28 21:35:38 -05:00