Commit graph

58 commits

Author SHA1 Message Date
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
120b0aa2a4 Three things, sorry. SDL composite texture bugfix, sprite offset position, some Grid render efficiencies 2026-03-03 23:17:02 -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
726a9cf09d Mobile-"ish" emscripten support
Full screen "wasm-game" for viewport compatibility between desktop and
web interfaces. Viewport modes ("fit", "center", and "stretch") should
now work the same way under WASM/SDL and SFML. This should also enable
android or web-for-mobile aspect ratios to be supported more easily.
2026-02-09 08:40:34 -05:00
001cc6efd6 grid layer API modernization 2026-02-03 20:18:12 -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
da434dcc64 Rotation 2026-01-25 23:20:52 -05:00
486087b9cb Shaders 2026-01-25 21:04:01 -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
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
4bf590749c Alignment: reactive or automatically calculated repositioning of UIDrawables on their parent 2026-01-13 20:40:34 -05:00
bf8557798a Grid: add apply_threshold and apply_ranges for HeightMap (closes #199)
Add methods to apply HeightMap data to Grid walkable/transparent properties:
- apply_threshold(source, range, walkable, transparent): Apply properties
  to cells where HeightMap value is in the specified range
- apply_ranges(source, ranges): Apply multiple threshold rules in one pass

Features:
- Size mismatch between HeightMap and Grid raises ValueError
- Both methods return self for chaining
- Uses dynamic type lookup via module for HeightMap type checking
- First matching range wins in apply_ranges
- Cells not matching any range remain unchanged
- TCOD map is synced after changes for FOV/pathfinding

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:42:47 -05:00
b32f5af28c UIGridPathfinding: clear and separate A-star and Djikstra path systems 2026-01-10 22:09:45 -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
1f002e820c long -> intptr_t for casts. WIP: mingw cross-compilation for Windows (see #162) 2026-01-08 10:41:24 -05:00
7e47050d6f bugfixes for .parent property - partial #183 solution 2026-01-06 10:21:50 -05:00
a4c2c04343 bugfix: segfault in Grid.at() due to internal types not exported to module
After #184/#189 made GridPoint and GridPointState internal-only types,
code using PyObject_GetAttrString(mcrf_module, "GridPoint") would get
NULL and crash when dereferencing.

Fixed by using the type directly via &mcrfpydef::PyUIGridPointType
instead of looking it up in the module.

Affected functions:
- UIGrid::py_at()
- UIGridPointState::get_point()
- UIEntity::at()
- UIGridPointState_to_PyObject()

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 04:38:56 -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
02c512402e bugfix: segfault due to use of uninitialized Vector class reference 2026-01-05 10:31:41 -05:00
d2e4791f5a Positions are always mcrfpy.Vector, Vector/tuple/iterables expected as inputs, and for position-only inputs we permit x,y args to prevent requiring double-parens 2026-01-05 10:16:16 -05:00
9ab618079a .animate helper: create and start an animation directly on a target. Preferred use pattern; closes #175 2026-01-04 15:32:14 -05:00
357c2ac7d7 Animation fixes: 0-duration edge case, integer value bug resolution 2026-01-04 00:45:16 -05:00
f62362032e feat: Grid camera defaults to tile (0,0) at top-left + center_camera() method (#169)
Changes:
- Default Grid center now positions tile (0,0) at widget's top-left corner
- Added center_camera() method to center grid's middle tile at view center
- Added center_camera((tile_x, tile_y)) to position tile at top-left of widget
- Uses NaN as sentinel to detect if user provided center values in kwargs
- Animation-compatible: center_camera() just sets center property, no special state

Behavior:
- center_camera() → grid's center tile at view center
- center_camera((0, 0)) → tile (0,0) at top-left corner
- center_camera((5, 10)) → tile (5,10) at top-left corner

Before: Grid(size=(320,240)) showed 3/4 of content off-screen (center=0,0)
After: Grid(size=(320,240)) shows tile (0,0) at top-left (center=160,120)

Closes #169

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 17:22:26 -05:00
c9c7375827 refactor: Rename click kwarg to on_click for API consistency (closes #126)
BREAKING CHANGE: Constructor keyword argument renamed from `click` to
`on_click` for all UIDrawable types (Frame, Caption, Sprite, Grid, Line,
Circle, Arc).

Before: Frame(pos=(0,0), size=(100,100), click=handler)
After:  Frame(pos=(0,0), size=(100,100), on_click=handler)

The property name was already `on_click` - this makes the constructor
kwarg match, completing the callback naming standardization from #139.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 14:31:22 -05:00
7d57ce2608 feat: Implement SpatialHash for O(1) entity spatial queries (closes #115)
Add SpatialHash class for efficient spatial queries on entities:
- New SpatialHash.h/cpp with bucket-based spatial hashing
- Grid.entities_in_radius(x, y, radius) method for O(k) queries
- Automatic spatial hash updates on entity add/remove/move

Benchmark results at 2,000 entities:
- Single query: 16.2× faster (0.044ms → 0.003ms)
- N×N visibility: 104.8× faster (74ms → 1ms)

This enables efficient range queries for AI, visibility, and
collision detection without scanning all entities.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 00:44:07 -05:00
8f2407b518 fix: EntityCollection iterator O(n²) → O(n) with 100× speedup (closes #159)
Problem: EntityCollection iterator used index-based access on std::list,
causing O(n) traversal per element access (O(n²) total for iteration).

Root cause: Each call to next() started from begin() and advanced index steps:
  std::advance(l_begin, self->index-1);  // O(index) for linked list!

Solution:
- Store actual std::list iterators (current, end) instead of index
- Increment iterator directly in next() - O(1) operation
- Cache Entity and Iterator type lookups to avoid repeated dict lookups

Benchmark results (2,000 entities):
- Before: 13.577ms via EntityCollection
- After:  0.131ms via EntityCollection
- Speedup: 103×

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-28 00:30:31 -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
42fcd3417e refactor: Remove layer-related GridPoint properties, fix layer z-index
- 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>
2025-11-28 23:21:39 -05:00
a258613faa feat: Migrate Grid to user-driven layer rendering (closes #150)
- 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>
2025-11-28 23:04:09 -05:00
9469c04b01 feat: Implement chunk-based Grid rendering for large grids (closes #123)
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>
2025-11-28 22:33:16 -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
f769c6c5f5 fix: Remove O(n²) list-building from compute_fov() (closes #146)
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>
2025-11-28 21:26:32 -05:00
68f8349fe8 feat: Implement texture caching system with dirty flag optimization (closes #144)
- 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>
2025-11-28 19:30:24 -05:00
219a559c35 feat: Add dirty flag propagation to all UIDrawables and expand metrics API (#144, #104)
- Add markDirty() calls to setProperty() methods in:
  - UISprite: position, scale, sprite_index changes
  - UICaption: position, font_size, colors, text changes
  - UIGrid: position, size, center, zoom, color changes
  - UILine: thickness, position, endpoints, color changes
  - UICircle: radius, position, colors changes
  - UIArc: radius, angles, position, color changes
  - UIEntity: position changes propagate to parent grid

- Expand getMetrics() Python API to include detailed timing breakdown:
  - grid_render_time, entity_render_time, fov_overlay_time
  - python_time, animation_time
  - grid_cells_rendered, entities_rendered, total_entities

- Add comprehensive benchmark suite (tests/benchmarks/benchmark_suite.py):
  - 6 scenarios: empty, static UI, animated UI, mixed, deep hierarchy, grid stress
  - Automated metrics collection and performance assessment
  - Timing breakdown percentages

This enables proper dirty flag propagation for the upcoming texture caching
system (#144) and provides infrastructure for performance benchmarking (#104).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 15:44:09 -05:00
6c496b8732 feat: Implement comprehensive mouse event system
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>
2025-11-27 23:08:31 -05:00
52a655399e refactor: Rename click property to on_click (closes #139)
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>
2025-11-27 22:31:53 -05:00
e9b5a8301d feat: Add entity.grid property and fix auto-removal bug
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>
2025-11-27 21:08:31 -05:00
e3d8f54d46 feat: Implement Phase A UI hierarchy foundations (closes #122, #102, #116, #118)
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>
2025-11-27 16:33:17 -05:00
afcb54d9fe fix: Make UICollection/EntityCollection match Python list semantics
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>
2025-11-26 08:08:43 -05:00
deb5d81ab6 feat: Add .find() method to UICollection and EntityCollection
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>
2025-11-26 05:24:55 -05:00
4d6808e34d feat: Add UIDrawable children collection to Grid
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>
2025-11-25 21:52:37 -05:00
cc80964835 fix: update child class property overrides to use MCRF_PROPERTY macros
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)
2025-10-30 12:33:27 -04:00
e9e9cd2f81 feat: Add comprehensive profiling system with F3 overlay
Add real-time performance profiling infrastructure to monitor frame times,
render performance, and identify bottlenecks.

Features:
- Profiler.h: ScopedTimer RAII helper for automatic timing measurements
- ProfilerOverlay: F3-togglable overlay displaying real-time metrics
- Detailed timing breakdowns: grid rendering, entity rendering, FOV,
  Python callbacks, and animation updates
- Per-frame counters: cells rendered, entities rendered, draw calls
- Performance color coding: green (<16ms), yellow (<33ms), red (>33ms)
- Benchmark suite: static grid and moving entities performance tests

Integration:
- GameEngine: Integrated profiler overlay with F3 toggle
- UIGrid: Added timing instrumentation for grid and entity rendering
- Metrics tracked in ProfilingMetrics struct with 60-frame averaging

Usage:
- Press F3 in-game to toggle profiler overlay
- Run benchmarks with tests/benchmark_*.py scripts
- ScopedTimer automatically measures code block execution time

This addresses issue #104 (Basic profiling/metrics).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 00:45:44 -04:00
327da3622a feat: Change EntityCollection.remove() to accept Entity objects
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>
2025-10-23 13:17:45 -04:00
7aef412343 feat: Thread-safe FOV system with improved API
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>
2025-07-22 23:00:34 -04:00
b5eab85e70 Convert UIGrid perspective from index to weak_ptr<UIEntity>
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>
2025-07-21 23:47:21 -04:00
f4343e1e82 Squashed commit of the following: [alpha_presentable]
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
2025-07-15 21:30:49 -04:00
96857a41c6 hotfix: windows build, fresh docs 2025-07-10 16:34:46 -04:00