to_bytes() returns raw uint8 cell data as Python bytes object.
from_bytes(data, size, enum=None) is a classmethod that constructs
a new DiscreteMap from serialized data with dimension validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
UIFrame, UICaption, and UISprite now accept "pos" as an alias for "position"
in the animation property system. UICaption and UISprite gain Vector2f
setProperty/getProperty overrides enabling animate("pos", (x, y), duration).
Color compound animation (fill_color, outline_color) was already supported.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Breaking API change: removes 4 camelCase function aliases from the mcrfpy
module. The snake_case equivalents (set_scale, find_all, get_metrics,
set_dev_console) remain and are the canonical API going forward.
- Removed setScale, findAll, getMetrics, setDevConsole from mcrfpyMethods[]
- Updated game scripts to use snake_case names
- Updated test scripts to use snake_case names
- Removed camelCase entries from type stubs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removed custom __eq__/__ne__ that allowed comparing enums to legacy string
names (e.g., Key.ESCAPE == "Escape"). Removed _legacy_names dicts and
to_legacy_string() functions. Kept from_legacy_string() in PyKey.cpp as
it's used by C++ event dispatch. Updated ~50 Python test/demo/cookbook
files to use enum members instead of string comparisons. Also updates
grid.position -> grid.pos in files that had both types of changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Grid.position was a redundant alias for Grid.pos. Removed get_position/
set_position functions, getsetters entry, and setProperty/getProperty/
hasProperty branches. Updated all tests to use grid.pos.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
sprite_number was a legacy alias for sprite_index. All code should use
sprite_index directly. Removed from getsetters, setProperty/getProperty/
hasProperty in UISprite and UIEntity, animation property handling, and
type stubs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add `collide` kwarg to Grid.find_path() and Grid.get_dijkstra_map() that
treats entities bearing a given label as impassable obstacles via
mark-and-restore on the TCOD walkability map. Dijkstra cache key now
includes collide label for separate caching. Add Entity.find_path()
convenience method that delegates to the grid.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
- SoundBuffer.gain(factor): new DSP method for amplitude scaling before
mixing (0.5 = half volume, 2.0 = double, clamped to int16 range)
- Fix sfxr square/saw waveform artifacts: phase now wraps at period
boundary instead of growing unbounded; noise buffer refreshes per period
- Fix PySound construction from SoundBuffer on SDL2 backend: use
loadFromSamples() directly instead of copy-assign (deleted on SDL2)
- Add Image::create(w, h, pixels) overload to HeadlessTypes and
SDL2Types for pixel data initialization
- Waveform test suite (62 lines)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extends the shade_sprite module (for merchant-shade.itch.io character
sprite sheets) with procedural faction generation and asset management:
- FactionGenerator: seed-based faction recipes with Biome, Element,
Aesthetic, and RoleType enums for thematic variety
- AssetLibrary: filesystem scanner that discovers and categorizes
layer PNGs by type (skins, clothes, hair, etc.)
- TextureCache: avoids redundant disk I/O when building many variants
- CharacterAssembler: HSL shift documentation, method improvements
- Demo expanded to 6 interactive scenes (animation viewer, HSL recolor,
character gallery, faction generator, layer compositing, equipment)
- EVALUATION.md: 7DRL readiness assessment of the full module
- 329-line faction generation test suite
Assets themselves are not included -- sprite sheets are external
dependencies, some under commercial license.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaced the NotImplementedError stub with a full animation
implementation. Entity3D now supports animating: x, y, z,
world_x, world_y, world_z, rotation, rot_y, scale, scale_x,
scale_y, scale_z, sprite_index, visible.
Added Entity3D as a third target type in the Animation system
(alongside UIDrawable and UIEntity), with startEntity3D(),
applyValue(Entity3D*), and proper callback support.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EntityCollection3D now has API parity with UIEntityCollection:
- pop(index=-1): Remove and return entity at index
- find(name): Search by entity name, return Entity3D or None
- extend(iterable): Append multiple Entity3D objects
Also adds `name` property to Entity3D for use with find().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
screen_to_world() previously only intersected the Y=0 plane.
Now accepts an optional y_plane parameter (default 0.0) for
intersecting arbitrary horizontal planes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The root cause was PyViewport3DType being declared `static` in
Viewport3D.h, creating per-translation-unit copies. Entity3D.cpp's
copy was never passed through PyType_Ready, causing segfaults when
tp_alloc was called.
Changed `static` to `inline` (matching PyEntity3DType and
PyModel3DType patterns), and implemented get_viewport using the
standard type->tp_alloc pattern.
Co-Authored-By: Claude Opus 4.6 <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 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>
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>
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>
TileLayer (closes#200):
- apply_threshold(source, range, tile): Set tile index where heightmap value is in range
- apply_ranges(source, ranges): Apply multiple tile assignments in one pass
ColorLayer (closes#201):
- apply_threshold(source, range, color): Set fixed color where value is in range
- apply_gradient(source, range, color_low, color_high): Interpolate colors based on value
- apply_ranges(source, ranges): Apply multiple color assignments (fixed or gradient)
All methods return self for chaining. HeightMap size must match layer dimensions.
Later ranges override earlier ones if overlapping. Cells not matching any range are unchanged.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Rename parameters for clearer semantics:
- dig_hill: depth -> target_height
- dig_bezier: start_depth/end_depth -> start_height/end_height
The libtcod "dig" functions set terrain TO a target height, not
relative to current values. "target_height" makes this clearer.
Also add warnings for likely user errors:
- add_hill/dig_hill/dig_bezier with radius <= 0 (no-op)
- smooth with iterations <= 0 already raises ValueError
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>