Compare commits

..

54 commits

Author SHA1 Message Date
64ffe1f699 Draft tutorials 2025-07-09 22:34:43 -04:00
9be3161a24 docs: update ROADMAP with FOV, A* pathfinding, and GUI text widget completions
- Mark TCOD Integration Sprint as complete
- Document FOV system with perspective rendering implementation
- Update UIEntity pathfinding status to complete with A* and caching
- Add comprehensive achievement entry for July 10 work
- Reflect current engine capabilities accurately

The engine now has all core roguelike mechanics:
- Field of View with per-entity visibility
- Pathfinding (both Dijkstra and A*)
- Text input for in-game consoles
- Performance optimizations throughout

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-09 22:19:38 -04:00
d13153ddb4 feat(engine): implement perspective FOV, pathfinding, and GUI text widgets
Major Engine Enhancements:
- Complete FOV (Field of View) system with perspective rendering
  - UIGrid.perspective property for entity-based visibility
  - Three-layer overlay colors (unexplored, explored, visible)
  - Per-entity visibility state tracking
  - Perfect knowledge updates only for explored areas

- Advanced Pathfinding Integration
  - A* pathfinding implementation in UIGrid
  - Entity.path_to() method for direct pathfinding
  - Dijkstra maps for multi-target pathfinding
  - Path caching for performance optimization

- GUI Text Input Widgets
  - TextInputWidget class with cursor, selection, scrolling
  - Improved widget with proper text rendering and input handling
  - Example showcase of multiple text input fields
  - Foundation for in-game console and chat systems

- Performance & Architecture Improvements
  - PyTexture copy operations optimized
  - GameEngine update cycle refined
  - UIEntity property handling enhanced
  - UITestScene modernized

Test Suite:
- Interactive visibility demos showing FOV in action
- Pathfinding comparison (A* vs Dijkstra)
- Debug utilities for visibility and empty path handling
- Sizzle reel demo combining pathfinding and vision
- Multiple text input test scenarios

This commit brings McRogueFace closer to a complete roguelike engine
with essential features like line-of-sight, intelligent pathfinding,
and interactive text input capabilities.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-09 22:18:29 -04:00
051a2ca951 feat(demos): enhance interactive pathfinding demos with entity.path_to()
- dijkstra_interactive_enhanced.py: Animation along paths with smooth movement
  - M key to start movement animation
  - P to pause/resume
  - R to reset positions
  - Visual path gradient for better clarity

- pathfinding_showcase.py: Advanced multi-entity behaviors
  - Chase mode: enemies pursue player
  - Flee mode: enemies avoid player
  - Patrol mode: entities follow waypoints
  - WASD player movement
  - Dijkstra distance field visualization (D key)
  - Larger dungeon map with multiple rooms

- Both demos use new entity.path_to() method
- Smooth interpolated movement animations
- Real-time pathfinding recalculation
- Comprehensive test coverage

These demos showcase the power of integrated pathfinding for game AI.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-09 13:07:33 -04:00
7ee0a08662 feat(entity): implement path_to() method for entity pathfinding
- Add path_to(target_x, target_y) method to UIEntity class
- Uses existing Dijkstra pathfinding implementation from UIGrid
- Returns list of (x, y) coordinate tuples for complete path
- Supports both positional and keyword argument formats
- Proper error handling for out-of-bounds and no-grid scenarios
- Comprehensive test suite covering normal and edge cases

Part of TCOD integration sprint - gives entities immediate pathfinding capabilities.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-09 13:01:03 -04:00
1a3e308c77 docs: update roadmap with Dijkstra pathfinding progress
- Mark UIGrid TCOD Integration as completed
- Document critical PyArg bug fix achievement
- Update UIEntity Pathfinding to 50% complete
- Add detailed progress notes for July 9 sprint work

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-09 12:11:43 -04:00
c1e02070f4 feat(tcod): complete Dijkstra pathfinding implementation with critical PyArg fix
- Add complete Dijkstra pathfinding to UIGrid class
  - compute_dijkstra(), get_dijkstra_distance(), get_dijkstra_path()
  - Full TCODMap and TCODDijkstra integration
  - Proper memory management in constructors/destructors

- Create mcrfpy.libtcod submodule with Python bindings
  - dijkstra_compute(), dijkstra_get_distance(), dijkstra_get_path()
  - line() function for drawing corridors
  - Foundation for future FOV and pathfinding algorithms

- Fix critical PyArg bug in UIGridPoint color setter
  - PyObject_to_sfColor() now handles both Color objects and tuples
  - Prevents "SystemError: new style getargs format but argument is not a tuple"
  - Proper error handling and exception propagation

- Add comprehensive test suite
  - test_dijkstra_simple.py validates all pathfinding operations
  - dijkstra_test.py for headless testing with screenshots
  - dijkstra_interactive.py for full user interaction demos

- Consolidate and clean up test files
  - Removed 6 duplicate/broken demo attempts
  - Two clean versions: headless test + interactive demo

Part of TCOD integration sprint for RoguelikeDev Tutorial Event.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-09 12:10:48 -04:00
192d1ae1dd Roguelike Tutorial Planning + Prep 2025-07-09 08:36:11 -04:00
4e94d1d79e feat(docs): complete markdown API documentation export
- Created comprehensive markdown documentation matching HTML completeness
- Documented all 75 functions, 20 classes, 56 methods, and 20 automation methods
- Zero ellipsis instances - complete coverage with no missing documentation
- Added proper markdown formatting with code blocks and navigation
- Included full parameter documentation, return values, and examples

Key features:
- 23KB GitHub-compatible markdown documentation
- 47 argument sections with detailed parameters
- 35 return value specifications
- 23 code examples with syntax highlighting
- 38 explanatory notes and 10 exception specifications
- Full table of contents with anchor links
- Professional markdown formatting

Both export formats now available:
- HTML: docs/api_reference_complete.html (54KB, rich styling)
- Markdown: docs/API_REFERENCE_COMPLETE.md (23KB, GitHub-compatible)

Test results: 100% pass rate with zero missing documentation

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 12:16:56 -04:00
1e67541c29 feat(docs): complete API documentation with zero missing methods
- Eliminated ALL ellipsis instances (0 remaining)
- Documented 40 functions with complete signatures and examples
- Documented 21 classes with full method and property documentation
- Added 56 method descriptions with detailed parameters and return values
- Included 15 complete property specifications
- Added 24 code examples and 38 explanatory notes
- Comprehensive coverage of all collection methods, system classes, and functions

Key highlights:
- EntityCollection/UICollection: Complete method docs (append, remove, extend, count, index)
- Animation: Full property and method documentation with examples
- Color: All manipulation methods (from_hex, to_hex, lerp) with examples
- Vector: Complete mathematical operations (magnitude, normalize, dot, distance_to, angle, copy)
- Scene: All management methods including register_keyboard
- Timer: Complete control methods (pause, resume, cancel, restart)
- Window: All management methods (get, center, screenshot)
- System functions: Complete audio, scene, UI, and system function documentation

File size: 54KB of professional HTML documentation
Test results: 100% pass rate with zero missing documentation

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 11:55:19 -04:00
edb7967080 feat(docs): create professional HTML API documentation
- Fixed all formatting issues from original HTML output
- Added comprehensive constructor documentation for all classes
- Enhanced visual design with modern styling and typography
- Fixed literal newline display and markdown link conversion
- Added proper semantic HTML structure and navigation
- Includes detailed documentation for Entity, collections, and system types

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 11:19:09 -04:00
1e65d50c82 feat: complete API reference generator and finish Phase 7 documentation
Implemented comprehensive API documentation generator that:
- Introspects live mcrfpy module for accurate documentation
- Generates organized Markdown reference (docs/API_REFERENCE.md)
- Categorizes classes and functions by type
- Includes full automation module documentation
- Provides summary statistics

Results:
- 20 classes documented
- 19 module functions documented
- 20 automation methods documented
- 100% coverage of public API
- Clean, readable Markdown output

Phase 7 Summary:
- Completed 4/5 tasks (1 cancelled as architecturally inappropriate)
- All documentation tasks successful
- Type stubs, docstrings, and API reference all complete
- McRogueFace now has professional-grade documentation

Test coverage included for all documentation features.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 10:15:37 -04:00
e34d4f967e docs: cancel PyPI wheel task and add future vision for Python extension architecture
Task #70 Analysis:
- Discovered fundamental incompatibility with PyPI distribution
- McRogueFace embeds CPython rather than being loaded by it
- Traditional wheels expect to extend existing Python interpreter
- Current architecture is application-with-embedded-Python

Decisions:
- Cancelled PyPI wheel preparation as out of scope for Alpha
- Cleaned up attempted packaging files (pyproject.toml, setup.py, etc.)
- Identified better distribution methods (installers, package managers)

Added Future Vision:
- Comprehensive plan for pure Python extension architecture
- Would allow true "pip install mcrogueface" experience
- Requires major refactoring to invert control flow
- Python would drive main loop with C++ performance extensions
- Unscheduled but documented as long-term possibility

This clarifies the architectural boundaries and sets realistic
expectations for distribution methods while preserving the vision
of what McRogueFace could become with significant rework.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 10:09:37 -04:00
d0e02d5b83 feat: generate comprehensive .pyi type stubs for IDE support (#108)
Created complete type stub files for the mcrfpy module to enable:
- Full IntelliSense/autocomplete in IDEs
- Static type checking with mypy/pyright
- Better documentation tooltips
- Parameter hints and return types

Implementation details:
- Manually crafted stubs for accuracy (15KB, 533 lines)
- Complete coverage: 19 classes, 112 functions/methods
- Proper type annotations using typing module
- @overload decorators for multiple signatures
- Type aliases for common patterns (UIElement union)
- Preserved all docstrings for IDE help
- Automation module fully typed
- PEP 561 compliant with py.typed marker

Testing:
- Validated Python syntax with ast.parse()
- Verified all expected classes and functions
- Confirmed type annotations are well-formed
- Checked docstring preservation (80 docstrings)

Usage:
- VS Code: Add stubs/ to python.analysis.extraPaths
- PyCharm: Mark stubs/ directory as Sources Root
- Other IDEs will auto-detect .pyi files

This significantly improves the developer experience when using
McRogueFace as a Python game engine.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 09:59:19 -04:00
692ef0f6ad docs: add comprehensive parameter documentation to all API methods (#86)
Enhanced documentation for the mcrfpy module with:
- Detailed docstrings for all API methods
- Type hints in documentation (name: type format)
- Return type specifications
- Exception documentation where applicable
- Usage examples for complex methods
- Module-level documentation with overview and example code

Specific improvements:
- Audio API: Added parameter types and return values
- Scene API: Documented transition types and error conditions
- Timer API: Clarified handler signature and runtime parameter
- UI Search: Added wildcard pattern examples for findAll()
- Metrics API: Documented all dictionary keys returned

Also fixed method signatures:
- Changed METH_VARARGS to METH_NOARGS for parameterless methods
- Ensures proper Python calling conventions

Test coverage included - all documentation is accessible via Python's
__doc__ attributes and shows correctly formatted information.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 09:51:51 -04:00
6d7fab5f31 docs: mark issue #85 as completed in Phase 7 2025-07-08 09:34:48 -04:00
d51bed623a docs: replace all 'docstring' placeholders with comprehensive documentation (#85)
Added proper Python docstrings for all UI component classes:

UIFrame:
- Container element that can hold child drawables
- Documents position, size, colors, outline, and clip_children
- Includes constructor signature with all parameters

UICaption:
- Text display element with font and styling
- Documents text content, position, font, colors, outline
- Notes that w/h are computed from text content

UISprite:
- Texture/sprite display element
- Documents position, texture, sprite_index, scale
- Notes that w/h are computed from texture and scale

UIGrid:
- Tile-based grid for game worlds
- Documents grid dimensions, tile size, texture atlas
- Includes entities collection and background_color

All docstrings follow consistent format:
- Constructor signature with defaults
- Brief description
- Args section with types and defaults
- Attributes section with all properties

This completes Phase 7 task #85 for documentation improvements.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 09:34:22 -04:00
94a0282f9f docs: update ROADMAP with PyArgHelpers infrastructure completion 2025-07-08 09:31:00 -04:00
cf67c995f6 refactor: implement PyArgHelpers for standardized Python argument parsing
This major refactoring standardizes how position, size, and other arguments
are parsed across all UI components. PyArgHelpers provides consistent handling
for various argument patterns:

- Position as (x, y) tuple or separate x, y args
- Size as (w, h) tuple or separate width, height args
- Grid position and size with proper validation
- Color parsing with PyColorObject support

Changes across UI components:
- UICaption: Migrated to PyArgHelpers, improved resize() for future multiline support
- UIFrame: Uses standardized position parsing
- UISprite: Consistent position handling
- UIGrid: Grid-specific position/size helpers
- UIEntity: Unified argument parsing

Also includes:
- Improved error messages for type mismatches (int or float accepted)
- Reduced code duplication across constructors
- Better handling of keyword/positional argument conflicts
- Maintains backward compatibility with existing API

This addresses the inconsistent argument handling patterns discovered during
the inheritance hierarchy work and prepares for Phase 7 documentation.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-08 08:49:26 -04:00
1d90cdab1d feat(Python): establish proper inheritance hierarchy for UI types
All UIDrawable-derived Python types now properly inherit from the Drawable
base class in Python, matching the C++ inheritance structure.

Changes:
- Add Py_TPFLAGS_BASETYPE to PyDrawableType to allow inheritance
- Set tp_base = &mcrfpydef::PyDrawableType for all UI types
- Add PyDrawable.h include to UI type headers
- Rename _Drawable to Drawable and update error message

This enables proper Python inheritance: Frame, Caption, Sprite, Grid,
and Entity all inherit from Drawable, allowing shared functionality
and isinstance() checks.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-07 19:44:30 -04:00
e1c6c53157 refactor: move position property to UIDrawable base class (UISprite)
- Update UISprite to use base class position instead of sprite position
- Synchronize sprite position with base class position for rendering
- Implement onPositionChanged() for position synchronization
- Update all UISprite methods to use base position consistently
- Add comprehensive test coverage for UISprite position handling

This is part 3 of moving position to the base class. UIGrid is the final
class that needs to be updated.
2025-07-07 17:54:47 -04:00
5d24ba6a85 refactor: move position property to UIDrawable base class (UICaption)
- Update UICaption to use base class position instead of text position
- Synchronize text position with base class position for rendering
- Add onPositionChanged() virtual method for position synchronization
- Update all UICaption methods to use base position consistently
- Add comprehensive test coverage for UICaption position handling

This is part 2 of moving position to the base class. UISprite and UIGrid
will be updated in subsequent commits.
2025-07-07 17:45:53 -04:00
c4b4f12758 refactor: move position property to UIDrawable base class (UIFrame)
- Add position member to UIDrawable base class
- Add common position getters/setters (x, y, pos) to base class
- Update UIFrame to use base class position instead of box position
- Synchronize box position with base class position for rendering
- Update all UIFrame methods to use base position consistently
- Add comprehensive test coverage for UIFrame position handling

This is part 1 of moving position to the base class. Other derived classes
(UICaption, UISprite, UIGrid) will be updated in subsequent commits.
2025-07-07 17:38:11 -04:00
419f7d716a refactor: remove UIEntity collision_pos field
- Remove redundant collision_pos field from UIEntity
- Update position getters/setters to use integer-cast position when needed
- Remove all collision_pos synchronization code
- Simplify entity position handling to use single float position field
- Add comprehensive test coverage proving functionality is preserved

This removes technical debt and simplifies the codebase without changing API behavior.
2025-07-07 17:27:40 -04:00
7c87b5a092 feat: add PyArgHelpers infrastructure for standardized argument parsing
- Create PyArgHelpers.h with parsing functions for position, size, grid coordinates, and color
- Support tuple-based vector arguments with conflict detection
- Provide consistent error messages and validation
- Add comprehensive test coverage for infrastructure

This sets the foundation for standardizing all Python API constructors.
2025-07-07 17:21:27 -04:00
e2696e60df docs: mark Phase 6 (Rendering Revolution) as complete
Phase 6 is now complete with all core rendering features implemented:

Completed Features:
- Grid background colors (#50) - customizable backgrounds with animation
- RenderTexture overhaul (#6) - UIFrame clipping with opt-in architecture
- Viewport-based rendering (#8) - three scaling modes with coordinate transform

Strategic Decisions:
- UIGrid already has optimal RenderTexture implementation for its viewport needs
- UICaption/UISprite clipping deemed unnecessary (no children to clip)
- Effects/Shader/Particle systems deferred to post-Phase 7 for focused delivery

The rendering foundation is now solid and ready for Phase 7: Documentation & Distribution.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-07 10:58:00 -04:00
5a49cb7b6d feat(viewport): complete viewport-based rendering system (#8)
Implements a comprehensive viewport system that allows fixed game resolution
with flexible window scaling, addressing the primary wishes for issues #34, #49, and #8.

Key Features:
- Fixed game resolution independent of window size (window.game_resolution property)
- Three scaling modes accessible via window.scaling_mode:
  - "center": 1:1 pixels, viewport centered in window
  - "stretch": viewport fills window, ignores aspect ratio
  - "fit": maintains aspect ratio with black bars
- Automatic window-to-game coordinate transformation for mouse input
- Full Python API integration with PyWindow properties

Technical Implementation:
- GameEngine::ViewportMode enum with Center, Stretch, Fit modes
- SFML View system for efficient GPU-based viewport scaling
- updateViewport() recalculates on window resize or mode change
- windowToGameCoords() transforms mouse coordinates correctly
- PyScene mouse input automatically uses transformed coordinates

Tests:
- test_viewport_simple.py: Basic API functionality
- test_viewport_visual.py: Visual verification with screenshots
- test_viewport_scaling.py: Interactive mode switching and resizing

This completes the viewport-based rendering task and provides the foundation
for resolution-independent game development as requested for Crypt of Sokoban.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-07 10:28:50 -04:00
93256b96c6 docs: update ROADMAP for Phase 6 progress
- Marked Phase 6 as IN PROGRESS
- Updated RenderTexture overhaul (#6) as PARTIALLY COMPLETE
- Marked Grid background colors (#50) as COMPLETED
- Added technical notes from implementation experience
- Identified viewport rendering (#8) as next priority
2025-07-06 16:58:35 -04:00
967ebcf478 feat(rendering): implement RenderTexture base infrastructure and UIFrame clipping (#6)
- Added RenderTexture support to UIDrawable base class
  - std::unique_ptr<sf::RenderTexture> for opt-in rendering
  - Dirty flag system for optimization
  - enableRenderTexture() and markDirty() methods

- Implemented clip_children property for UIFrame
  - Python-accessible boolean property
  - Automatic RenderTexture creation when enabled
  - Proper coordinate transformation for nested frames

- Updated UIFrame::render() for clipping support
  - Renders to RenderTexture when clip_children=true
  - Handles nested clipping correctly
  - Only re-renders when dirty flag is set

- Added comprehensive dirty flag propagation
  - All property setters mark frame as dirty
  - Size changes recreate RenderTexture
  - Animation system integration

- Created tests for clipping functionality
  - Basic clipping test with visual verification
  - Advanced nested clipping test
  - Dynamic resize handling test

This is Phase 1 of the RenderTexture overhaul, providing the foundation
for advanced rendering effects like blur, glow, and viewport rendering.
2025-07-06 16:13:12 -04:00
5e4224a4f8 docs: create RenderTexture overhaul design document
- Comprehensive design for Issue #6 implementation
- Opt-in architecture to maintain backward compatibility
- Phased implementation plan with clear milestones
- Performance considerations and risk mitigation
- API design for clipping and future effects

Also includes Grid background color test
2025-07-06 16:00:11 -04:00
ff7cf25806 feat(Grid): add customizable background_color property (#50)
- Added sf::Color background_color member with default dark gray
- Python property getter/setter for background_color
- Animation support for individual color components (r/g/b/a)
- Replaces hardcoded clear color in render method
- Test demonstrates color changes and property access

Closes #50
2025-07-06 15:58:17 -04:00
4b2ad0ff18 docs: update roadmap for Phase 6 preparation
- Mark Phase 5 (Window/Scene Architecture) as complete
- Update issue statuses (#34, #61, #1, #105 completed)
- Add Phase 6 implementation strategy for RenderTexture overhaul
- Archive Phase 5 test files to .archive/
- Identify quick wins and technical approach for rendering work
2025-07-06 14:43:43 -04:00
eaeef1a889 feat(Phase 5): Complete Window/Scene Architecture
- Window singleton with properties (resolution, fullscreen, vsync, title)
- OOP Scene support with lifecycle methods (on_enter, on_exit, on_keypress, update)
- Window resize events trigger scene.on_resize callbacks
- Scene transitions (fade, slide_left/right/up/down) with smooth animations
- Full integration of Python Scene objects with C++ engine

All Phase 5 tasks (#34, #1, #61, #105) completed successfully.
2025-07-06 14:40:43 -04:00
f76a26c120 research: SFML 3.0 migration analysis
- Analyzed SFML 3.0 breaking changes (event system, scoped enums, C++17)
- Assessed migration impact on McRogueFace (40+ files affected)
- Evaluated timing relative to mcrfpy.sfml module plans
- Recommended deferring migration until after mcrfpy.sfml implementation
- Created SFML_3_MIGRATION_RESEARCH.md with comprehensive strategy
2025-07-06 13:08:52 -04:00
193294d3a7 research: SFML exposure options analysis (#14)
- Analyzed current SFML 2.6.1 usage throughout codebase
- Evaluated python-sfml (abandoned, only supports SFML 2.3.2)
- Recommended direct integration as mcrfpy.sfml module
- Created comprehensive SFML_EXPOSURE_RESEARCH.md with implementation plan
- Identified opportunity to provide modern SFML 2.6+ Python bindings
2025-07-06 12:49:11 -04:00
f23aa784f2 feat: add basic profiling/metrics system (#104)
- Add ProfilingMetrics struct to track performance data
- Track frame time (current and 60-frame rolling average)
- Calculate FPS from average frame time
- Count draw calls, UI elements, and visible elements per frame
- Track total runtime and current frame number
- PyScene counts elements during render
- Expose metrics via mcrfpy.getMetrics() returning dict

This provides basic performance monitoring capabilities for
identifying bottlenecks and optimizing rendering performance.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 12:15:32 -04:00
1c7195a748 fix: improve click handling with proper z-order and coordinate transforms
- UIFrame: Fix coordinate transformation (subtract parent pos, not add)
- UIFrame: Check children in reverse order (highest z-index first)
- UIFrame: Skip invisible elements entirely
- PyScene: Sort elements by z-index before checking clicks
- PyScene: Stop at first element that handles the click
- UIGrid: Implement entity click detection with grid coordinate transform
- UIGrid: Check entities in reverse order, return sprite as target

Click events now correctly respect z-order (top elements get priority),
handle coordinate transforms for nested frames, and support clicking
on grid entities. Elements without click handlers are transparent to
clicks, allowing elements below to receive them.

Note: Click testing requires non-headless mode due to PyScene limitation.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 12:11:13 -04:00
edfe3ba184 feat: implement name system for finding UI elements (#39/40/41)
- Add 'name' property to UIDrawable base class
- All UI elements (Frame, Caption, Sprite, Grid, Entity) support .name
- Entity delegates name to its sprite member
- Add find(name, scene=None) function for exact match search
- Add findAll(pattern, scene=None) with wildcard support (* matches any sequence)
- Both functions search recursively through Frame children and Grid entities
- Comprehensive test coverage for all functionality

This provides a simple way to find UI elements by name in Python scripts,
supporting both exact matches and wildcard patterns.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 12:02:37 -04:00
97067a104e fix: prevent segfault when closing window via X button
- Add cleanup() method to GameEngine to clear Python references before destruction
- Clear timers and McRFPy_API references in proper order
- Call cleanup() at end of run loop and in destructor
- Ensure cleanup is only called once per GameEngine instance

Also includes:
- Fix audio ::stop() calls (already in place, OpenAL warning is benign)
- Add Caption support for x, y keywords (e.g. Caption("text", x=5, y=10))
- Refactor UIDrawable_methods.h into UIBase.h for better organization
- Move UIEntity-specific implementations to UIEntityPyMethods.h

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 10:08:42 -04:00
ee6550bf63 feat: stabilize test suite and add UIDrawable methods
- Add visible, opacity properties to all UI classes (#87, #88)
- Add get_bounds(), move(), resize() methods to UIDrawable (#89, #98)
- Create UIDrawable_methods.h with template implementations
- Fix test termination issues - all tests now exit properly
- Fix test_sprite_texture_swap.py click handler signature
- Fix test_drawable_base.py segfault in headless mode
- Convert audio objects to pointers for cleanup (OpenAL warning persists)
- Remove debug print statements from UICaption
- Special handling for UIEntity to delegate drawable methods to sprite

All test files are now "airtight" - they complete successfully,
terminate on their own, and handle edge cases properly.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 09:51:37 -04:00
cc9b5c8f88 docs: add Phase 1-3 completion summary
- Document all completed tasks across three phases
- Show before/after API improvements
- Highlight technical achievements
- Outline next steps for Phase 4-7
2025-07-06 08:53:30 -04:00
27db9a4184 feat: implement mcrfpy.Timer object with pause/resume/cancel capabilities closes #103
- Created PyTimer.h/cpp with object-oriented timer interface
- Enhanced PyTimerCallable with pause/resume state tracking
- Added timer control methods: pause(), resume(), cancel(), restart()
- Added timer properties: interval, remaining, paused, active, callback
- Fixed timing logic to prevent rapid catch-up after resume
- Timer objects automatically register with game engine
- Added comprehensive test demonstrating all functionality
2025-07-06 08:52:05 -04:00
1aa35202e1 feat(Color): add helper methods from_hex, to_hex, lerp closes #94
- Add Color.from_hex(hex_string) class method for creating colors from hex
- Support formats: #RRGGBB, RRGGBB, #RRGGBBAA, RRGGBBAA
- Add color.to_hex() to convert Color to hex string
- Add color.lerp(other, t) for smooth color interpolation
- Comprehensive test coverage for all methods

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 08:40:25 -04:00
b390a087bc fix: properly configure UTF-8 encoding for Python stdio
- Use PyConfig to set stdio_encoding="UTF-8" during initialization
- Set stdio_errors="surrogateescape" for robust handling
- Configure in both init_python() and init_python_with_config()
- Cleaner solution than wrapping streams after initialization
- Fixes UnicodeEncodeError when printing unicode characters

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 01:48:38 -04:00
0f518127ec feat(Vector): implement arithmetic operations closes #93
- Add PyNumberMethods with add, subtract, multiply, divide, negate, absolute
- Add rich comparison for equality/inequality checks
- Add boolean check (zero vector is False)
- Implement vector methods: magnitude(), normalize(), dot(), distance_to(), angle(), copy()
- Fix UIDrawable::get_click() segfault when click_callable is null
- Comprehensive test coverage for all arithmetic operations

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 01:35:41 -04:00
75f75d250f feat: Complete position argument standardization for all UI classes
- Frame and Sprite now support pos keyword override
- Entity now accepts x,y arguments (was pos-only before)
- All UI classes now consistently support:
  - (x, y) positional
  - ((x, y)) tuple
  - x=x, y=y keywords
  - pos=(x,y) keyword
  - pos=Vector keyword
- Improves API consistency and flexibility
2025-07-06 01:14:45 -04:00
c48c91e5d7 feat: Standardize position arguments across all UI classes
- Create PyPositionHelper for consistent position parsing
- Grid.at() now accepts (x,y), ((x,y)), x=x, y=y, pos=(x,y)
- Caption now accepts x,y args in addition to pos
- Grid init fully supports keyword arguments
- Maintain backward compatibility for all formats
- Consistent error messages across classes
2025-07-06 01:06:12 -04:00
fe5976c425 feat: Add Entity.die() method for lifecycle management closes #30
- Remove entity from its grid's entity list
- Clear grid reference after removal
- Safe to call multiple times (no-op if not on grid)
- Works with shared_ptr entity management
2025-07-06 00:45:01 -04:00
61a05dd6ba perf: Skip out-of-bounds entities during Grid rendering closes #52
- Add visibility bounds check in entity render loop
- Skip entities outside view with 1 cell margin
- Improves performance for large grids with many entities
- Bounds check considers zoom and pan settings
2025-07-06 00:42:15 -04:00
c0270c9b32 verify: Sprite texture swapping functionality closes #19
- Texture property getter/setter already implemented
- Position/scale preservation during swap confirmed
- Type validation for texture assignment working
- Tests verify functionality is complete
2025-07-06 00:36:52 -04:00
da7180f5ed feat: Grid size tuple support closes #90
- Add grid_size keyword parameter to Grid.__init__
- Accept tuple or list of two integers
- Override grid_x/grid_y if grid_size provided
- Maintain backward compatibility
- Add comprehensive test coverage
2025-07-06 00:31:29 -04:00
f1b354e47d feat: Phase 1 - safe constructors and _Drawable foundation
Closes #7 - Make all UI class constructors safe:
- Added safe default constructors for UISprite, UIGrid, UIEntity, UICaption
- Initialize all members to predictable values
- Made Python init functions accept no arguments
- Added x,y properties to UIEntity

Closes #71 - Create _Drawable Python base class:
- Created PyDrawable.h/cpp with base type (not yet inherited by UI types)
- Registered in module initialization

Closes #87 - Add visible property:
- Added bool visible=true to UIDrawable base class
- All render methods check visibility before drawing

Closes #88 - Add opacity property:
- Added float opacity=1.0 to UIDrawable base class
- UICaption and UISprite apply opacity to alpha channel

Closes #89 - Add get_bounds() method:
- Virtual method returns sf::FloatRect(x,y,w,h)
- Implemented in Frame, Caption, Sprite, Grid

Closes #98 - Add move() and resize() methods:
- move(dx,dy) for relative movement
- resize(w,h) for absolute sizing
- Caption resize is no-op (size controlled by font)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-06 00:13:39 -04:00
a88ce0e259 docs: comprehensive alpha_streamline_2 plan and strategic vision
- Add 7-phase development plan for alpha_streamline_2 branch
- Define architectural dependencies and critical path
- Identify new issues needed (Timer objects, event system, etc.)
- Add strategic vision document with 3 transformative directions
- Timeline: 10-12 weeks to solid Beta foundation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-05 22:16:52 -04:00
5b6b0cc8ff feat(Grid): flexible at() method arguments
- Support tuple argument: grid.at((x, y))
- Support keyword arguments: grid.at(x=5, y=3)
- Support pos keyword: grid.at(pos=(2, 8))
- Maintain backward compatibility with grid.at(x, y)
- Add comprehensive error handling for invalid arguments

Improves API ergonomics and Python-like flexibility

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-05 20:35:33 -04:00
1920 changed files with 62321 additions and 2101102 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
.archive/debug_multi_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
.archive/debug_multi_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
.archive/debug_multi_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""
Test for Entity property setters - fixing "new style getargs format" error
Verifies that Entity position and sprite_number setters work correctly.
"""
def test_entity_setters(timer_name):
"""Test that Entity property setters work correctly"""
import mcrfpy
print("Testing Entity property setters...")
# Create test scene and grid
mcrfpy.createScene("entity_test")
ui = mcrfpy.sceneUI("entity_test")
# Create grid with texture
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400))
ui.append(grid)
# Create entity
initial_pos = mcrfpy.Vector(2.5, 3.5)
entity = mcrfpy.Entity(initial_pos, texture, 5, grid)
grid.entities.append(entity)
print(f"✓ Created entity at position {entity.pos}")
# Test position setter with Vector
new_pos = mcrfpy.Vector(4.0, 5.0)
try:
entity.pos = new_pos
assert entity.pos.x == 4.0, f"Expected x=4.0, got {entity.pos.x}"
assert entity.pos.y == 5.0, f"Expected y=5.0, got {entity.pos.y}"
print(f"✓ Position setter works with Vector: {entity.pos}")
except Exception as e:
print(f"✗ Position setter failed: {e}")
raise
# Test position setter with tuple (should also work via PyVector::from_arg)
try:
entity.pos = (7.5, 8.5)
assert entity.pos.x == 7.5, f"Expected x=7.5, got {entity.pos.x}"
assert entity.pos.y == 8.5, f"Expected y=8.5, got {entity.pos.y}"
print(f"✓ Position setter works with tuple: {entity.pos}")
except Exception as e:
print(f"✗ Position setter with tuple failed: {e}")
raise
# Test draw_pos setter (collision position)
try:
entity.draw_pos = mcrfpy.Vector(3, 4)
assert entity.draw_pos.x == 3, f"Expected x=3, got {entity.draw_pos.x}"
assert entity.draw_pos.y == 4, f"Expected y=4, got {entity.draw_pos.y}"
print(f"✓ Draw position setter works: {entity.draw_pos}")
except Exception as e:
print(f"✗ Draw position setter failed: {e}")
raise
# Test sprite_number setter
try:
entity.sprite_number = 10
assert entity.sprite_number == 10, f"Expected sprite_number=10, got {entity.sprite_number}"
print(f"✓ Sprite number setter works: {entity.sprite_number}")
except Exception as e:
print(f"✗ Sprite number setter failed: {e}")
raise
# Test invalid position setter (should raise TypeError)
try:
entity.pos = "invalid"
print("✗ Position setter should have raised TypeError for string")
assert False, "Should have raised TypeError"
except TypeError as e:
print(f"✓ Position setter correctly rejects invalid type: {e}")
except Exception as e:
print(f"✗ Unexpected error: {e}")
raise
# Test invalid sprite number (should raise TypeError)
try:
entity.sprite_number = "invalid"
print("✗ Sprite number setter should have raised TypeError for string")
assert False, "Should have raised TypeError"
except TypeError as e:
print(f"✓ Sprite number setter correctly rejects invalid type: {e}")
except Exception as e:
print(f"✗ Unexpected error: {e}")
raise
# Cleanup timer
mcrfpy.delTimer("test_timer")
print("\n✅ Entity property setters test PASSED - All setters work correctly")
# Execute the test after a short delay to ensure window is ready
import mcrfpy
mcrfpy.setTimer("test_timer", test_entity_setters, 100)

View file

@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""
Simple test for Entity property setters
"""
def test_entity_setters(timer_name):
"""Test Entity property setters"""
import mcrfpy
import sys
print("Testing Entity property setters...")
# Create test scene and grid
mcrfpy.createScene("test")
ui = mcrfpy.sceneUI("test")
# Create grid with texture
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400))
ui.append(grid)
# Create entity
entity = mcrfpy.Entity((2.5, 3.5), texture, 5, grid)
grid.entities.append(entity)
# Test 1: Initial position
print(f"Initial position: {entity.pos}")
print(f"Initial position x={entity.pos.x}, y={entity.pos.y}")
# Test 2: Set position with Vector
entity.pos = mcrfpy.Vector(4.0, 5.0)
print(f"After Vector setter: pos={entity.pos}, x={entity.pos.x}, y={entity.pos.y}")
# Test 3: Set position with tuple
entity.pos = (7.5, 8.5)
print(f"After tuple setter: pos={entity.pos}, x={entity.pos.x}, y={entity.pos.y}")
# Test 4: sprite_number
print(f"Initial sprite_number: {entity.sprite_number}")
entity.sprite_number = 10
print(f"After setter: sprite_number={entity.sprite_number}")
# Test 5: Invalid types
try:
entity.pos = "invalid"
print("ERROR: Should have raised TypeError")
except TypeError as e:
print(f"✓ Correctly rejected invalid position: {e}")
try:
entity.sprite_number = "invalid"
print("ERROR: Should have raised TypeError")
except TypeError as e:
print(f"✓ Correctly rejected invalid sprite_number: {e}")
print("\n✅ Entity property setters test completed")
sys.exit(0)
# Execute the test after a short delay
import mcrfpy
mcrfpy.setTimer("test", test_entity_setters, 100)

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""
Test for Issue #27: EntityCollection.extend() method
Verifies that EntityCollection can extend with multiple entities at once.
"""
def test_entity_extend(timer_name):
"""Test that EntityCollection.extend() method works correctly"""
import mcrfpy
import sys
print("Issue #27 test: EntityCollection.extend() method")
# Create test scene and grid
mcrfpy.createScene("test")
ui = mcrfpy.sceneUI("test")
# Create grid with texture
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400))
ui.append(grid)
# Add some initial entities
entity1 = mcrfpy.Entity((1, 1), texture, 1, grid)
entity2 = mcrfpy.Entity((2, 2), texture, 2, grid)
grid.entities.append(entity1)
grid.entities.append(entity2)
print(f"✓ Initial entities: {len(grid.entities)}")
# Test 1: Extend with a list of entities
new_entities = [
mcrfpy.Entity((3, 3), texture, 3, grid),
mcrfpy.Entity((4, 4), texture, 4, grid),
mcrfpy.Entity((5, 5), texture, 5, grid)
]
try:
grid.entities.extend(new_entities)
assert len(grid.entities) == 5, f"Expected 5 entities, got {len(grid.entities)}"
print(f"✓ Extended with list: now {len(grid.entities)} entities")
except Exception as e:
print(f"✗ Failed to extend with list: {e}")
raise
# Test 2: Extend with a tuple
more_entities = (
mcrfpy.Entity((6, 6), texture, 6, grid),
mcrfpy.Entity((7, 7), texture, 7, grid)
)
try:
grid.entities.extend(more_entities)
assert len(grid.entities) == 7, f"Expected 7 entities, got {len(grid.entities)}"
print(f"✓ Extended with tuple: now {len(grid.entities)} entities")
except Exception as e:
print(f"✗ Failed to extend with tuple: {e}")
raise
# Test 3: Extend with generator expression
try:
grid.entities.extend(mcrfpy.Entity((8, i), texture, 8+i, grid) for i in range(3))
assert len(grid.entities) == 10, f"Expected 10 entities, got {len(grid.entities)}"
print(f"✓ Extended with generator: now {len(grid.entities)} entities")
except Exception as e:
print(f"✗ Failed to extend with generator: {e}")
raise
# Test 4: Verify all entities have correct grid association
for i, entity in enumerate(grid.entities):
# Just checking that we can iterate and access them
assert entity.sprite_number >= 1, f"Entity {i} has invalid sprite number"
print("✓ All entities accessible and valid")
# Test 5: Invalid input - non-iterable
try:
grid.entities.extend(42)
print("✗ Should have raised TypeError for non-iterable")
except TypeError as e:
print(f"✓ Correctly rejected non-iterable: {e}")
# Test 6: Invalid input - iterable with non-Entity
try:
grid.entities.extend([entity1, "not an entity", entity2])
print("✗ Should have raised TypeError for non-Entity in iterable")
except TypeError as e:
print(f"✓ Correctly rejected non-Entity in iterable: {e}")
# Test 7: Empty iterable (should work)
initial_count = len(grid.entities)
try:
grid.entities.extend([])
assert len(grid.entities) == initial_count, "Empty extend changed count"
print("✓ Empty extend works correctly")
except Exception as e:
print(f"✗ Empty extend failed: {e}")
raise
print(f"\n✅ Issue #27 test PASSED - EntityCollection.extend() works correctly")
sys.exit(0)
# Execute the test after a short delay
import mcrfpy
mcrfpy.setTimer("test", test_entity_extend, 100)

View file

@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""
Test for Issue #33: Sprite index validation
Verifies that Sprite and Entity objects validate sprite indices
against the texture's actual sprite count.
"""
def test_sprite_index_validation(timer_name):
"""Test that sprite index validation works correctly"""
import mcrfpy
import sys
print("Issue #33 test: Sprite index validation")
# Create test scene
mcrfpy.createScene("test")
ui = mcrfpy.sceneUI("test")
# Create texture - kenney_ice.png is 11x12 sprites of 16x16 each
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
# Total sprites = 11 * 12 = 132 sprites (indices 0-131)
# Test 1: Create sprite with valid index
try:
sprite = mcrfpy.Sprite(100, 100, texture, 50) # Valid index
ui.append(sprite)
print(f"✓ Created sprite with valid index 50")
except Exception as e:
print(f"✗ Failed to create sprite with valid index: {e}")
raise
# Test 2: Set valid sprite index
try:
sprite.sprite_number = 100 # Still valid
assert sprite.sprite_number == 100
print(f"✓ Set sprite to valid index 100")
except Exception as e:
print(f"✗ Failed to set valid sprite index: {e}")
raise
# Test 3: Set maximum valid index
try:
sprite.sprite_number = 131 # Maximum valid index
assert sprite.sprite_number == 131
print(f"✓ Set sprite to maximum valid index 131")
except Exception as e:
print(f"✗ Failed to set maximum valid index: {e}")
raise
# Test 4: Invalid negative index
try:
sprite.sprite_number = -1
print("✗ Should have raised ValueError for negative index")
except ValueError as e:
print(f"✓ Correctly rejected negative index: {e}")
except Exception as e:
print(f"✗ Wrong exception type for negative index: {e}")
raise
# Test 5: Invalid index too large
try:
sprite.sprite_number = 132 # One past the maximum
print("✗ Should have raised ValueError for index 132")
except ValueError as e:
print(f"✓ Correctly rejected out-of-bounds index: {e}")
except Exception as e:
print(f"✗ Wrong exception type for out-of-bounds index: {e}")
raise
# Test 6: Very large invalid index
try:
sprite.sprite_number = 1000
print("✗ Should have raised ValueError for index 1000")
except ValueError as e:
print(f"✓ Correctly rejected large invalid index: {e}")
# Test 7: Entity sprite_number validation
grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400))
ui.append(grid)
entity = mcrfpy.Entity((5, 5), texture, 50, grid)
grid.entities.append(entity)
try:
entity.sprite_number = 200 # Out of bounds
print("✗ Entity should also validate sprite indices")
except ValueError as e:
print(f"✓ Entity also validates sprite indices: {e}")
except Exception as e:
# Entity might not have the same validation yet
print(f"Note: Entity validation not implemented yet: {e}")
# Test 8: Different texture sizes
# Create a smaller texture to test different bounds
small_texture = mcrfpy.Texture("assets/Sprite-0001.png", 32, 32)
small_sprite = mcrfpy.Sprite(200, 200, small_texture, 0)
# This texture might have fewer sprites, test accordingly
try:
small_sprite.sprite_number = 100 # Might be out of bounds
print("Note: Small texture accepted index 100")
except ValueError as e:
print(f"✓ Small texture has different bounds: {e}")
print(f"\n✅ Issue #33 test PASSED - Sprite index validation works correctly")
sys.exit(0)
# Execute the test after a short delay
import mcrfpy
mcrfpy.setTimer("test", test_sprite_index_validation, 100)

View file

@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""
Test for Issue #73: Entity.index() method for removal
Verifies that Entity objects can report their index in the grid's entity collection.
"""
def test_entity_index(timer_name):
"""Test that Entity.index() method works correctly"""
import mcrfpy
import sys
print("Issue #73 test: Entity.index() method")
# Create test scene and grid
mcrfpy.createScene("test")
ui = mcrfpy.sceneUI("test")
# Create grid with texture
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400))
ui.append(grid)
# Create multiple entities
entities = []
for i in range(5):
entity = mcrfpy.Entity((i, i), texture, i, grid)
entities.append(entity)
grid.entities.append(entity)
print(f"✓ Created {len(entities)} entities")
# Test 1: Check each entity knows its index
for expected_idx, entity in enumerate(entities):
try:
actual_idx = entity.index()
assert actual_idx == expected_idx, f"Expected index {expected_idx}, got {actual_idx}"
print(f"✓ Entity {expected_idx} correctly reports index {actual_idx}")
except Exception as e:
print(f"✗ Entity {expected_idx} index() failed: {e}")
raise
# Test 2: Remove entity using index
entity_to_remove = entities[2]
remove_idx = entity_to_remove.index()
grid.entities.remove(remove_idx)
print(f"✓ Removed entity at index {remove_idx}")
# Test 3: Verify indices updated after removal
for i, entity in enumerate(entities):
if i == 2:
# This entity was removed, should raise error
try:
idx = entity.index()
print(f"✗ Removed entity still reports index {idx}")
except ValueError as e:
print(f"✓ Removed entity correctly raises error: {e}")
elif i < 2:
# These entities should keep their indices
idx = entity.index()
assert idx == i, f"Entity before removal has wrong index: {idx}"
else:
# These entities should have shifted down by 1
idx = entity.index()
assert idx == i - 1, f"Entity after removal has wrong index: {idx}"
# Test 4: Entity without grid
orphan_entity = mcrfpy.Entity((0, 0), texture, 0, None)
try:
idx = orphan_entity.index()
print(f"✗ Orphan entity should raise error but returned {idx}")
except RuntimeError as e:
print(f"✓ Orphan entity correctly raises error: {e}")
# Test 5: Use index() in practical removal pattern
# Add some new entities
for i in range(3):
entity = mcrfpy.Entity((7+i, 7+i), texture, 10+i, grid)
grid.entities.append(entity)
# Remove entities with sprite_number > 10
removed_count = 0
i = 0
while i < len(grid.entities):
entity = grid.entities[i]
if entity.sprite_number > 10:
grid.entities.remove(entity.index())
removed_count += 1
# Don't increment i, as entities shifted down
else:
i += 1
print(f"✓ Removed {removed_count} entities using index() in loop")
assert len(grid.entities) == 5, f"Expected 5 entities remaining, got {len(grid.entities)}"
print("\n✅ Issue #73 test PASSED - Entity.index() method works correctly")
sys.exit(0)
# Execute the test after a short delay
import mcrfpy
mcrfpy.setTimer("test", test_entity_index, 100)

View file

@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""
Simple test for Issue #73: Entity.index() method
"""
def test_entity_index(timer_name):
"""Test that Entity.index() method works correctly"""
import mcrfpy
import sys
print("Testing Entity.index() method...")
# Create test scene and grid
mcrfpy.createScene("test")
ui = mcrfpy.sceneUI("test")
# Create grid with texture
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400))
ui.append(grid)
# Clear any existing entities
while len(grid.entities) > 0:
grid.entities.remove(0)
# Create entities
entity1 = mcrfpy.Entity((1, 1), texture, 1, grid)
entity2 = mcrfpy.Entity((2, 2), texture, 2, grid)
entity3 = mcrfpy.Entity((3, 3), texture, 3, grid)
grid.entities.append(entity1)
grid.entities.append(entity2)
grid.entities.append(entity3)
print(f"Created {len(grid.entities)} entities")
# Test index() method
idx1 = entity1.index()
idx2 = entity2.index()
idx3 = entity3.index()
print(f"Entity 1 index: {idx1}")
print(f"Entity 2 index: {idx2}")
print(f"Entity 3 index: {idx3}")
assert idx1 == 0, f"Entity 1 should be at index 0, got {idx1}"
assert idx2 == 1, f"Entity 2 should be at index 1, got {idx2}"
assert idx3 == 2, f"Entity 3 should be at index 2, got {idx3}"
print("✓ All entities report correct indices")
# Test removal using index
remove_idx = entity2.index()
grid.entities.remove(remove_idx)
print(f"✓ Removed entity at index {remove_idx}")
# Check remaining entities
assert len(grid.entities) == 2
assert entity1.index() == 0
assert entity3.index() == 1 # Should have shifted down
print("✓ Indices updated correctly after removal")
# Test entity not in grid
orphan = mcrfpy.Entity((5, 5), texture, 5, None)
try:
idx = orphan.index()
print(f"✗ Orphan entity should raise error but returned {idx}")
except RuntimeError as e:
print(f"✓ Orphan entity correctly raises error")
print("\n✅ Entity.index() test PASSED")
sys.exit(0)
# Execute the test after a short delay
import mcrfpy
mcrfpy.setTimer("test", test_entity_index, 100)

View file

@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""
Test for Issue #74: Add missing Grid.grid_y property
Verifies that Grid objects expose grid_x and grid_y properties correctly.
"""
def test_grid_xy_properties(timer_name):
"""Test that Grid has grid_x and grid_y properties"""
import mcrfpy
# Test was run
print("Issue #74 test: Grid.grid_x and Grid.grid_y properties")
# Test with texture
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
grid = mcrfpy.Grid(20, 15, texture, (0, 0), (800, 600))
# Test grid_x property
assert hasattr(grid, 'grid_x'), "Grid should have grid_x property"
assert grid.grid_x == 20, f"Expected grid_x=20, got {grid.grid_x}"
print(f"✓ grid.grid_x = {grid.grid_x}")
# Test grid_y property
assert hasattr(grid, 'grid_y'), "Grid should have grid_y property"
assert grid.grid_y == 15, f"Expected grid_y=15, got {grid.grid_y}"
print(f"✓ grid.grid_y = {grid.grid_y}")
# Test grid_size still works
assert hasattr(grid, 'grid_size'), "Grid should still have grid_size property"
assert grid.grid_size == (20, 15), f"Expected grid_size=(20, 15), got {grid.grid_size}"
print(f"✓ grid.grid_size = {grid.grid_size}")
# Test without texture
grid2 = mcrfpy.Grid(30, 25, None, (10, 10), (480, 400))
assert grid2.grid_x == 30, f"Expected grid_x=30, got {grid2.grid_x}"
assert grid2.grid_y == 25, f"Expected grid_y=25, got {grid2.grid_y}"
assert grid2.grid_size == (30, 25), f"Expected grid_size=(30, 25), got {grid2.grid_size}"
print("✓ Grid without texture also has correct grid_x and grid_y")
# Test using in error message context (original issue)
try:
grid.at((-1, 0)) # Should raise error
except ValueError as e:
error_msg = str(e)
assert "Grid.grid_x" in error_msg, f"Error message should reference Grid.grid_x: {error_msg}"
print(f"✓ Error message correctly references Grid.grid_x: {error_msg}")
try:
grid.at((0, -1)) # Should raise error
except ValueError as e:
error_msg = str(e)
assert "Grid.grid_y" in error_msg, f"Error message should reference Grid.grid_y: {error_msg}"
print(f"✓ Error message correctly references Grid.grid_y: {error_msg}")
print("\n✅ Issue #74 test PASSED - Grid.grid_x and Grid.grid_y properties work correctly")
# Execute the test after a short delay to ensure window is ready
import mcrfpy
mcrfpy.setTimer("test_timer", test_grid_xy_properties, 100)

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Test that Issue #78 is fixed - Middle Mouse Click should NOT send 'C' keyboard event"""
import mcrfpy
from mcrfpy import automation
import sys
# Track events
keyboard_events = []
click_events = []
def keyboard_handler(key):
"""Track keyboard events"""
keyboard_events.append(key)
print(f"Keyboard event received: '{key}'")
def click_handler(x, y, button):
"""Track click events"""
click_events.append((x, y, button))
print(f"Click event received: ({x}, {y}, button={button})")
def test_middle_click_fix(runtime):
"""Test that middle click no longer sends 'C' key event"""
print(f"\n=== Testing Issue #78 Fix (runtime: {runtime}) ===")
# Simulate middle click
print("\nSimulating middle click at (200, 200)...")
automation.middleClick(200, 200)
# Also test other clicks for comparison
print("Simulating left click at (100, 100)...")
automation.click(100, 100)
print("Simulating right click at (300, 300)...")
automation.rightClick(300, 300)
# Wait a moment for events to process
mcrfpy.setTimer("check_results", check_results, 500)
def check_results(runtime):
"""Check if the bug is fixed"""
print(f"\n=== Results ===")
print(f"Keyboard events received: {len(keyboard_events)}")
print(f"Click events received: {len(click_events)}")
# Check if 'C' was incorrectly triggered
if 'C' in keyboard_events or 'c' in keyboard_events:
print("\n✗ FAIL - Issue #78 still exists: Middle click triggered 'C' keyboard event!")
print(f"Keyboard events: {keyboard_events}")
else:
print("\n✓ PASS - Issue #78 is FIXED: No spurious 'C' keyboard event from middle click!")
# Take screenshot
filename = f"issue78_fixed_{int(runtime)}.png"
automation.screenshot(filename)
print(f"\nScreenshot saved: {filename}")
# Cleanup and exit
mcrfpy.delTimer("check_results")
sys.exit(0)
# Set up test scene
print("Setting up test scene...")
mcrfpy.createScene("issue78_test")
mcrfpy.setScene("issue78_test")
ui = mcrfpy.sceneUI("issue78_test")
# Register keyboard handler
mcrfpy.keypressScene(keyboard_handler)
# Create a clickable frame
frame = mcrfpy.Frame(50, 50, 400, 400,
fill_color=mcrfpy.Color(100, 150, 200),
outline_color=mcrfpy.Color(255, 255, 255),
outline=3.0)
frame.click = click_handler
ui.append(frame)
# Add label
caption = mcrfpy.Caption(mcrfpy.Vector(100, 100),
text="Issue #78 Test - Middle Click",
fill_color=mcrfpy.Color(255, 255, 255))
caption.size = 24
ui.append(caption)
# Schedule test
print("Scheduling test to run after render loop starts...")
mcrfpy.setTimer("test", test_middle_click_fix, 1000)

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""
Test for Sprite texture setter - fixing "error return without exception set"
"""
def test_sprite_texture_setter(timer_name):
"""Test that Sprite texture setter works correctly"""
import mcrfpy
import sys
print("Testing Sprite texture setter...")
# Create test scene
mcrfpy.createScene("test")
ui = mcrfpy.sceneUI("test")
# Create textures
texture1 = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
texture2 = mcrfpy.Texture("assets/kenney_lava.png", 16, 16)
# Create sprite with first texture
sprite = mcrfpy.Sprite(100, 100, texture1, 5)
ui.append(sprite)
# Test getting texture
try:
current_texture = sprite.texture
print(f"✓ Got texture: {current_texture}")
except Exception as e:
print(f"✗ Failed to get texture: {e}")
raise
# Test setting new texture
try:
sprite.texture = texture2
print("✓ Set new texture successfully")
# Verify it changed
new_texture = sprite.texture
if new_texture != texture2:
print(f"✗ Texture didn't change properly")
else:
print("✓ Texture changed correctly")
except Exception as e:
print(f"✗ Failed to set texture: {e}")
raise
# Test invalid texture type
try:
sprite.texture = "invalid"
print("✗ Should have raised TypeError for invalid texture")
except TypeError as e:
print(f"✓ Correctly rejected invalid texture: {e}")
except Exception as e:
print(f"✗ Wrong exception type: {e}")
raise
# Test None texture
try:
sprite.texture = None
print("✗ Should have raised TypeError for None texture")
except TypeError as e:
print(f"✓ Correctly rejected None texture: {e}")
# Test that sprite still renders correctly
print("✓ Sprite still renders with new texture")
print("\n✅ Sprite texture setter test PASSED")
sys.exit(0)
# Execute the test after a short delay
import mcrfpy
mcrfpy.setTimer("test", test_sprite_texture_setter, 100)

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -1,123 +0,0 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential cmake git \
zlib1g-dev libx11-dev libxrandr-dev libxcursor-dev \
libfreetype-dev libudev-dev libvorbis-dev libflac-dev \
libgl-dev libopenal-dev
- name: Check for pre-built libraries
run: |
if [ ! -d "__lib" ]; then
echo "::error::__lib/ directory not found. Pre-built libraries must be available on the runner."
echo "See BUILD_FROM_SOURCE.md for instructions on building dependencies."
exit 1
fi
- name: Build (Release)
run: make linux
- name: Run tests (Release)
run: cd tests && python3 run_tests.py -v
debug-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential cmake git \
zlib1g-dev libx11-dev libxrandr-dev libxcursor-dev \
libfreetype-dev libudev-dev libvorbis-dev libflac-dev \
libgl-dev libopenal-dev
- name: Check for debug libraries
run: |
if [ ! -d "__lib_debug" ]; then
echo "::error::__lib_debug/ directory not found. Build debug Python first: tools/build_debug_python.sh"
exit 1
fi
- name: Build and test (debug Python)
run: make debug-test
asan-test:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential cmake git \
zlib1g-dev libx11-dev libxrandr-dev libxcursor-dev \
libfreetype-dev libudev-dev libvorbis-dev libflac-dev \
libgl-dev libopenal-dev
- name: Check for debug libraries
run: |
if [ ! -d "__lib_debug" ]; then
echo "::error::__lib_debug/ directory not found. Build debug Python first: tools/build_debug_python.sh"
exit 1
fi
- name: Build and test (ASan + UBSan)
run: make asan-test
valgrind-test:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
build-essential cmake git valgrind \
zlib1g-dev libx11-dev libxrandr-dev libxcursor-dev \
libfreetype-dev libudev-dev libvorbis-dev libflac-dev \
libgl-dev libopenal-dev
- name: Check for debug libraries
run: |
if [ ! -d "__lib_debug" ]; then
echo "::error::__lib_debug/ directory not found. Build debug Python first: tools/build_debug_python.sh"
exit 1
fi
- name: Build and test (Valgrind memcheck)
run: make valgrind-test
timeout-minutes: 30

69
.gitignore vendored
View file

@ -7,83 +7,26 @@ PCbuild
.vs
obj
build
/lib
lib
obj
__pycache__
# unimportant files that won't pass clean dir check
build*
docs
.claude
my_games
# images are produced by many tests
*.png
# generated snippet preview screenshots (#381); consumed/committed by the doc-site repo
snippet-shots/
# WASM stdlib for Emscripten build
!wasm_stdlib/
.cache/
7DRL2025 Release/
CMakeFiles/
Makefile
*.md
*.zip
__lib/
__lib_debug/
__lib_windows/
build-windows/
build_windows/
_oldscripts/
# Audit tooling virtualenv (tools/audit_pymethoddef.py)
.venv-audit/
assets/
cellular_automata_fire/
*.txt
deps/
fetch_issues_txt.py
forest_fire_CA.py
mcrogueface.github.io
scripts/
test_*
tcod_reference
.archive
.mcp.json
dist/
# Keep important documentation and tests
!CLAUDE.md
!README.md
!tests/
# Fuzzing build artifacts and runtime data (build-fuzz matched by build* above)
tests/fuzz/corpora/
tests/fuzz/crashes/
# >>> generated-docs (untrack via tools/untrack_generated_docs.sh) >>>
# Rendered/generated documentation is regenerated by the pre-commit hook and
# shipped inside release artifacts -- it no longer lives in git. The compact
# api/manifest.json is the exception: it STAYS committed (refreshed by the hook).
# Personal-notes docs (plans, triage, research) are also untracked here.
# tools/untrack_generated_docs.sh reads exactly this block to migrate the
# currently-tracked copies out of the index.
docs/API_REFERENCE_DYNAMIC.md
docs/api_reference_dynamic.html
docs/mcrfpy.3
stubs/mcrfpy.pyi
docs/generated/
docs/API_REFERENCE_COMPLETE.md
docs/api_reference_complete.html
docs/VISION.md
docs/GAMES.md
docs/plan-*.md
docs/api-audit-*.md
docs/ISSUE_TRIAGE_*.md
docs/GRID_ENTITY_OVERHAUL_ROADMAP.md
docs/sprint-*.md
docs/EMSCRIPTEN_RESEARCH.md
docs/WASM_TROUBLESHOOTING.md
docs/MCROGUFACE_LITE_PICOCALC_RESEARCH.md
docs/3D_SYSTEM_GUIDE.md
docs/PROCEDURAL_GENERATION_SPEC.md
# <<< generated-docs <<<

13
.gitmodules vendored
View file

@ -10,13 +10,6 @@
[submodule "modules/SFML"]
path = modules/SFML
url = git@github.com:SFML/SFML.git
[submodule "modules/libtcod-headless"]
path = modules/libtcod-headless
url = git@github.com:jmccardle/libtcod-headless.git
branch = 2.2.1-headless
[submodule "modules/RapidXML"]
path = modules/RapidXML
url = https://github.com/Fe-Bell/RapidXML
[submodule "modules/json"]
path = modules/json
url = git@github.com:nlohmann/json.git
[submodule "modules/libtcod"]
path = modules/libtcod
url = git@github.com:libtcod/libtcod.git

1093
ALPHA_STREAMLINE_WORKLOG.md Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,306 +0,0 @@
# Building McRogueFace from Source
This document describes how to build McRogueFace from a fresh clone.
## Build Options
There are two ways to build McRogueFace:
1. **Quick Build** (recommended): Use pre-built dependency libraries from a `build_deps` archive
2. **Full Build**: Compile all dependencies from submodules
## Prerequisites
### System Dependencies
Install these packages before building:
```bash
# Debian/Ubuntu
sudo apt install \
build-essential \
cmake \
git \
zlib1g-dev \
libx11-dev \
libxrandr-dev \
libxcursor-dev \
libfreetype-dev \
libudev-dev \
libvorbis-dev \
libflac-dev \
libgl-dev \
libopenal-dev
```
**Note:** SDL is NOT required - McRogueFace uses libtcod-headless which has no SDL dependency.
---
## Option 1: Quick Build (Using Pre-built Dependencies)
If you have a `build_deps.tar.gz` or `build_deps.zip` archive:
```bash
# Clone McRogueFace (no submodules needed)
git clone <repository-url> McRogueFace
cd McRogueFace
# Extract pre-built dependencies
tar -xzf /path/to/build_deps.tar.gz
# Or for zip: unzip /path/to/build_deps.zip
# Build McRogueFace
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
# Run
./mcrogueface
```
The `build_deps` archive contains:
- `__lib/` - Pre-built shared libraries (Python, SFML, libtcod-headless)
- `deps/` - Header symlinks for compilation
**Total build time: ~30 seconds**
---
## Option 2: Full Build (Compiling All Dependencies)
### 1. Clone with Submodules
```bash
git clone --recursive <repository-url> McRogueFace
cd McRogueFace
```
If submodules weren't cloned:
```bash
git submodule update --init --recursive
```
**Note:** imgui/imgui-sfml submodules may fail - this is fine, they're not used.
### 2. Create Dependency Symlinks
```bash
cd deps
ln -sf ../modules/cpython cpython
ln -sf ../modules/libtcod-headless/src/libtcod libtcod
ln -sf ../modules/cpython/Include Python
ln -sf ../modules/SFML/include/SFML SFML
cd ..
```
### 3. Build libtcod-headless
libtcod-headless is our SDL-free fork with vendored dependencies:
```bash
cd modules/libtcod-headless
mkdir build && cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON
make -j$(nproc)
cd ../../..
```
That's it! No special flags needed - libtcod-headless defaults to:
- `LIBTCOD_SDL3=disable` (no SDL dependency)
- Vendored lodepng, utf8proc, stb
### 4. Build Python 3.12
```bash
cd modules/cpython
./configure --enable-shared
make -j$(nproc)
cd ../..
```
### 5. Build SFML 2.6
```bash
cd modules/SFML
mkdir build && cd build
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON
make -j$(nproc)
cd ../../..
```
### 6. Copy Libraries
```bash
mkdir -p __lib
# Python
cp modules/cpython/libpython3.12.so* __lib/
# SFML
cp modules/SFML/build/lib/libsfml-*.so* __lib/
# libtcod-headless
cp modules/libtcod-headless/build/bin/libtcod.so* __lib/
# Python standard library
cp -r modules/cpython/Lib __lib/Python
```
### 7. Build McRogueFace
```bash
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
```
### 8. Run
```bash
./mcrogueface
```
---
## Submodule Versions
| Submodule | Version | Notes |
|-----------|---------|-------|
| SFML | 2.6.1 | Graphics, audio, windowing |
| cpython | 3.12.2 | Embedded Python interpreter |
| libtcod-headless | 2.2.1 | SDL-free fork for FOV, pathfinding |
---
## Creating a build_deps Archive
To create a `build_deps` archive for distribution:
```bash
cd McRogueFace
# Create archive directory
mkdir -p build_deps_staging
# Copy libraries
cp -r __lib build_deps_staging/
# Copy/create deps symlinks as actual directories with only needed headers
mkdir -p build_deps_staging/deps
cp -rL deps/libtcod build_deps_staging/deps/ # Follow symlink
cp -rL deps/Python build_deps_staging/deps/
cp -rL deps/SFML build_deps_staging/deps/
cp -r deps/platform build_deps_staging/deps/
# Create archives
cd build_deps_staging
tar -czf ../build_deps.tar.gz __lib deps
zip -r ../build_deps.zip __lib deps
cd ..
# Cleanup
rm -rf build_deps_staging
```
The resulting archive can be distributed alongside releases for users who want to build McRogueFace without compiling dependencies.
**Archive contents:**
```
build_deps.tar.gz
├── __lib/
│ ├── libpython3.12.so*
│ ├── libsfml-*.so*
│ ├── libtcod.so*
│ └── Python/ # Python standard library
└── deps/
├── libtcod/ # libtcod headers
├── Python/ # Python headers
├── SFML/ # SFML headers
└── platform/ # Platform-specific configs
```
---
## Verify the Build
```bash
cd build
# Check version
./mcrogueface --version
# Test headless mode
./mcrogueface --headless -c "import mcrfpy; print('Success')"
# Verify no SDL dependencies
ldd mcrogueface | grep -i sdl # Should output nothing
```
---
## Troubleshooting
### OpenAL not found
```bash
sudo apt install libopenal-dev
```
### FreeType not found
```bash
sudo apt install libfreetype-dev
```
### X11/Xrandr not found
```bash
sudo apt install libx11-dev libxrandr-dev
```
### Python standard library missing
Ensure `__lib/Python` contains the standard library:
```bash
ls __lib/Python/os.py # Should exist
```
### libtcod symbols not found
Ensure libtcod.so is in `__lib/` with correct version:
```bash
ls -la __lib/libtcod.so*
# Should show libtcod.so -> libtcod.so.2 -> libtcod.so.2.2.1
```
---
## Build Times (approximate)
On a typical 4-core system:
| Component | Time |
|-----------|------|
| libtcod-headless | ~30 seconds |
| Python 3.12 | ~3-5 minutes |
| SFML 2.6 | ~1 minute |
| McRogueFace | ~30 seconds |
| **Full build total** | **~5-7 minutes** |
| **Quick build (pre-built deps)** | **~30 seconds** |
---
## Runtime Dependencies
The built executable requires these system libraries:
- `libz.so.1` (zlib)
- `libopenal.so.1` (OpenAL)
- `libX11.so.6`, `libXrandr.so.2` (X11)
- `libfreetype.so.6` (FreeType)
- `libGL.so.1` (OpenGL)
All other dependencies (Python, SFML, libtcod) are bundled in `lib/`.

1015
CLAUDE.md

File diff suppressed because it is too large Load diff

View file

@ -8,527 +8,49 @@ project(McRogueFace)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# Headless build option (no SFML, no graphics - for server/testing/Emscripten prep)
option(MCRF_HEADLESS "Build without graphics dependencies (SFML, ImGui)" OFF)
# SDL2 backend option (SDL2 + OpenGL ES 2 - for Emscripten/WebGL, Android, cross-platform)
option(MCRF_SDL2 "Build with SDL2+OpenGL ES 2 backend instead of SFML" OFF)
# Playground mode - minimal scripts for web playground (REPL-focused)
option(MCRF_PLAYGROUND "Build with minimal playground scripts instead of full game" OFF)
# Demo mode - self-contained demo game for web showcase
option(MCRF_DEMO "Build with demo scripts (web showcase)" OFF)
# Game shell mode - fullscreen canvas, no REPL chrome (for itch.io / standalone web games)
option(MCRF_GAME_SHELL "Use minimal game-only HTML shell (no REPL)" OFF)
# Debug/sanitizer build options
option(MCRF_SANITIZE_ADDRESS "Build with AddressSanitizer" OFF)
option(MCRF_SANITIZE_UNDEFINED "Build with UBSan" OFF)
option(MCRF_SANITIZE_THREAD "Build with ThreadSanitizer" OFF)
option(MCRF_DEBUG_PYTHON "Link against debug CPython from __lib_debug/" OFF)
option(MCRF_FREE_THREADED_PYTHON "Link against free-threaded CPython (python3.14t)" OFF)
option(MCRF_WASM_DEBUG "Build WASM with DWARF debug info and source maps" OFF)
option(MCRF_FUZZER "Build with libFuzzer coverage instrumentation for atheris" OFF)
option(MCRF_PROFILE "Keep frame pointers for perf/Callgrind profiling (use with RelWithDebInfo)" OFF)
# Validate mutually exclusive sanitizers
if(MCRF_SANITIZE_ADDRESS AND MCRF_SANITIZE_THREAD)
message(FATAL_ERROR "ASan and TSan are mutually exclusive. Use one or the other.")
endif()
# Validate debug Python library exists when requested
if(MCRF_DEBUG_PYTHON)
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0")
message(FATAL_ERROR
"__lib_debug/libpython3.14.so.1.0 not found.\n"
"Build it first: tools/build_debug_python.sh")
endif()
message(STATUS "Using debug CPython from __lib_debug/")
endif()
# Emscripten builds: use SDL2 if specified, otherwise fall back to headless
if(EMSCRIPTEN)
if(MCRF_SDL2)
message(STATUS "Emscripten detected - using SDL2 backend")
set(MCRF_HEADLESS OFF)
else()
set(MCRF_HEADLESS ON)
message(STATUS "Emscripten detected - forcing HEADLESS mode (use -DMCRF_SDL2=ON for graphics)")
endif()
endif()
if(MCRF_SDL2)
message(STATUS "Building with SDL2 backend - SDL2+OpenGL ES 2")
endif()
if(MCRF_PLAYGROUND)
message(STATUS "Building in PLAYGROUND mode - minimal scripts for web REPL")
endif()
if(MCRF_HEADLESS)
message(STATUS "Building in HEADLESS mode - no SFML/ImGui dependencies")
endif()
# Detect cross-compilation for Windows (MinGW)
if(CMAKE_CROSSCOMPILING AND WIN32)
set(MCRF_CROSS_WINDOWS TRUE)
message(STATUS "Cross-compiling for Windows using MinGW")
endif()
# Add include directories
#include_directories(${CMAKE_SOURCE_DIR}/deps_linux)
include_directories(${CMAKE_SOURCE_DIR}/deps)
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/deps/libtcod)
include_directories(${CMAKE_SOURCE_DIR}/src)
include_directories(${CMAKE_SOURCE_DIR}/src/3d)
include_directories(${CMAKE_SOURCE_DIR}/src/platform)
include_directories(${CMAKE_SOURCE_DIR}/src/tiled)
include_directories(${CMAKE_SOURCE_DIR}/src/ldtk)
include_directories(${CMAKE_SOURCE_DIR}/src/audio)
include_directories(${CMAKE_SOURCE_DIR}/modules/RapidXML)
include_directories(${CMAKE_SOURCE_DIR}/modules/json/single_include)
#include_directories(${CMAKE_SOURCE_DIR}/deps_linux/Python-3.11.1)
include_directories(${CMAKE_SOURCE_DIR}/deps/libtcod)
# Python includes: use different paths for Windows vs Linux vs Emscripten
if(EMSCRIPTEN)
# Emscripten build: use Python headers compiled for wasm32-emscripten
# The pyconfig.h from cross-build has correct LONG_BIT and other settings
set(PYTHON_WASM_BUILD "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/build/python")
# Force-include wasm pyconfig.h BEFORE anything else to set correct platform defines
add_compile_options(-include ${PYTHON_WASM_BUILD}/pyconfig.h)
# Override LONG_BIT - Emscripten's limits.h incorrectly defines it as 64 for wasm32
add_compile_definitions(LONG_BIT=32)
# Include wasm build directory FIRST so its pyconfig.h is found by #include "pyconfig.h"
include_directories(BEFORE ${PYTHON_WASM_BUILD})
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include)
message(STATUS "Using Emscripten Python from: ${PYTHON_WASM_BUILD}")
elseif(MCRF_CROSS_WINDOWS)
# Windows cross-compilation: use cpython headers with PC/pyconfig.h
# Problem: Python.h uses #include "pyconfig.h" which finds Include/pyconfig.h (Linux) first
# Solution: Use -include to force Windows pyconfig.h to be included first
# This defines MS_WINDOWS before Python.h is processed, ensuring correct struct layouts
add_compile_options(-include ${CMAKE_SOURCE_DIR}/deps/cpython/PC/pyconfig.h)
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include)
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/PC) # For other Windows-specific headers
# Also include SFML and libtcod Windows headers
include_directories(${CMAKE_SOURCE_DIR}/__lib_windows/sfml/include)
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/include)
else()
# Native builds (Linux/Windows): use existing Python setup
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
endif()
# ImGui and ImGui-SFML include directories (not needed in headless or SDL2 mode)
# SDL2 builds will use ImGui with SDL2 backend later; for now, no ImGui
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui)
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui-sfml)
# ImGui source files
set(IMGUI_SOURCES
${CMAKE_SOURCE_DIR}/modules/imgui/imgui.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_draw.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_tables.cpp
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_widgets.cpp
${CMAKE_SOURCE_DIR}/modules/imgui-sfml/imgui-SFML.cpp
)
endif()
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
# Collect all the source files
file(GLOB_RECURSE SOURCES "src/*.cpp")
# Add ImGui sources to the build (only if using SFML)
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
list(APPEND SOURCES ${IMGUI_SOURCES})
# Add GLAD for OpenGL function loading (needed for 3D rendering on SFML)
list(APPEND SOURCES "${CMAKE_SOURCE_DIR}/src/3d/glad.c")
endif()
# Find OpenGL (required by ImGui-SFML) - not needed in headless mode
# SDL2 builds handle OpenGL ES 2 differently (via SDL2 or Emscripten)
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
if(MCRF_CROSS_WINDOWS)
# For cross-compilation, OpenGL is provided by MinGW
set(OPENGL_LIBRARIES opengl32)
else()
find_package(OpenGL REQUIRED)
set(OPENGL_LIBRARIES OpenGL::GL)
endif()
endif()
# Create a list of libraries to link against
if(EMSCRIPTEN)
# Emscripten build: link against WASM-compiled Python and libtcod
set(PYTHON_WASM_BUILD "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/build/python")
set(PYTHON_WASM_PREFIX "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/prefix")
set(LIBTCOD_WASM_BUILD "${CMAKE_SOURCE_DIR}/modules/libtcod-headless/build-emscripten")
# Collect HACL crypto object files (not included in libpython3.14.a)
file(GLOB PYTHON_HACL_OBJECTS "${PYTHON_WASM_BUILD}/Modules/_hacl/*.o")
set(LINK_LIBS
${PYTHON_WASM_BUILD}/libpython3.14.a
${PYTHON_HACL_OBJECTS}
${PYTHON_WASM_BUILD}/Modules/expat/libexpat.a
${PYTHON_WASM_PREFIX}/lib/libmpdec.a
${PYTHON_WASM_PREFIX}/lib/libffi.a
${LIBTCOD_WASM_BUILD}/libtcod.a
${LIBTCOD_WASM_BUILD}/_deps/lodepng-c-build/liblodepng-c.a
${LIBTCOD_WASM_BUILD}/_deps/utf8proc-build/libutf8proc.a)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux) # Use Linux platform stubs for now
# For SDL2 builds, add stb headers for image/font loading
if(MCRF_SDL2)
include_directories(${CMAKE_SOURCE_DIR}/deps/stb)
endif()
message(STATUS "Linking Emscripten Python: ${PYTHON_WASM_BUILD}/libpython3.14.a")
message(STATUS "Linking Emscripten libtcod: ${LIBTCOD_WASM_BUILD}/libtcod.a")
elseif(MCRF_SDL2)
# SDL2 build (non-Emscripten): link against SDL2 and system libraries
# Note: For desktop SDL2 builds in the future
find_package(SDL2 REQUIRED)
find_package(OpenGL REQUIRED)
set(LINK_LIBS
SDL2::SDL2
OpenGL::GL
tcod
python3.14
m dl util pthread)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
include_directories(${CMAKE_SOURCE_DIR}/deps/stb) # stb_image.h, stb_truetype.h
link_directories(${CMAKE_SOURCE_DIR}/__lib)
message(STATUS "Building with SDL2 backend (desktop)")
elseif(MCRF_HEADLESS)
# Headless build: no SFML, no OpenGL
if(WIN32 OR MCRF_CROSS_WINDOWS)
set(LINK_LIBS
libtcod
python314)
if(MCRF_CROSS_WINDOWS)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/lib)
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows)
else()
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
link_directories(${CMAKE_SOURCE_DIR}/__lib)
endif()
else()
# Unix/Linux headless build
if(MCRF_FREE_THREADED_PYTHON)
set(PYTHON_LIB python3.14t)
elseif(MCRF_DEBUG_PYTHON)
set(PYTHON_LIB python3.14d)
else()
set(PYTHON_LIB python3.14)
endif()
set(LINK_LIBS
tcod
${PYTHON_LIB}
m dl util pthread)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
if(MCRF_DEBUG_PYTHON OR MCRF_FREE_THREADED_PYTHON)
link_directories(${CMAKE_SOURCE_DIR}/__lib_debug)
endif()
link_directories(${CMAKE_SOURCE_DIR}/__lib)
endif()
elseif(MCRF_CROSS_WINDOWS)
# MinGW cross-compilation: use full library names
set(LINK_LIBS
sfml-graphics
sfml-window
sfml-system
sfml-audio
libtcod
python314
${OPENGL_LIBRARIES})
# Add Windows system libraries needed by SFML and MinGW
list(APPEND LINK_LIBS
winmm # Windows multimedia (for audio)
gdi32 # Graphics Device Interface
ws2_32 # Winsock (networking, used by some deps)
ole32 # OLE support
oleaut32 # OLE automation
uuid # UUID library
comdlg32 # Common dialogs
imm32 # Input Method Manager
version # Version info
)
set(LINK_LIBS
m
dl
util
pthread
python3.12
sfml-graphics
sfml-window
sfml-system
sfml-audio
tcod)
# On Windows, add any additional libs and include directories
if(WIN32)
# Add the necessary Windows-specific libraries and include directories
# include_directories(path_to_additional_includes)
# link_directories(path_to_additional_libs)
# list(APPEND LINK_LIBS additional_windows_libs)
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
# Link directories for cross-compiled Windows libs
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/sfml/lib)
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/lib)
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows)
elseif(WIN32)
# Native Windows build (MSVC)
set(LINK_LIBS
sfml-graphics
sfml-window
sfml-system
sfml-audio
tcod
python314
${OPENGL_LIBRARIES})
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
link_directories(${CMAKE_SOURCE_DIR}/__lib)
else()
# Unix/Linux build
if(MCRF_FREE_THREADED_PYTHON)
set(PYTHON_LIB python3.14t)
elseif(MCRF_DEBUG_PYTHON)
set(PYTHON_LIB python3.14d)
else()
set(PYTHON_LIB python3.14)
endif()
set(LINK_LIBS
sfml-graphics
sfml-window
sfml-system
sfml-audio
tcod
${PYTHON_LIB}
m dl util pthread
${OPENGL_LIBRARIES})
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
if(MCRF_DEBUG_PYTHON OR MCRF_FREE_THREADED_PYTHON)
link_directories(${CMAKE_SOURCE_DIR}/__lib_debug)
endif()
link_directories(${CMAKE_SOURCE_DIR}/__lib)
endif()
# Add the directory where the linker should look for the libraries
#link_directories(${CMAKE_SOURCE_DIR}/deps_linux)
link_directories(${CMAKE_SOURCE_DIR}/__lib)
# Define the executable target before linking libraries
add_executable(mcrogueface ${SOURCES})
# Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code)
# We ALWAYS need this because libtcod headers expect SDL3, not SDL2
# Our SDL2 backend is separate from libtcod's SDL3 renderer
target_compile_definitions(mcrogueface PRIVATE NO_SDL)
# Sanitizer instrumentation — applied to mcrogueface target only (not imported libs)
if(MCRF_SANITIZE_ADDRESS)
message(STATUS "AddressSanitizer enabled")
target_compile_options(mcrogueface PRIVATE
-fsanitize=address -fno-omit-frame-pointer -g -O1)
target_link_options(mcrogueface PRIVATE
-fsanitize=address)
endif()
if(MCRF_SANITIZE_UNDEFINED)
message(STATUS "UndefinedBehaviorSanitizer enabled")
# -fno-sanitize=function is Clang-only; -fno-sanitize=vptr avoids CPython false positives
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(UBSAN_EXCLUSIONS -fno-sanitize=function,vptr)
else()
set(UBSAN_EXCLUSIONS -fno-sanitize=vptr)
endif()
target_compile_options(mcrogueface PRIVATE
-fsanitize=undefined ${UBSAN_EXCLUSIONS} -g -O1)
target_link_options(mcrogueface PRIVATE
-fsanitize=undefined ${UBSAN_EXCLUSIONS})
endif()
if(MCRF_SANITIZE_THREAD)
message(STATUS "ThreadSanitizer enabled")
target_compile_options(mcrogueface PRIVATE
-fsanitize=thread -g -O1)
target_link_options(mcrogueface PRIVATE
-fsanitize=thread)
endif()
# Profiling instrumentation (#345) — retain frame pointers so perf --call-graph fp
# and Callgrind can unwind through optimized code. Optimization/-g come from the
# RelWithDebInfo build type; this only adds the frame pointer. Binary is NOT stripped.
if(MCRF_PROFILE)
message(STATUS "Profiling build: frame pointers retained (perf/Callgrind)")
target_compile_options(mcrogueface PRIVATE -fno-omit-frame-pointer)
endif()
if(MCRF_FUZZER)
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(FATAL_ERROR "MCRF_FUZZER=ON requires Clang. Invoke with CC=clang-18 CXX=clang++-18.")
endif()
message(STATUS "Building mcrfpy_fuzz harness (libFuzzer + ASan + UBSan)")
set(MCRF_FUZZ_SOURCES ${SOURCES})
list(REMOVE_ITEM MCRF_FUZZ_SOURCES ${CMAKE_SOURCE_DIR}/src/main.cpp)
list(APPEND MCRF_FUZZ_SOURCES ${CMAKE_SOURCE_DIR}/tests/fuzz/fuzz_common.cpp)
add_executable(mcrfpy_fuzz ${MCRF_FUZZ_SOURCES})
target_compile_definitions(mcrfpy_fuzz PRIVATE NO_SDL MCRF_FUZZ_HARNESS)
if(MCRF_DEBUG_PYTHON OR MCRF_FREE_THREADED_PYTHON)
target_compile_definitions(mcrfpy_fuzz PRIVATE Py_DEBUG)
endif()
if(MCRF_FREE_THREADED_PYTHON)
target_compile_definitions(mcrfpy_fuzz PRIVATE Py_GIL_DISABLED)
endif()
if(MCRF_HEADLESS)
target_compile_definitions(mcrfpy_fuzz PRIVATE MCRF_HEADLESS)
endif()
if(MCRF_SDL2)
target_compile_definitions(mcrfpy_fuzz PRIVATE MCRF_SDL2)
endif()
target_include_directories(mcrfpy_fuzz PRIVATE
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/tests/fuzz)
target_compile_options(mcrfpy_fuzz PRIVATE
-fsanitize=fuzzer-no-link,address,undefined
-fno-sanitize=function,vptr
-fno-omit-frame-pointer -g -O1)
target_link_options(mcrfpy_fuzz PRIVATE
-fsanitize=fuzzer,address,undefined
-fno-sanitize=function,vptr)
target_link_libraries(mcrfpy_fuzz ${LINK_LIBS})
# Copy Python runtime + assets next to mcrfpy_fuzz so the embedded
# interpreter finds the stdlib and default_font/default_texture load.
add_custom_command(TARGET mcrfpy_fuzz POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrfpy_fuzz>/lib
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets $<TARGET_FILE_DIR:mcrfpy_fuzz>/assets)
if(MCRF_DEBUG_PYTHON)
add_custom_command(TARGET mcrfpy_fuzz POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0
$<TARGET_FILE_DIR:mcrfpy_fuzz>/lib/libpython3.14.so.1.0
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0
$<TARGET_FILE_DIR:mcrfpy_fuzz>/lib/libpython3.14d.so.1.0
COMMAND ${CMAKE_COMMAND} -E create_symlink
libpython3.14d.so.1.0
$<TARGET_FILE_DIR:mcrfpy_fuzz>/lib/libpython3.14d.so)
endif()
endif()
# Enable Py_DEBUG when linking against debug CPython (matches pydebug ABI)
if(MCRF_DEBUG_PYTHON OR MCRF_FREE_THREADED_PYTHON)
target_compile_definitions(mcrogueface PRIVATE Py_DEBUG)
endif()
# Enable Py_GIL_DISABLED for free-threaded CPython (no-GIL build)
if(MCRF_FREE_THREADED_PYTHON)
target_compile_definitions(mcrogueface PRIVATE Py_GIL_DISABLED)
endif()
# Define MCRF_HEADLESS for headless builds (excludes SFML/ImGui code)
if(MCRF_HEADLESS)
target_compile_definitions(mcrogueface PRIVATE MCRF_HEADLESS)
endif()
# Define MCRF_SDL2 for SDL2 builds (uses SDL2+OpenGL ES 2 instead of SFML)
if(MCRF_SDL2)
target_compile_definitions(mcrogueface PRIVATE MCRF_SDL2)
endif()
# Asset/script directories for WASM preloading (game projects override these)
set(MCRF_ASSETS_DIR "${CMAKE_SOURCE_DIR}/assets" CACHE PATH "Assets directory for WASM preloading")
set(MCRF_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/src/scripts" CACHE PATH "Scripts directory for WASM preloading")
set(MCRF_SCRIPTS_PLAYGROUND_DIR "${CMAKE_SOURCE_DIR}/src/scripts_playground" CACHE PATH "Playground scripts for WASM")
set(MCRF_SCRIPTS_DEMO_DIR "${CMAKE_SOURCE_DIR}/src/scripts_demo" CACHE PATH "Demo scripts for WASM showcase")
# Emscripten-specific link options (use ports for zlib, bzip2, sqlite3)
if(EMSCRIPTEN)
# Base Emscripten options
set(EMSCRIPTEN_LINK_OPTIONS
-sUSE_ZLIB=1
-sUSE_BZIP2=1
-sUSE_SQLITE3=1
-sALLOW_MEMORY_GROWTH=1
-sSTACK_SIZE=2097152
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,FS,IDBFS
-sEXPORTED_FUNCTIONS=_main,_run_python_string,_run_python_string_with_output,_reset_python_environment,_notify_canvas_resize,_sync_storage
-lidbfs.js
-sASSERTIONS=2
-sSTACK_OVERFLOW_CHECK=2
-fexceptions
-sNO_DISABLE_EXCEPTION_CATCHING
# Disable features that require dynamic linking support
-sERROR_ON_UNDEFINED_SYMBOLS=0
-sALLOW_UNIMPLEMENTED_SYSCALLS=1
# Preload Python stdlib into virtual filesystem at /lib/python3.14
--preload-file=${CMAKE_SOURCE_DIR}/wasm_stdlib/lib@/lib
# Preload game scripts into /scripts (playground, demo, or full game)
--preload-file=$<IF:$<BOOL:${MCRF_PLAYGROUND}>,${MCRF_SCRIPTS_PLAYGROUND_DIR},$<IF:$<BOOL:${MCRF_DEMO}>,${MCRF_SCRIPTS_DEMO_DIR},${MCRF_SCRIPTS_DIR}>>@/scripts
# Preload assets
--preload-file=${MCRF_ASSETS_DIR}@/assets
# Use custom HTML shell - game shell (fullscreen) or playground shell (REPL)
--shell-file=${CMAKE_SOURCE_DIR}/src/$<IF:$<BOOL:${MCRF_GAME_SHELL}>,shell_game.html,shell.html>
# Pre-JS to fix browser zoom causing undefined values in events
--pre-js=${CMAKE_SOURCE_DIR}/src/emscripten_pre.js
)
# Add SDL2 options if using SDL2 backend
if(MCRF_SDL2)
list(APPEND EMSCRIPTEN_LINK_OPTIONS
-sUSE_SDL=2
-sUSE_SDL_MIXER=2
-sFULL_ES2=1
-sMIN_WEBGL_VERSION=2
-sMAX_WEBGL_VERSION=2
-sUSE_FREETYPE=1
)
# SDL2, SDL2_mixer, and FreeType flags are also needed at compile time for headers
target_compile_options(mcrogueface PRIVATE
-sUSE_SDL=2
-sUSE_SDL_MIXER=2
-sUSE_FREETYPE=1
)
message(STATUS "Emscripten SDL2 options enabled: -sUSE_SDL=2 -sUSE_SDL_MIXER=2 -sFULL_ES2=1 -sUSE_FREETYPE=1")
endif()
# WASM debug builds: DWARF symbols, source maps, symbol map for stack traces
if(MCRF_WASM_DEBUG)
list(APPEND EMSCRIPTEN_LINK_OPTIONS
-g4
-gsource-map
--emit-symbol-map
)
target_compile_options(mcrogueface PRIVATE -g4)
message(STATUS "Emscripten debug enabled: DWARF (-g4), source maps, symbol map")
endif()
target_link_options(mcrogueface PRIVATE ${EMSCRIPTEN_LINK_OPTIONS})
# Output as HTML to use the shell file
set_target_properties(mcrogueface PROPERTIES SUFFIX ".html")
# Set Python home for the embedded interpreter
target_compile_definitions(mcrogueface PRIVATE
MCRF_WASM_PYTHON_HOME="/lib/python3.14"
)
endif()
# On Windows, define Py_ENABLE_SHARED for proper Python DLL imports
# Py_PYCONFIG_H prevents Include/pyconfig.h (Linux config) from being included
# (PC/pyconfig.h already defines HAVE_DECLSPEC_DLL and MS_WINDOWS)
if(WIN32 OR MCRF_CROSS_WINDOWS)
target_compile_definitions(mcrogueface PRIVATE Py_ENABLE_SHARED Py_PYCONFIG_H)
endif()
# On Windows, set subsystem to WINDOWS to hide console (release builds only)
# Use -DMCRF_WINDOWS_CONSOLE=ON for debug builds with console output
option(MCRF_WINDOWS_CONSOLE "Keep console window visible for debugging" OFF)
if(WIN32 AND NOT MCRF_CROSS_WINDOWS)
# MSVC-specific flags
if(NOT MCRF_WINDOWS_CONSOLE)
set_target_properties(mcrogueface PROPERTIES
WIN32_EXECUTABLE TRUE
LINK_FLAGS "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
endif()
elseif(MCRF_CROSS_WINDOWS)
# MinGW cross-compilation
if(NOT MCRF_WINDOWS_CONSOLE)
# Release: use -mwindows to hide console
set_target_properties(mcrogueface PROPERTIES
WIN32_EXECUTABLE TRUE
LINK_FLAGS "-mwindows")
else()
# Debug: keep console for stdout/stderr output
message(STATUS "Windows console enabled for debugging")
endif()
endif()
# Now the linker will find the libraries in the specified directory
target_link_libraries(mcrogueface ${LINK_LIBS})
@ -543,63 +65,11 @@ add_custom_command(TARGET mcrogueface POST_BUILD
${CMAKE_SOURCE_DIR}/src/scripts $<TARGET_FILE_DIR:mcrogueface>/scripts)
# Copy Python standard library to build directory
if(MCRF_DEBUG_PYTHON)
# Copy all libs first (SFML, libtcod, Python stdlib), then overwrite with debug Python
# The debug lib has SONAME libpython3.14d.so.1.0, so we need both names
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0
$<TARGET_FILE_DIR:mcrogueface>/lib/libpython3.14.so.1.0
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0
$<TARGET_FILE_DIR:mcrogueface>/lib/libpython3.14d.so.1.0
COMMAND ${CMAKE_COMMAND} -E create_symlink
libpython3.14d.so.1.0
$<TARGET_FILE_DIR:mcrogueface>/lib/libpython3.14d.so)
else()
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
endif()
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
# On Windows, copy DLLs to executable directory
if(MCRF_CROSS_WINDOWS)
# Cross-compilation: copy DLLs from __lib_windows
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib_windows/sfml/bin $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/bin $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/python314.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/python3.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140_1.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E copy
/usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E echo "Copied Windows DLLs to executable directory")
# Copy Python standard library zip
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/__lib_windows/python314.zip $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E echo "Copied Python stdlib")
elseif(WIN32)
# Native Windows build: copy DLLs from __lib
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>
COMMAND ${CMAKE_COMMAND} -E echo "Copied DLLs to executable directory")
endif()
# rpath for including shared libraries (Linux/Unix only)
if(NOT WIN32)
set_target_properties(mcrogueface PROPERTIES
INSTALL_RPATH "$ORIGIN/./lib")
endif()
# rpath for including shared libraries
set_target_properties(mcrogueface PROPERTIES
INSTALL_RPATH "$ORIGIN/./lib")

54
GNUmakefile Normal file
View file

@ -0,0 +1,54 @@
# Convenience Makefile wrapper for McRogueFace
# This delegates to CMake build in the build directory
.PHONY: all build clean run test dist help
# Default target
all: build
# Build the project
build:
@./build.sh
# Clean build artifacts
clean:
@./clean.sh
# Run the game
run: build
@cd build && ./mcrogueface
# Run in Python mode
python: build
@cd build && ./mcrogueface -i
# Test basic functionality
test: build
@echo "Testing McRogueFace..."
@cd build && ./mcrogueface -V
@cd build && ./mcrogueface -c "print('Test passed')"
@cd build && ./mcrogueface --headless -c "import mcrfpy; print('mcrfpy imported successfully')"
# Create distribution archive
dist: build
@echo "Creating distribution archive..."
@cd build && zip -r ../McRogueFace-$$(date +%Y%m%d).zip . -x "*.o" "CMakeFiles/*" "Makefile" "*.cmake"
@echo "Distribution archive created: McRogueFace-$$(date +%Y%m%d).zip"
# Show help
help:
@echo "McRogueFace Build System"
@echo "======================="
@echo ""
@echo "Available targets:"
@echo " make - Build the project (default)"
@echo " make build - Build the project"
@echo " make clean - Remove all build artifacts"
@echo " make run - Build and run the game"
@echo " make python - Build and run in Python interactive mode"
@echo " make test - Run basic tests"
@echo " make dist - Create distribution archive"
@echo " make help - Show this help message"
@echo ""
@echo "Build output goes to: ./build/"
@echo "Distribution archives are created in project root"

590
Makefile
View file

@ -1,590 +0,0 @@
# McRogueFace Build Makefile
# Usage:
# make - Build for Linux (default)
# make windows - Cross-compile for Windows using MinGW (release)
# make windows-debug - Cross-compile for Windows with console & debug symbols
# make clean - Clean Linux build
# make clean-windows - Clean Windows build
# make run - Run the Linux build
#
# Profiling (see docs/profiling.md):
# make profile - RelWithDebInfo + frame pointers build in build-profile/
# make callgrind SCRIPT=tests/benchmarks/foo.py - Callgrind a headless benchmark
#
# WebAssembly / Emscripten:
# make wasm - Build full game for web WITH the REPL console (dev/debug build)
# make wasm-game - Build the clean full game for web (fullscreen, no REPL) -- shipped
# make playground - Build minimal playground for web REPL
# make serve - Serve the clean game (build-wasm-game) locally on port 8080
# make serve-game - Serve the clean game (build-wasm-game) locally on port 8080
# make serve-dev - Serve the wasm dev build with REPL console (build-emscripten)
# make clean-wasm - Clean Emscripten builds
#
# Packaging:
# make package-windows-light - Windows with minimal stdlib (~5 MB)
# make package-windows-full - Windows with full stdlib (~15 MB)
# make package-linux-light - Linux with minimal stdlib
# make package-linux-full - Linux with full stdlib
# make package-all - All platform/preset combinations
#
# Release:
# make version-bump NEXT_VERSION=x.y.z-suffix
# Tags HEAD with current version, builds all packages, bumps to NEXT_VERSION
.PHONY: all linux windows windows-debug clean clean-windows clean-dist run
.PHONY: wasm wasm-game wasm-debug playground playground-debug serve serve-game serve-dev serve-playground clean-wasm
.PHONY: package-windows-light package-windows-full package-linux-light package-linux-full package-all
.PHONY: version-bump
.PHONY: debug debug-test asan asan-test tsan tsan-test valgrind-test massif-test analyze clean-debug
.PHONY: profile callgrind clean-profile
.PHONY: install-hooks
# Number of parallel jobs for compilation
JOBS := $(shell nproc 2>/dev/null || echo 4)
all: linux
linux:
@echo "Building McRogueFace for Linux..."
@mkdir -p build
@cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make -j$(JOBS)
@echo "Build complete! Run with: ./build/mcrogueface"
windows:
@echo "Cross-compiling McRogueFace for Windows..."
@mkdir -p build-windows
@cd build-windows && cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/mingw-w64-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Release && make -j$(JOBS)
@echo "Windows build complete! Output: build-windows/mcrogueface.exe"
windows-debug:
@echo "Cross-compiling McRogueFace for Windows (debug with console)..."
@mkdir -p build-windows-debug
@cd build-windows-debug && cmake .. \
-DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/mingw-w64-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Debug \
-DMCRF_WINDOWS_CONSOLE=ON && make -j$(JOBS)
@echo "Windows debug build complete! Output: build-windows-debug/mcrogueface.exe"
@echo "Run from cmd.exe to see console output"
clean:
@echo "Cleaning Linux build..."
@rm -rf build
clean-windows:
@echo "Cleaning Windows builds..."
@rm -rf build-windows build-windows-debug
clean-dist:
@echo "Cleaning distribution packages..."
@rm -rf dist
clean-all: clean clean-windows clean-wasm clean-debug clean-profile clean-dist
@echo "All builds and packages cleaned."
run: linux
@cd build && ./mcrogueface
# Install git hooks by symlinking tools/hooks/* into .git/hooks/ (idempotent).
install-hooks:
@hooks_dir="$$(git rev-parse --git-path hooks)"; \
mkdir -p "$$hooks_dir"; \
for hook in tools/hooks/*; do \
[ -e "$$hook" ] || continue; \
name="$$(basename "$$hook")"; \
ln -sf "$$(pwd)/$$hook" "$$hooks_dir/$$name"; \
echo "Linked $$hooks_dir/$$name -> $$hook"; \
done
# ---------------------------------------------------------------------------
# Documentation / release
#
# Every piece of the docs pipeline already existed; nothing chained them, so the
# site drifted 26 engine commits behind without anything noticing. These targets
# are the chain.
#
# SITE_DIR is the mcrogueface.github.io checkout. Override if yours lives elsewhere:
# make release-docs SITE_DIR=/path/to/mcrogueface.github.io
# ---------------------------------------------------------------------------
SITE_DIR ?= $(CURDIR)/../mcrogueface.github.io
# Two DIFFERENT refs, easily conflated:
#
# BASE_REF the PREVIOUS release. The API delta is measured against it, to answer
# "what changed, and which pages does that oblige us to revisit?"
# RELEASE_REF the tag being CUT. The site pins its source links to it, which is what
# freezes this version into the site's history.
#
# When cutting a release, set both:
# make release-docs BASE_REF=0.2.8 RELEASE_REF=0.2.9
#
# Note: BASE_REF must carry api/manifest.json. The manifest infrastructure landed in
# 54624b3, so tags older than that (0.2.8 included) have no baseline to diff against --
# api_delta will say so rather than inventing one.
BASE_REF ?= $(shell git describe --tags --abbrev=0 2>/dev/null)
RELEASE_REF ?= $(shell git describe --tags --always --abbrev=0 2>/dev/null)
# Regenerate everything derived from the compiled module: man page, API reference
# (HTML + Markdown), type stubs, and the tracked api/manifest.json.
docs: linux
@./tools/generate_all_docs.sh
@./build/mcrogueface --headless --exec ../tools/generate_api_manifest.py
# Re-run every docs snippet and stamp its `# mcrf:` header with what actually
# happened (status from the run, verified from the engine, objects from the source).
stamp-snippets: linux
@python3 tools/stamp_snippets.py
# CI gate: fail if any snippet is broken or its stamp is stale. Writes nothing.
check-snippets: linux
@python3 tools/stamp_snippets.py --check
# Render a deterministic preview PNG for every snippet into snippet-shots/ (gitignored;
# the images belong in the doc-site repo, which pulls them from here -- see #381). The
# images are a visual-regression oracle: a changed PNG means behaviour changed.
snippet-shots: linux
@python3 tools/generate_snippet_shots.py
# What changed in the API since the last release, and which site pages that
# obligates you to update (resolved via each page's `mcrf.objects` frontmatter).
api-delta:
@python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format md
# The full release documentation pass. Run this when cutting a tag: it refreshes
# everything the engine derives, proves the published samples still run, rebuilds
# the site's generated reference + snippet library against this engine, and prints
# the checklist of hand-written pages the API change obligates you to revisit.
#
# Deliberately NOT automatic: it does not commit, and it does not touch the
# hand-written pages. It tells you what it found and leaves the judgment to you.
release-docs: docs stamp-snippets snippet-shots
@echo ""
@echo "=== Regenerating the site against this engine (SITE_DIR=$(SITE_DIR)) ==="
@test -d "$(SITE_DIR)" || { echo "SITE_DIR not found: $(SITE_DIR)"; exit 1; }
@cd "$(SITE_DIR)" && python3 tools/build_reference.py
@cd "$(SITE_DIR)" && python3 tools/build_library.py --ref $(RELEASE_REF)
@echo ""
@echo "=== API delta $(BASE_REF) -> now, and the pages it obligates ==="
@python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format md
@echo ""
@echo "Site pinned to RELEASE_REF=$(RELEASE_REF); delta measured from BASE_REF=$(BASE_REF)."
@echo "Next: review the delta above, update the hand-written pages it names,"
@echo " then commit both repos. For a Gitea checklist issue instead, run:"
@echo " python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format gitea"
.PHONY: docs stamp-snippets check-snippets snippet-shots api-delta release-docs
# Debug and sanitizer targets
debug:
@echo "Building McRogueFace with debug Python (pydebug assertions)..."
@mkdir -p build-debug
@cd build-debug && cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DMCRF_DEBUG_PYTHON=ON && make -j$(JOBS)
@echo "Debug build complete! Output: build-debug/mcrogueface"
debug-test: debug
@echo "Running test suite with debug Python..."
cd tests && MCRF_BUILD_DIR=../build-debug \
MCRF_LIB_DIR=../__lib_debug \
python3 run_tests.py -v
# Profiling build (#345): RelWithDebInfo (-O2 -g) + frame pointers, self-contained
# in build-profile/ (lib/assets/scripts copied like the normal build). Suited to
# both Callgrind (deterministic, no perms) and perf (--call-graph fp).
profile:
@echo "Building McRogueFace for profiling (RelWithDebInfo + frame pointers)..."
@mkdir -p build-profile
@cd build-profile && cmake .. \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DMCRF_PROFILE=ON && make -j$(JOBS)
@echo "Profile build complete! Binary: build-profile/mcrogueface"
# Callgrind a headless benchmark script. Deterministic, exact instruction counts,
# no special permissions. Usage: make callgrind SCRIPT=tests/benchmarks/foo.py
# Output: callgrind.out (feed to callgrind_annotate).
SCRIPT ?= tests/benchmarks/issue_331_property_read_bench.py
callgrind: profile
@echo "Running Callgrind on $(SCRIPT)..."
@cd build-profile && valgrind --tool=callgrind \
--callgrind-out-file=callgrind.out \
./mcrogueface --headless --exec ../$(SCRIPT)
@echo "Done. Annotate with: callgrind_annotate build-profile/callgrind.out | head -60"
clean-profile:
@echo "Cleaning profile build..."
@rm -rf build-profile
asan:
@echo "Building McRogueFace with ASan + UBSan..."
@mkdir -p build-asan
@cd build-asan && cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DMCRF_DEBUG_PYTHON=ON \
-DMCRF_SANITIZE_ADDRESS=ON \
-DMCRF_SANITIZE_UNDEFINED=ON && make -j$(JOBS)
@echo "ASan build complete! Output: build-asan/mcrogueface"
asan-test: asan
@echo "Running test suite under ASan + UBSan..."
cd tests && MCRF_BUILD_DIR=../build-asan \
MCRF_LIB_DIR=../__lib_debug \
PYTHONMALLOC=malloc \
ASAN_OPTIONS="detect_leaks=1:halt_on_error=1:print_summary=1" \
LSAN_OPTIONS="suppressions=$(CURDIR)/sanitizers/asan.supp" \
UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1" \
python3 run_tests.py -v --sanitizer
# Fuzzing targets (clang-18 + libFuzzer + ASan + UBSan).
# Design: ONE instrumented executable `mcrfpy_fuzz` that embeds CPython,
# registers the mcrfpy module, and dispatches each libFuzzer iteration to
# a Python `fuzz_one_input(data)` function loaded from the script named by
# the MCRF_FUZZ_TARGET env var. libFuzzer instruments the C++ engine code
# where all the #258-#278 bugs live. No atheris dependency.
FUZZ_TARGETS := grid_entity property_types anim_timer_scene maps_procgen fov pathfinding_behavior audio_dsp import_parsers texture_factory shader_bindings
FUZZ_SECONDS ?= 30
# Shared env for running the fuzz binary. PYTHONHOME points at the build-fuzz
# copy of the bundled stdlib (post-build copied into build-fuzz/lib/).
# ASAN_OPTIONS: leak detection disabled because libFuzzer intentionally holds
# inputs for its corpus; abort_on_error ensures crashes are loud and repro-able.
define FUZZ_ENV
MCRF_LIB_DIR=../__lib_debug \
PYTHONMALLOC=malloc \
PYTHONHOME=../__lib/Python \
ASAN_OPTIONS="detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1" \
UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1"
endef
fuzz-build:
@echo "Building mcrfpy_fuzz with libFuzzer + ASan (clang-18)..."
@mkdir -p build-fuzz
@cd build-fuzz && CC=clang-18 CXX=clang++-18 cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DMCRF_DEBUG_PYTHON=ON \
-DMCRF_SANITIZE_ADDRESS=ON \
-DMCRF_SANITIZE_UNDEFINED=ON \
-DMCRF_FUZZER=ON \
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld && make -j$(JOBS) mcrfpy_fuzz
@echo "Fuzz build complete! Output: build-fuzz/mcrfpy_fuzz"
fuzz: fuzz-build
@for t in $(FUZZ_TARGETS); do \
if [ ! -f tests/fuzz/fuzz_$$t.py ]; then \
echo "SKIP: tests/fuzz/fuzz_$$t.py does not exist yet"; \
continue; \
fi; \
echo "=== fuzzing $$t for $(FUZZ_SECONDS)s ==="; \
mkdir -p tests/fuzz/corpora/$$t tests/fuzz/crashes; \
( cd build-fuzz && $(FUZZ_ENV) MCRF_FUZZ_TARGET=$$t \
./mcrfpy_fuzz \
-max_total_time=$(FUZZ_SECONDS) \
-artifact_prefix=../tests/fuzz/crashes/$$t- \
../tests/fuzz/corpora/$$t ../tests/fuzz/seeds/$$t ) || exit 1; \
done
fuzz-long: fuzz-build
@test -n "$(TARGET)" || (echo "Usage: make fuzz-long TARGET=<name> SECONDS=<n>"; exit 1)
@test -f tests/fuzz/fuzz_$(TARGET).py || (echo "No target: tests/fuzz/fuzz_$(TARGET).py"; exit 1)
@mkdir -p tests/fuzz/corpora/$(TARGET) tests/fuzz/crashes
@( cd build-fuzz && $(FUZZ_ENV) MCRF_FUZZ_TARGET=$(TARGET) \
./mcrfpy_fuzz \
-max_total_time=$(or $(SECONDS),3600) \
-artifact_prefix=../tests/fuzz/crashes/$(TARGET)- \
../tests/fuzz/corpora/$(TARGET) ../tests/fuzz/seeds/$(TARGET) )
fuzz-repro:
@test -n "$(TARGET)" || (echo "Usage: make fuzz-repro TARGET=<name> CRASH=<path>"; exit 1)
@test -n "$(CRASH)" || (echo "Usage: make fuzz-repro TARGET=<name> CRASH=<path>"; exit 1)
@( cd build-fuzz && $(FUZZ_ENV) MCRF_FUZZ_TARGET=$(TARGET) \
./mcrfpy_fuzz ../$(CRASH) )
clean-fuzz:
@echo "Cleaning fuzz build and corpora..."
@rm -rf build-fuzz tests/fuzz/corpora tests/fuzz/crashes
tsan:
@echo "Building McRogueFace with TSan + free-threaded Python..."
@echo "NOTE: Requires free-threaded debug Python built with:"
@echo " tools/build_debug_python.sh --tsan"
@mkdir -p build-tsan
@cd build-tsan && cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DMCRF_FREE_THREADED_PYTHON=ON \
-DMCRF_SANITIZE_THREAD=ON && make -j$(JOBS)
@echo "TSan build complete! Output: build-tsan/mcrogueface"
tsan-test: tsan
@echo "Running test suite under TSan..."
cd tests && MCRF_BUILD_DIR=../build-tsan \
MCRF_LIB_DIR=../__lib_debug \
TSAN_OPTIONS="halt_on_error=1:second_deadlock_stack=1" \
python3 run_tests.py -v --sanitizer
valgrind-test: debug
@echo "Running test suite under Valgrind memcheck..."
cd tests && MCRF_BUILD_DIR=../build-debug \
MCRF_LIB_DIR=../__lib_debug \
MCRF_TIMEOUT_MULTIPLIER=50 \
PYTHONMALLOC=malloc \
python3 run_tests.py -v --valgrind
massif-test: debug
@echo "Running heap profiling under Valgrind Massif..."
@mkdir -p build-debug
cd build-debug && valgrind --tool=massif \
--massif-out-file=massif.out \
--pages-as-heap=no \
--detailed-freq=10 \
--max-snapshots=100 \
./mcrogueface --headless --exec ../tests/benchmarks/stress_test_suite.py
@echo "Massif output: build-debug/massif.out"
@echo "View with: ms_print build-debug/massif.out"
analyze:
@echo "Running cppcheck static analysis..."
cppcheck --enable=warning,performance,portability \
--suppress=missingIncludeSystem \
--suppress=unusedFunction \
--suppress=noExplicitConstructor \
--suppress=missingOverride \
--inline-suppr \
-I src/ -I deps/ -I deps/cpython -I deps/Python \
-I src/platform -I src/3d -I src/tiled -I src/ldtk -I src/audio \
--std=c++20 \
--quiet \
src/ 2>&1
@echo "Static analysis complete."
clean-debug:
@echo "Cleaning debug/sanitizer builds..."
@rm -rf build-debug build-asan build-tsan build-fuzz
# Packaging targets using tools/package.sh
package-windows-light: windows
@./tools/package.sh windows light
package-windows-full: windows
@./tools/package.sh windows full
package-linux-light: linux
@./tools/package.sh linux light
package-linux-full: linux
@./tools/package.sh linux full
package-all: windows linux
@./tools/package.sh all
# Legacy target for backwards compatibility
package-windows: package-windows-full
# Emscripten / WebAssembly targets
# Requires: source ~/emsdk/emsdk_env.sh (or wherever your emsdk is installed)
#
# For iterative development, configure once then rebuild:
# source ~/emsdk/emsdk_env.sh && emmake make -C build-emscripten
#
wasm:
@if ! command -v emcmake >/dev/null 2>&1; then \
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
exit 1; \
fi
@if [ ! -f build-emscripten/Makefile ]; then \
echo "Configuring WebAssembly build (full game)..."; \
mkdir -p build-emscripten; \
cd build-emscripten && emcmake cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DMCRF_SDL2=ON; \
fi
@echo "Building McRogueFace for WebAssembly..."
@emmake make -C build-emscripten -j$(JOBS)
@echo "WebAssembly dev build complete (game + REPL console)! Files in build-emscripten/"
@echo "Run 'make serve-dev' to test locally. For the clean shipped game, use 'make wasm-game' + 'make serve'."
playground:
@if ! command -v emcmake >/dev/null 2>&1; then \
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
exit 1; \
fi
@if [ ! -f build-playground/Makefile ]; then \
echo "Configuring Playground build..."; \
mkdir -p build-playground; \
cd build-playground && emcmake cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DMCRF_SDL2=ON \
-DMCRF_PLAYGROUND=ON; \
fi
@echo "Building McRogueFace Playground for WebAssembly..."
@emmake make -C build-playground -j$(JOBS)
@echo "Playground build complete! Files in build-playground/"
@echo "Run 'make serve-playground' to test locally"
serve:
@echo "Serving the clean game build (build-wasm-game) at http://localhost:8080"
@echo "(Run 'make wasm-game' first if it is not built. For the REPL dev build, use 'make serve-dev'.)"
@echo "Press Ctrl+C to stop"
@cd build-wasm-game && python3 -m http.server 8080
serve-dev:
@echo "Serving the wasm DEV build with REPL console (build-emscripten) at http://localhost:8080"
@echo "(Run 'make wasm' first if it is not built.)"
@echo "Press Ctrl+C to stop"
@cd build-emscripten && python3 -m http.server 8080
serve-playground:
@echo "Serving Playground build at http://localhost:8080"
@echo "Press Ctrl+C to stop"
@cd build-playground && python3 -m http.server 8080
wasm-game:
@if ! command -v emcmake >/dev/null 2>&1; then \
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
exit 1; \
fi
@if [ ! -f build-wasm-game/Makefile ]; then \
echo "Configuring WebAssembly game build (fullscreen, no REPL)..."; \
mkdir -p build-wasm-game; \
cd build-wasm-game && emcmake cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DMCRF_SDL2=ON \
-DMCRF_GAME_SHELL=ON; \
fi
@echo "Building McRogueFace game for WebAssembly..."
@emmake make -C build-wasm-game -j$(JOBS)
@echo "Clean game build complete (no REPL console)! Files in build-wasm-game/"
@echo "Run 'make serve' (or 'make serve-game') to test locally"
serve-game:
@echo "Serving game build at http://localhost:8080"
@echo "Press Ctrl+C to stop"
@cd build-wasm-game && python3 -m http.server 8080
wasm-demo:
@if ! command -v emcmake >/dev/null 2>&1; then \
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
exit 1; \
fi
@if [ ! -f build-wasm-demo/Makefile ]; then \
echo "Configuring WebAssembly demo build..."; \
mkdir -p build-wasm-demo; \
cd build-wasm-demo && emcmake cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DMCRF_SDL2=ON \
-DMCRF_DEMO=ON \
-DMCRF_GAME_SHELL=ON; \
fi
@echo "Building McRogueFace demo for WebAssembly..."
@emmake make -C build-wasm-demo -j$(JOBS)
@cp web/index.html build-wasm-demo/index.html
@echo "Demo build complete! Files in build-wasm-demo/"
@echo "Run 'make serve-demo' to test locally"
serve-demo:
@echo "Serving demo build at http://localhost:8080"
@echo "Press Ctrl+C to stop"
@cd build-wasm-demo && python3 -m http.server 8080
clean-wasm:
@echo "Cleaning Emscripten builds..."
@rm -rf build-emscripten build-playground build-wasm-game build-wasm-demo build-wasm-debug build-playground-debug
wasm-debug:
@if ! command -v emcmake >/dev/null 2>&1; then \
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
exit 1; \
fi
@if [ ! -f build-wasm-debug/Makefile ]; then \
echo "Configuring WebAssembly debug build (DWARF + source maps)..."; \
mkdir -p build-wasm-debug; \
cd build-wasm-debug && emcmake cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DMCRF_SDL2=ON \
-DMCRF_WASM_DEBUG=ON; \
fi
@echo "Building McRogueFace for WebAssembly (debug)..."
@emmake make -C build-wasm-debug -j$(JOBS)
@echo "Debug WASM build complete! Files in build-wasm-debug/"
@echo "Debug artifacts: .wasm.map (source map), .symbols (symbol map)"
@echo "Run 'make serve-wasm-debug' to test locally"
serve-wasm-debug:
@echo "Serving debug WASM build at http://localhost:8080"
@echo "Press Ctrl+C to stop"
@cd build-wasm-debug && python3 -m http.server 8080
playground-debug:
@if ! command -v emcmake >/dev/null 2>&1; then \
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
exit 1; \
fi
@if [ ! -f build-playground-debug/Makefile ]; then \
echo "Configuring Playground debug build (DWARF + source maps)..."; \
mkdir -p build-playground-debug; \
cd build-playground-debug && emcmake cmake .. \
-DCMAKE_BUILD_TYPE=Debug \
-DMCRF_SDL2=ON \
-DMCRF_PLAYGROUND=ON \
-DMCRF_WASM_DEBUG=ON; \
fi
@echo "Building McRogueFace Playground for WebAssembly (debug)..."
@emmake make -C build-playground-debug -j$(JOBS)
@echo "Playground debug build complete! Files in build-playground-debug/"
@echo "Run 'make serve-playground-debug' to test locally"
serve-playground-debug:
@echo "Serving debug Playground build at http://localhost:8080"
@echo "Press Ctrl+C to stop"
@cd build-playground-debug && python3 -m http.server 8080
# Current version extracted from source
CURRENT_VERSION := $(shell grep 'MCRFPY_VERSION' src/McRogueFaceVersion.h | sed 's/.*"\(.*\)"/\1/')
# Release workflow: tag current version, build all packages, bump to next version
# Usage: make version-bump NEXT_VERSION=0.2.6-prerelease-7drl2026
version-bump:
ifndef NEXT_VERSION
$(error Usage: make version-bump NEXT_VERSION=x.y.z-suffix)
endif
@if ! command -v emcmake >/dev/null 2>&1; then \
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
exit 1; \
fi
# git status (clean working dir check), but ignore modules/, because building submodules dirties their subdirs
@if [ -n "$$(git status --porcelain | grep -v modules)" ]; then \
echo "Error: Working tree is not clean. Commit or stash changes first."; \
exit 1; \
fi
@echo "=== Releasing $(CURRENT_VERSION) ==="
@# Idempotent tag: ok if it already points at HEAD (resuming partial run)
@if git rev-parse "$(CURRENT_VERSION)" >/dev/null 2>&1; then \
TAG_COMMIT=$$(git rev-parse "$(CURRENT_VERSION)^{}"); \
HEAD_COMMIT=$$(git rev-parse HEAD); \
if [ "$$TAG_COMMIT" != "$$HEAD_COMMIT" ]; then \
echo "Error: Tag $(CURRENT_VERSION) already exists but points to a different commit."; \
exit 1; \
fi; \
echo "Tag $(CURRENT_VERSION) already exists at HEAD (resuming)."; \
else \
git tag "$(CURRENT_VERSION)"; \
fi
$(MAKE) package-linux-full
$(MAKE) package-windows-full
$(MAKE) wasm-game
@echo "Packaging WASM build (clean game, no REPL console)..."
@mkdir -p dist
cd build-wasm-game && zip -r ../dist/McRogueFace-$(CURRENT_VERSION)-WASM.zip \
mcrogueface.html mcrogueface.js mcrogueface.wasm mcrogueface.data
@echo ""
@echo "Bumping version: $(CURRENT_VERSION) -> $(NEXT_VERSION)"
@sed -i 's|MCRFPY_VERSION "$(CURRENT_VERSION)"|MCRFPY_VERSION "$(NEXT_VERSION)"|' src/McRogueFaceVersion.h
@TAGGED_HASH=$$(git rev-parse --short HEAD); \
git add src/McRogueFaceVersion.h && \
git commit -m "Version bump: $(CURRENT_VERSION) ($$TAGGED_HASH) -> $(NEXT_VERSION)"
@echo ""
@echo "=== Release $(CURRENT_VERSION) complete ==="
@echo "Tag: $(CURRENT_VERSION)"
@echo "Next: $(NEXT_VERSION)"
@echo "Packages:"
@ls -lh dist/*$(CURRENT_VERSION)* 2>/dev/null

View file

@ -0,0 +1,93 @@
# Phase 1-3 Completion Summary
## Overview
Successfully completed all tasks in Phases 1, 2, and 3 of the alpha_streamline_2 branch. This represents a major architectural improvement to McRogueFace's Python API, making it more consistent, safer, and feature-rich.
## Phase 1: Architecture Stabilization (Completed)
- ✅ #7 - Audited and fixed unsafe constructors across all UI classes
- ✅ #71 - Implemented _Drawable base class properties at C++ level
- ✅ #87 - Added visible property for show/hide functionality
- ✅ #88 - Added opacity property for transparency control
- ✅ #89 - Added get_bounds() method returning (x, y, width, height)
- ✅ #98 - Added move()/resize() methods for dynamic UI manipulation
## Phase 2: API Enhancements (Completed)
- ✅ #101 - Standardized default positions (all UI elements default to 0,0)
- ✅ #38 - Frame accepts children parameter in constructor
- ✅ #42 - All UI elements accept click handler in __init__
- ✅ #90 - Grid accepts size as tuple: Grid((20, 15))
- ✅ #19 - Sprite texture swapping via texture property
- ✅ #52 - Grid rendering skips out-of-bounds entities
## Phase 3: Game-Ready Features (Completed)
- ✅ #30 - Entity.die() method for proper cleanup
- ✅ #93 - Vector arithmetic operators (+, -, *, /, ==, bool, abs, neg)
- ✅ #94 - Color helper methods (from_hex, to_hex, lerp)
- ✅ #103 - Timer objects with pause/resume/cancel functionality
## Additional Improvements
- ✅ Standardized position arguments across all UI classes
- Created PyPositionHelper for consistent argument parsing
- All classes now accept: (x, y), pos=(x,y), x=x, y=y formats
- ✅ Fixed UTF-8 encoding configuration for Python output
- Configured PyConfig.stdio_encoding during initialization
- Resolved unicode character printing issues
## Technical Achievements
### Architecture
- Safe two-phase initialization for all Python objects
- Consistent constructor patterns across UI hierarchy
- Proper shared_ptr lifetime management
- Clean separation between C++ implementation and Python API
### API Consistency
- All UI elements follow same initialization patterns
- Position arguments work uniformly across all classes
- Properties accessible via standard Python attribute access
- Methods follow Python naming conventions
### Developer Experience
- Intuitive object construction with sensible defaults
- Flexible argument formats reduce boilerplate
- Clear error messages for invalid inputs
- Comprehensive test coverage for all features
## Impact on Game Development
### Before
```python
# Inconsistent, error-prone API
frame = mcrfpy.Frame()
frame.x = 100 # Had to set position after creation
frame.y = 50
caption = mcrfpy.Caption(mcrfpy.default_font, "Hello", 20, 20) # Different argument order
grid = mcrfpy.Grid(10, 10, 32, 32, 0, 0) # Confusing parameter order
```
### After
```python
# Clean, consistent API
frame = mcrfpy.Frame(x=100, y=50, children=[
mcrfpy.Caption("Hello", pos=(20, 20)),
mcrfpy.Sprite("icon.png", (10, 10))
])
grid = mcrfpy.Grid(size=(10, 10), pos=(0, 0))
# Advanced features
timer = mcrfpy.Timer("animation", update_frame, 16)
timer.pause() # Pause during menu
timer.resume() # Resume when gameplay continues
player.move(velocity * delta_time) # Vector math works naturally
ui_theme = mcrfpy.Color.from_hex("#2D3436")
```
## Next Steps
With Phases 1-3 complete, the codebase is ready for:
- Phase 4: Event System & Animations (advanced interactivity)
- Phase 5: Scene Management (transitions, lifecycle)
- Phase 6: Audio System (procedural generation, effects)
- Phase 7: Optimization (sprite batching, profiling)
The foundation is now solid for building sophisticated roguelike games with McRogueFace.

222
README.md
View file

@ -3,221 +3,79 @@
A Python-powered 2D game engine for creating roguelike games, built with C++ and SFML.
* Core roguelike logic from libtcod: field of view, pathfinding
* Animate sprites with multiple frames. Smooth transitions for positions, sizes, zoom, and camera
* Simple GUI element system allows keyboard and mouse input, composition
* No compilation or installation necessary. The runtime is a full Python environment; "Zip And Ship"
**Pre-Alpha Release Demo**: my 7DRL 2025 entry *"Crypt of Sokoban"* - a prototype with buttons, boulders, enemies, and items.
📖 **[Full Documentation & Tutorials](https://mcrogueface.github.io/)** - Quickstart guide, API reference, and cookbook
## Tenets
- **Python & C++ Hand-in-Hand**: Create your game without ever recompiling. Your Python commands create C++ objects, and animations can occur without calling Python at all.
- **Simple Yet Flexible UI System**: Sprites, Grids, Frames, and Captions with full animation support
- **Entity-Component Architecture**: Implement your game objects with Python integration
- **Built-in Roguelike Support**: Dungeon generation, pathfinding, and field-of-view via libtcod (demos still under construction)
- **Automation API**: PyAutoGUI-inspired event generation framework. All McRogueFace interactions can be performed headlessly via script: for software testing or AI integration
- **Interactive Development**: Python REPL integration for live game debugging. Use `mcrogueface` like a Python interpreter
## Quick Start
**Download** the [latest release](https://github.com/jmccardle/McRogueFace/releases/latest):
- **Windows**: `McRogueFace-*-Win.zip`
- **Linux**: `McRogueFace-*-Linux.tar.bz2`
```bash
# Clone and build
git clone <wherever you found this repo>
cd McRogueFace
make
Extract and run `mcrogueface` (or `mcrogueface.exe` on Windows) to see the demo game.
# Run the example game
cd build
./mcrogueface
```
### Your First Game
Create `scripts/game.py` (or edit the existing one):
## Example: Creating a Simple Scene
```python
import mcrfpy
# Create and activate a scene
scene = mcrfpy.Scene("game")
scene.activate()
# Create a new scene
mcrfpy.createScene("intro")
# Load a sprite sheet
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
# Add a text caption
caption = mcrfpy.Caption((50, 50), "Welcome to McRogueFace!")
caption.size = 48
caption.fill_color = (255, 255, 255)
# Create a tile grid
grid = mcrfpy.Grid(grid_size=(20, 15), texture=texture, pos=(50, 50), size=(640, 480))
grid.zoom = 2.0
scene.children.append(grid)
# Add to scene
mcrfpy.sceneUI("intro").append(caption)
# Add a player entity
player = mcrfpy.Entity(pos=(10, 7), texture=texture, sprite_index=84)
grid.entities.append(player)
# Handle keyboard input
def on_key(key, state):
if state != "start":
return
x, y = int(player.x), int(player.y)
if key == "W": y -= 1
elif key == "S": y += 1
elif key == "A": x -= 1
elif key == "D": x += 1
player.x, player.y = x, y
scene.on_key = on_key
```
Run `mcrogueface` and you have a movable character!
### Visual Framework
- **Sprite**: Single image or sprite from a shared sheet
- **Caption**: Text rendering with fonts
- **Frame**: Container rectangle for composing UIs
- **Grid**: 2D tile array with zoom and camera control
- **Entity**: Grid-based game object with sprite and pathfinding
- **Animation**: Interpolate any property over time with easing
## Building from Source
For most users, pre-built releases are available. If you need to build from source:
### Quick Build (with pre-built dependencies)
Download `build_deps.tar.gz` from the releases page, then:
```bash
git clone <repository-url> McRogueFace
cd McRogueFace
tar -xzf /path/to/build_deps.tar.gz
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
```
### Full Build (compiling all dependencies)
```bash
git clone --recursive <repository-url> McRogueFace
cd McRogueFace
# See BUILD_FROM_SOURCE.md for complete instructions
```
**[BUILD_FROM_SOURCE.md](BUILD_FROM_SOURCE.md)** - Complete build guide including:
- System dependency installation
- Compiling SFML, Python, and libtcod-headless from source
- Creating `build_deps` archives for distribution
- Troubleshooting common build issues
### System Requirements
- **Linux**: Debian/Ubuntu tested; other distros should work
- **Windows**: Supported (see build guide for details)
- **macOS**: Untested
## Example: Main Menu with Buttons
```python
import mcrfpy
# Create a scene
scene = mcrfpy.Scene("menu")
# Add a background frame
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768),
fill_color=mcrfpy.Color(20, 20, 40))
scene.children.append(bg)
# Add a title
title = mcrfpy.Caption(pos=(312, 100), text="My Roguelike",
fill_color=mcrfpy.Color(255, 255, 100))
title.font_size = 48
scene.children.append(title)
# Create a button
button = mcrfpy.Frame(pos=(362, 300), size=(300, 80),
fill_color=mcrfpy.Color(50, 150, 50))
button_text = mcrfpy.Caption(pos=(90, 25), text="Start Game")
button.children.append(button_text)
def on_click(x, y, btn):
print("Game starting!")
button.on_click = on_click
scene.children.append(button)
scene.activate()
# Switch to the scene
mcrfpy.setScene("intro")
```
## Documentation
### 📚 Developer Documentation
For comprehensive documentation, tutorials, and API reference, visit:
**[https://mcrogueface.github.io](https://mcrogueface.github.io)**
For comprehensive documentation about systems, architecture, and development workflows:
**[Project Wiki](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki)**
Key wiki pages:
- **[Home](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Home)** - Documentation hub with multiple entry points
- **[Grid System](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Grid-System)** - Three-layer grid architecture
- **[Python Binding System](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Python-Binding-System)** - C++/Python integration
- **[Performance and Profiling](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Performance-and-Profiling)** - Optimization tools
- **[Adding Python Bindings](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Adding-Python-Bindings)** - Step-by-step binding guide
- **[Issue Roadmap](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Issue-Roadmap)** - All open issues organized by system
### 📖 Development Guides
In the repository root:
- **[CLAUDE.md](CLAUDE.md)** - Build instructions, testing guidelines, common tasks
- **[ROADMAP.md](ROADMAP.md)** - Strategic vision and development phases
- **[docs/api-stability.md](docs/api-stability.md)** - 1.0 compatibility policy (value semantics, bulk-edit convention, subinterpreter exclusion)
- **[docs/threading-model.md](docs/threading-model.md)** - Threading contract for off-main-thread access via `mcrfpy.lock()`
- **[roguelike_tutorial/](roguelike_tutorial/)** - Complete roguelike tutorial implementations
> **Note:** Running mcrfpy in a Python subinterpreter is unsupported in 1.x; the module declares `m_size = -1` and will refuse or misbehave. See [#220](https://dev.ffwf.net/forgejo/john/McRogueFace/issues/220).
## Build Requirements
## Requirements
- C++17 compiler (GCC 7+ or Clang 5+)
- CMake 3.14+
- Python 3.14 (embedded)
- SFML 2.6
- Python 3.12+
- SFML 2.5+
- Linux or Windows (macOS untested)
See [BUILD_FROM_SOURCE.md](BUILD_FROM_SOURCE.md) for detailed compilation instructions.
## Project Structure
```
McRogueFace/
├── assets/ # Sprites, fonts, audio
├── build/ # Build output: this is what you distribute
│ ├── assets/ # (copied from assets/)
│ ├── scripts/ # (copied from src/scripts/)
│ └── lib/ # Python stdlib and extension modules
├── docs/ # Generated HTML, markdown API docs
├── src/ # C++ engine source
│ └── scripts/ # Python game scripts
├── stubs/ # .pyi type stubs for IDE integration
├── tests/ # Automated test suite
└── tools/ # Documentation generation scripts
├── scripts/ # Python game scripts
├── assets/ # Sprites, fonts, audio
├── build/ # Build output directory
└── tests/ # Automated test suite
```
If you are building McRogueFace to implement game logic or scene configuration in C++, you'll have to compile the project.
If you are writing a game in Python using McRogueFace, you only need to rename and zip/distribute the `build` directory.
## Philosophy
- **C++ every frame, Python every tick**: All rendering data is handled in C++. Structure your UI and program animations in Python, and they are rendered without Python. All game logic can be written in Python.
- **No Compiling Required; Zip And Ship**: Implement your game objects with Python, zip up McRogueFace with your "game.py" to ship
- **Built-in Roguelike Support**: Dungeon generation, pathfinding, and field-of-view via libtcod
- **Hands-Off Testing**: PyAutoGUI-inspired event generation framework. All McRogueFace interactions can be performed headlessly via script: for software testing or AI integration
- **Interactive Development**: Python REPL integration for live game debugging. Use `mcrogueface` like a Python interpreter
## Contributing
PRs will be considered! Please include explicit mention that your contribution is your own work and released under the MIT license in the pull request.
### Issue Tracking
The project uses [Forgejo Issues](https://dev.ffwf.net/forgejo/john/McRogueFace/issues) for task tracking and bug reports. Issues are organized with labels:
- **System labels** (grid, animation, python-binding, etc.) - identify which codebase area
- **Priority labels** (tier1-active, tier2-foundation, tier3-future) - development timeline
- **Type labels** (Major Feature, Minor Feature, Bugfix, etc.) - effort and scope
See the [Issue Roadmap](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Issue-Roadmap) on the wiki for organized view of all open tasks.
The project has a private roadmap and issue list. Reach out via email or social media if you have bugs or feature requests.
## License
@ -225,6 +83,6 @@ This project is licensed under the MIT License - see LICENSE file for details.
## Acknowledgments
- Developed for 7-Day Roguelike 2023, 2024, 2025, 2026 - here's to many more
- Developed for 7-Day Roguelike 2023, 2024, 2025 - here's to many more
- Built with [SFML](https://www.sfml-dev.org/), [libtcod](https://github.com/libtcod/libtcod), and Python
- Inspired by David Churchill's COMP4300 game engine lectures

167
RENDERTEXTURE_DESIGN.md Normal file
View file

@ -0,0 +1,167 @@
# RenderTexture Overhaul Design Document
## Overview
This document outlines the design for implementing RenderTexture support across all UIDrawable classes in McRogueFace. This is Issue #6 and represents a major architectural change to the rendering system.
## Goals
1. **Automatic Clipping**: Children rendered outside parent bounds should be clipped
2. **Off-screen Rendering**: Enable post-processing effects and complex compositing
3. **Performance**: Cache static content, only re-render when changed
4. **Backward Compatibility**: Existing code should continue to work
## Current State
### Classes Already Using RenderTexture:
- **UIGrid**: Uses a 1920x1080 RenderTexture for compositing grid view
- **SceneTransition**: Uses two 1024x768 RenderTextures for transitions
- **HeadlessRenderer**: Uses RenderTexture for headless mode
### Classes Using Direct Rendering:
- **UIFrame**: Renders box and children directly
- **UICaption**: Renders text directly
- **UISprite**: Renders sprite directly
## Design Decisions
### 1. Opt-in Architecture
Not all UIDrawables need RenderTextures. We'll use an opt-in approach:
```cpp
class UIDrawable {
protected:
// RenderTexture support (opt-in)
std::unique_ptr<sf::RenderTexture> render_texture;
sf::Sprite render_sprite;
bool use_render_texture = false;
bool render_dirty = true;
// Enable RenderTexture for this drawable
void enableRenderTexture(unsigned int width, unsigned int height);
void updateRenderTexture();
};
```
### 2. When to Use RenderTexture
RenderTextures will be enabled for:
1. **UIFrame with clipping enabled** (new property: `clip_children = true`)
2. **UIDrawables with effects** (future: shaders, blend modes)
3. **Complex composites** (many children that rarely change)
### 3. Render Flow
```
Standard Flow:
render() → render directly to target
RenderTexture Flow:
render() → if dirty → clear RT → render to RT → dirty = false
→ draw RT sprite to target
```
### 4. Dirty Flag Management
Mark as dirty when:
- Properties change (position, size, color, etc.)
- Children added/removed
- Child marked as dirty (propagate up)
- Animation frame
### 5. Size Management
RenderTexture size options:
1. **Fixed Size**: Set at creation (current UIGrid approach)
2. **Dynamic Size**: Match bounds, recreate on resize
3. **Pooled Sizes**: Use standard sizes from pool
We'll use **Dynamic Size** with lazy creation.
## Implementation Plan
### Phase 1: Base Infrastructure (This PR)
1. Add RenderTexture members to UIDrawable
2. Add `enableRenderTexture()` method
3. Implement dirty flag system
4. Add `clip_children` property to UIFrame
### Phase 2: UIFrame Implementation
1. Update UIFrame::render() to use RenderTexture when clipping
2. Test with nested frames
3. Verify clipping works correctly
### Phase 3: Performance Optimization
1. Implement texture pooling
2. Add dirty flag propagation
3. Profile and optimize
### Phase 4: Extended Features
1. Blur/glow effects using RenderTexture
2. Viewport-based rendering (#8)
3. Screenshot improvements
## API Changes
### Python API:
```python
# Enable clipping on frames
frame.clip_children = True # New property
# Future: effects
frame.blur_amount = 5.0
sprite.glow_color = Color(255, 200, 100)
```
### C++ API:
```cpp
// Enable RenderTexture
frame->enableRenderTexture(width, height);
frame->setClipChildren(true);
// Mark dirty
frame->markDirty();
```
## Performance Considerations
1. **Memory**: Each RenderTexture uses GPU memory (width * height * 4 bytes)
2. **Creation Cost**: Creating RenderTextures is expensive, use pooling
3. **Clear Cost**: Clearing large RenderTextures each frame is costly
4. **Bandwidth**: Drawing to RenderTexture then to screen doubles bandwidth
## Migration Strategy
1. All existing code continues to work (direct rendering by default)
2. Gradually enable RenderTexture for specific use cases
3. Profile before/after to ensure performance gains
4. Document best practices
## Risks and Mitigation
| Risk | Mitigation |
|------|------------|
| Performance regression | Opt-in design, profile extensively |
| Memory usage increase | Texture pooling, size limits |
| Complexity increase | Clear documentation, examples |
| Integration issues | Extensive testing with SceneTransition |
## Success Criteria
1. ✓ Frames can clip children to bounds
2. ✓ No performance regression for direct rendering
3. ✓ Scene transitions continue to work
4. ✓ Memory usage is reasonable
5. ✓ API is intuitive and documented
## Future Extensions
1. **Shader Support** (#106): RenderTextures enable post-processing shaders
2. **Particle Systems** (#107): Render particles to texture for effects
3. **Caching**: Static UI elements cached in RenderTextures
4. **Resolution Independence**: RenderTextures for DPI scaling
## Conclusion
This design provides a foundation for professional rendering capabilities while maintaining backward compatibility and performance. The opt-in approach allows gradual adoption and testing.

View file

@ -1,154 +1,803 @@
# McRogueFace - Development Roadmap
**Version**: 0.2.8 | **Era**: McRogueFace (2D roguelikes) -- on the road to 1.0
## 🚨 URGENT PRIORITIES - July 9, 2025 🚨
For detailed architecture, philosophy, and decision framework, see the [Strategic Direction](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Strategic-Direction) wiki page. For per-issue tracking, see the [Issue Roadmap](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Issue-Roadmap).
### IMMEDIATE ACTION REQUIRED (Next 48 Hours)
**CRITICAL DEADLINE**: RoguelikeDev Tutorial Event starts July 15 - Need to advertise by July 11!
#### 1. Tutorial Emergency Plan (2 DAYS)
- [ ] **Day 1 (July 9)**: Parts 1-2 (Setup, Moving @, Drawing Map, Entities)
- [ ] **Day 2 (July 10)**: Parts 3-4 (FOV, Combat/AI)
- [ ] **July 11**: Announce on r/roguelikedev with 4 completed parts
- [ ] **July 12-14**: Complete remaining 10 parts before event starts
#### 1b. Sizzle Reel Demo (URGENT)
- [ ] **Expand animation_sizzle_reel_working.py** with Grid/Entity demos:
- Grid scrolling and zooming animations
- Entity movement patterns (patrol, chase, flee)
- Particle effects using entity spawning
- Tile animation demonstrations
- Color cycling and transparency effects
- Mass entity choreography (100+ entities)
- Performance stress test with 1000+ entities
#### 2. TCOD Integration Sprint ✅ COMPLETE!
- [x] **UIGrid TCOD Integration** (8 hours) ✅ COMPLETED!
- ✅ Add TCODMap* to UIGrid constructor with proper lifecycle
- ✅ Implement complete Dijkstra pathfinding system
- ✅ Create mcrfpy.libtcod submodule with Python bindings
- ✅ Fix critical PyArg bug preventing Color object assignments
- ✅ Implement FOV with perspective rendering
- [ ] Add batch operations for NumPy-style access (deferred)
- [ ] Create CellView for ergonomic .at((x,y)) access (deferred)
- [x] **UIEntity Pathfinding** (4 hours) ✅ COMPLETED!
- ✅ Implement Dijkstra maps for multiple targets in UIGrid
- ✅ Add path_to(target) method using A* to UIEntity
- ✅ Cache paths in UIEntity for performance
#### 3. Performance Critical Path
- [ ] **Implement SpatialHash** for 10,000+ entities (2 hours)
- [ ] **Add dirty flag system** to UIGrid (1 hour)
- [ ] **Batch update context managers** (2 hours)
- [ ] **Memory pool for entities** (2 hours)
#### 4. Bug Fixing Pipeline
- [ ] Set up GitHub Issues automation
- [ ] Create test for each bug before fixing
- [ ] Track: Memory leaks, Segfaults, Python/C++ boundary errors
---
## What Has Shipped
## 🎯 STRATEGIC ARCHITECTURE VISION
**Alpha 0.1** (2024) -- First complete release. Milestone: all datatypes behaving.
### Three-Layer Grid Architecture (From Compass Research)
Following successful roguelike patterns (Caves of Qud, Cogmind, DCSS):
**0.2 series** (Jan-Mar 2026) -- Weekly updates to GitHub. Key additions:
- 3D/Voxel pipeline (experimental): Viewport3D, Camera3D, Entity3D, VoxelGrid with greedy meshing and serialization
- Procedural generation: HeightMap, BSP, NoiseSource, DiscreteMap
- Tiled and LDtk import with Wang tile / AutoRule resolution
- Emscripten/SDL2 backend for WebAssembly deployment
- Animation callbacks, mouse event system, grid cell callbacks
- Multi-layer grid system with chunk-based rendering and dirty-flag caching
- Documentation macro system with auto-generated API docs, man pages, and type stubs
- Windows cross-compilation, mobile-ish WASM support, SDL2_mixer audio
- Behavior/Trigger turn manager: `grid.step()`, entity labels, `cell_pos`, Dijkstra-backed pathfinding (#295-#303)
1. **Visual Layer** (UIGridPoint) - Sprites, colors, animations
2. **World State Layer** (TCODMap) - Walkability, transparency, physics
3. **Entity Perspective Layer** (UIGridPointState) - Per-entity FOV, knowledge
**Proving grounds**: Crypt of Sokoban (7DRL 2025), then 7DRL 2026 -- both shipped on the same engine. The 2026 jam surfaced hotfix-worthy issues (SDL key scancodes, composite textures) that have since landed on master.
### Performance Architecture (Critical for 1000x1000 maps)
- **Spatial Hashing** for entity queries (not quadtrees!)
- **Batch Operations** with context managers (10-100x speedup)
- **Memory Pooling** for entities and components
- **Dirty Flag System** to avoid unnecessary updates
- **Zero-Copy NumPy Integration** via buffer protocol
### Key Insight from Research
"Minimizing Python/C++ boundary crossings matters more than individual function complexity"
- Batch everything possible
- Use context managers for logical operations
- Expose arrays, not individual cells
- Profile and optimize hot paths only
---
## Current Focus: API Freeze + Memory Safety Sweep
## Project Status: 🎉 ALPHA 0.1 RELEASE! 🎉
7DRL 2026 is behind us (Feb 28 -- Mar 8). The engine has two concurrent tracks to 1.0:
### Track 1: API Freeze
The process is underway. Closed in this pass: camelCase module functions (#304), deprecated `sprite_number` (#305), legacy string enum comparisons (#306), `Color.__eq__`/`__ne__` (#307), `Grid.position` alias (#308). The freeze decisions are now locked behind a public API-surface snapshot regression test (#314), so accidental signature drift fails CI.
Remaining freeze work:
1. Catalog every public Python class, method, and property -- audit against `stubs/mcrfpy.pyi` and generated docs (snapshot test now enforces the catalog)
2. Identify any last naming/signature/default changes before committing
3. Final breaking-change pass, bundled
4. Document the stable API as the contract
5. Experimental modules (3D/Voxel) stay out of the freeze with an `experimental` label
### Track 2: Fuzz-Driven Bug Sweep
The libFuzzer+ASan harness (#283) has nine work tranches merged: build plumbing (W1), native harness (W2/W3), then six targeted fuzzers under `tests/fuzz/`:
- `fuzz_grid_entity` -- EntityCollection lifetime (W4, fixed #258-#263, #273, #274)
- `fuzz_property_types` -- refcount / type confusion (W5, fixed #267, #268, #272)
- `fuzz_anim_timer_scene` -- animation/timer/scene lifecycles (W6)
- `fuzz_fov` -- compute_fov parameters (W8, fixed #310)
- `fuzz_maps_procgen` -- HeightMap/DiscreteMap interfaces (W7)
- `fuzz_pathfinding_behavior` -- Dijkstra + turn manager (W9, fixed #311)
Coverage extension (#312) added four more: `fuzz_audio_dsp` (SoundBuffer DSP), `fuzz_import_parsers` (Tiled/LDtk file parsers), `fuzz_texture_factory` (byte ingestion), `fuzz_shader_bindings` (uniform-binding lifetime), plus Tier C surface folded into the existing targets. That run found five new bugs: #321 (HIGH -- ColorLayer.draw_fov bad-free), #322 (WangSet.terrain_enum error-pending abort), #323/#324/#325 (float→int UB in pitch_shift/hsl_shift/Vector) -- all five now fixed and merged, see Recently Shipped.
### Recently Shipped (April 2026)
- **#294** -- `entity.perspective_map` replaces flat `vector<UIGridPointState>` with a 3-state DiscreteMap (UNKNOWN/DISCOVERED/VISIBLE). Per-entity FOV memory is now serializable, swappable, and structurally enforces visible-as-subset-of-discovered.
- **#315** -- Pathfinding API extended with built-in heuristics (Euclidean/Manhattan/Chebyshev/Diagonal/Zero), multi-root Dijkstra, FLEE primitives (invert + descent), and an interactive demo. EntityBehavior SEEK/FLEE refactored to a `PathProvider` strategy.
- **Phase 5.2** -- six performance benchmark scripts under `tests/benchmarks/` covering grid.step(), FOV writeback cost, spatial hash vs. O(n), pathfinding with collision labels, multi-GridView render, and Dijkstra variants. Baselines under `tests/benchmarks/baseline/phase5_2/`.
- **Phase 5.3** -- documentation regenerated; `tools/generate_stubs_v2.py` rewritten as introspection-based so it can no longer drift from the C++ source.
### Recently Shipped (July 2026)
- **Grid input revival + the GridData/GridView type split (#355, #357-#368, #370, #371)** -- merged to master as `36e74e0`; 15 issues closed. Grid input had been dead since the #252 GridView unification: cell callbacks never fired, grid children were unclickable, cell hover was stuck. The cause was structural -- `UIGrid` inherited *both* `UIDrawable` and `GridData`, so every map secretly carried a second camera and RenderTexture that nothing on screen corresponded to, and input, children, and dirty propagation all dead-ended in it. **Input fix (#355, #357, #358, #360, #362, #363, #365, #366)**: cell callbacks and hit-testing move to `UIGridView` -- input belongs to the *camera*, not the data, so two views over one map track hover independently; a `weak_ptr` view registry replaces the single `owning_view` (N views, one map); `find()`/`findAll()` descend into grids again; the ImGui scene explorer can expand them; hover-exit fires when the cursor leaves the window; `UICollection.repr` names Grids instead of `"UIDrawable"`. **Type split (#359, #361, #364, #370, #371)**: `UIGrid` is **deleted, not demoted**. `mcrfpy.GridData` is now public and standalone-constructible -- a map with no position, size, camera, or `render()`; only `UIGridView` is a `UIDrawable`. `mcrfpy.Grid` is an *alias bound to the same type object* as `GridView` (not a subclass -- `isinstance` works both ways, no MRO to explain). Overlay children belong to the view; entities belong to the map. Breaking change: `entity.grid` now returns a `GridData` (the setter still accepts a view). The split flushed out four latent bugs, each verified against unmodified master before being fixed: **#370** `Grid.center_camera()` and `Grid.size = ...` were silent **no-ops** (writing to the ghost camera nothing rendered); **#371** the shader uniform binder did type-confused pointer arithmetic, reading a `GridView` wrapper as a `_GridData` wrapper -- which "worked" only because both wrappers had identical layout and both C++ classes happened to put `UIDrawable` at offset 0; **#368** `markContentDirty()`'s walk up the parent chain was gated on a flag with exactly one `clearDirty()` call site in the whole engine, so the guard was permanently false and content invalidation propagated **nowhere** -- `Frame(cache_subtree=True)` silently froze its subtree's content (text never updated, colours never changed; only movement survived, being already on an unconditional path). The walk is now unconditional; entity movement, the hot path, is unaffected (164 ns/move). **#367**: hover state is cleared on focus loss. Suite 329/329. Riding along on the same branch: generated docs are **untracked** (regenerated by `tools/hooks/pre-commit`, installed via `make install-hooks`, and shipped inside release artifacts), with a compact `api/manifest.json` -- signatures + docstring hashes + carried-forward `since`/`modified` lifecycle metadata -- as the one committed exception, so API drift stays diffable by ref via `tools/api_delta.py REF1 REF2` without rebuilding at old commits.
- **Grid render/memory sprint (#351, #334, #338, #332, #335; safety #353/#354)** -- a batch review closed dead/unbottlenecked issues (#117 entity pool, #145 TexturePool -- no demonstrated allocation gate; reopen after profiling) and shipped six grid-layer changes. **#351**: `UIGridView::render()` had *no* dirty check post-#252 and cleared+redrew every frame -- added a `GridData::content_generation` counter (bumped at every data/layer/entity mutation choke point) so an idle, non-perspective, childless view re-blits its cached texture instead of re-rendering; fixed a latent bug where Python entity `set_position`/`set_spritenumber` updated the spatial hash but never invalidated render (masked by the unconditional redraw). A follow-on regression (**6b99a1b**): the C++ turn manager `py_step` mutates entity cells directly, bypassing the setters -- it now calls `GridData::markCompositeDirty()` once per step (must qualify `GridData::`; `using UIDrawable::markCompositeDirty` shadows the data-layer override). **#334**: `DiscreteMap` exposes its `uint8_t` plane via the Python buffer protocol (`tp_as_buffer`, 2D zero-copy numpy/memoryview views). **#332**: `GridData` migrated from per-cell `UIGridPoint` structs (24 B for 2 B payload) + the whole chunk manager to two dense `std::vector<uint8_t>` planes (walkable/transparent) -- deleted `GridChunk`/`ChunkManager`/`CHUNK_THRESHOLD`, stripped `UIGridPoint` to a Python-accessor namespace, net **-257 lines**; pathfinding's `isCellWalkable`/`cellWalkable` were the critical non-obvious callers. **#335**: `ColorLayer`/`TileLayer` get zero-copy numpy views through a new `with layer.edit() as view:` context manager (implements the #328 convention; `__exit__` marks the layer dirty) -- `(h,w,4)` uint8 and `(h,w)` int32. **#338** (issue left open): safe subset only -- `rotationTexture` is now lazily allocated (`unique_ptr`) on Grid/GridView; deferred the `render_sprite` relocation as not worth the churn. Validation was delivered via **The Crucible** (**#354**, `tests/benchmarks/crucible.py`) -- a headless, deterministic, wall-clock A/B harness that safely replaces the windowed Gauntlet (which OOM-hard-locked the desktop during grid ramp). **#353** hardened the Gauntlet with `predict_bytes`/`max_load` refusal + an `RLIMIT_AS` backstop + RSS watchdog. Crucible A/B, current master vs the 0.2.8 dist artifact (both headless): grid_alloc **-58.9%**, layer_fill **-56.1%**, entity_churn **-43.0%**, grid_fill **-25.4%**, fov_storm/step_swarm/path_queries single-digit; **geomean 0.673 = ~32.7% faster overall**, peak RSS ~103 vs ~119 MB -- the big wins track #332 SoA + #329 indexing. Suite 314/314. Remaining in the thread: **#333** (lazy TCOD map rebuild, unblocked by #332), **#352** (perspective-grid early-out), and the deferred #338 relocation.
- **Native profiling rig + hot-path perf batch (#345, #331, #342/#343/#344/#348)** -- a Callgrind/perf profiling workflow (`make profile` -> `build-profile/` RelWithDebInfo + frame pointers; `make callgrind SCRIPT=...`; `docs/profiling.md`) landed as **#345**, then drove five measured fixes. **#331**: hot property getters stop re-importing `mcrfpy` per call. **#348** (found *by* Callgrind, not by guessing): `UIGridView::get_grid` re-allocated a throwaway Grid wrapper + weakref every call at 0% cache hit -- now holds one persistent strong ref (`get_grid` inclusive **72.6M -> 2.1M Ir, -97%**). **#342**: scalar-float animation fast path bypasses two per-frame `std::variant` visits (`Animation::update` **-18%**, variant machinery **473M -> 99M Ir**) -- the profiler *disproved* the guessed strcmp-cascade and weak_ptr-lock hypotheses and found the real cost. **#344**: memoized Key/InputState enum members instead of rebuilding via `EnumMeta.__call__` per event (enum-ctor **19.18M -> 0.228M Ir, -99%**; wall-clock -31%). **#343**: skip the per-frame `GetAttrString("update")` for non-subclassed scenes (**4,559 -> 3 Ir/frame** on the real `doFrame` loop; measuring it required bypassing headless `step()`, which doesn't call `updatePythonScenes()` -- filed as **#350**). Harness total instructions **3,522.9M -> 3,211.5M (-8.8%)**; suite 307/307 with new regression tests `issue_34{2,4,8}_*`. Durable sprint doc with A/B numbers at `docs/sprint-perf-342-348.md`. Spun out **#349** (hybrid declarative scene serialization, Major Feature, tier2) from the finding that `automation.screenshot()` PNG encode is ~96% of a render-profile's instructions.
- **Tier1 memory-model batch resolved** -- all five pre-1.0 freeze decisions from the 2026-07-02 review are closed on master. **#326**: Color/Vector are value types forever (copies at every property boundary; write-through proxies rejected); **#328**: `with layer.edit() as view:` is the single bulk-edit convention for future buffer/numpy APIs (conservative invalidation on `__exit__`; unblocks #335); **#327**: threading contract documented -- `docs/threading-model.md` + normative `mcrfpy.lock()` docstring ("off-main-thread access outside the lock is undefined"); **#330**: subinterpreters explicitly excluded from 1.x compatibility; **#329**: `grid.entities` re-backed by `std::vector` -- indexing is O(1) (5000-entity indexed sweep: 49.7 ms -> 0.5 ms), iterator is index-based with the same mutation-guard semantics, suite 304/304. #326/#328/#330 are recorded in the new `docs/api-stability.md` compatibility policy, enforced by the api-surface snapshot test.
- **#340 The Gauntlet** -- interactive on-screen stress benchmark (`tests/benchmarks/gauntlet/`). Six trials (ENTITY SWARM, ANIMATION STORM, GRID TITAN, PATHFINDER RUSH, UI AVALANCHE, SIGHTLINE SIEGE) ramp load geometrically until p95 frame time breaks the 16.67 ms budget; live HUD with frame-time sparkline; menu + results screens diff against a committed JSON baseline with letter grades and a geometric-mean GAUNTLET SCORE. First real baseline captured windowed on 0.2.8 (e.g. 10,555 entities, 450 pathfinding queries/tick, 4,123 UI elements at budget). Caveat: desktop noise made callback-heavy trials vary run-to-run -- recapture on a quiet system for a canonical baseline. Found #341 (get_metrics draw_calls/render counters read 0).
### Recently Shipped (June 2026)
- **0.2.8 release** -- Version bumped to `0.2.8` (dropped the in-development `-7DRL-2026` suffix) and cut for distribution: `dist/McRogueFace-0.2.8-{Linux-full.tar.gz,Windows-full.zip,WASM.zip}`. Shipped libtcod (Linux `.so` / Windows `.dll` / Emscripten `.a`) rebuilt from submodule 79abc66 so release binaries carry the #321 FOV fix; the Linux build is `SDL3=OFF` with vendored utf8proc to stay self-contained (only libz + system libs), matching prior releases. Also fixed a pre-existing `tools/package.sh` bug that affected 0.2.7 too: the Linux package shipped the bare `libtcod.so` instead of its DT_NEEDED SONAME `libtcod.so.2` (+ compat symlink, mirroring the SFML packaging), so the runtime loader couldn't resolve it on a clean machine -- an absolute dev RUNPATH had masked this locally. No Gitea issue tracks this release (declined). Local commit `cf844f4`, pushed to origin/master; the `0.2.8` tag exists locally but has not been pushed yet.
- **#321** (HIGH) -- Fixed the `ColorLayer.draw_fov` heap-buffer-overflow by bumping `libtcod-headless` to 79abc66, pulling in upstream FOV overflow fixes (root cause: off-by-one in `view_array_insert` overflowing `active_views` in `fov_permissive2.c`; the "bad-free in ~GridData" of the issue title is the downstream symptom). Verified by A/B replay of the #312 `fuzz_fov` crash corpus under clang-18 ASan -- pre-fix (8835239) inputs abort with the overflow inside `GridData::computeFOV`, post-fix (79abc66) all clean plus a 45s/21,952-run fuzz smoke. (The gitignored shipped `__lib/libtcod.so` needed rebuilding from this commit for release binaries to carry the fix -- done for 0.2.8, see above.)
- **#322-#325** (fuzz-surfaced safety batch) -- the four remaining #312 fuzz bugs fixed and regression-tested. **#322**: `WangSet.terrain_enum` (and the parallel `AutoRuleSet.terrain_enum`) ignored Python C-API return codes, so an invalid-UTF-8 import name left an exception pending and the next `PyObject_Call` tripped a `_PyErr_Occurred` assertion/abort; every C-API return is now checked and the real exception propagated. **#323/#324/#325**: NaN/inf (or out-of-long-range) floats reached unchecked float->integer casts -- `pitch_shift` factor (`AudioEffects.cpp:27`, nan->unsigned long), `hsl_shift` shifts (`PyTexture.cpp:458`, nan->unsigned char), and `Vector.int` components (`PyVector.cpp:610`, inf->long), all undefined behavior; each now validates finiteness/range at the binding boundary and raises ValueError/OverflowError. Verified by A/B replay of the #312 crash corpus under clang-18 UBSan/ASan: all four inputs abort pre-fix at the exact cited lines and run clean post-fix, with four new regression tests (`tests/regression/issue_322..325_*`).
- **#312** -- Fuzz coverage extended to the remaining public API surface. Four new libFuzzer targets (`fuzz_audio_dsp`, `fuzz_import_parsers`, `fuzz_texture_factory`, `fuzz_shader_bindings`) cover the Tier A/B gaps (external file parsers, audio DSP math, raw-byte texture ingestion, shader uniform-binding lifetime); Tier C surface (Line/Circle/Arc, `Scene.children` collections, `find`/`find_all`/`bresenham`/`lock`, grid spatial queries, GridPoint dynamic attrs, `Grid.find_path`+AStarPath, ColorLayer perspective/`draw_fov`, layer `apply_*`) folded into the five existing targets. Each new target is signature-validated against the live API and seeded from real fixtures. The campaign immediately found **five new bugs** -- filed #321-#325 (no fixes this round; targets only). A pre-existing infra fix rode along: `tools/build_debug_libs.sh` flag-quoting bug that broke instrumented debug-lib rebuilds. The benchmark triplet is deliberately excluded from fuzzing (`end_benchmark()` writes a file per call).
- **#320** -- `Caption` constructor positional signature now matches its frozen docstring. The docstring advertised `Caption(pos, font, text, ...)` (parallel to `Sprite`/`Entity`, whose 2nd positional is the resource), but the implementation laid its two positional slots out as `(pos, text)` with `font` keyword-only, so `Caption((x,y), None, "text")` raised `TypeError`. Fixed `UICaption::init` to `(pos, font, text)` positional-or-keyword. Audited zero live callers of the old `(pos, text)` 2-positional form. Also added the matching read-only `Caption.font` getter (the class docstring listed `font` as an attribute but no getter existed; it now reflects the supplied or engine-default font). Also rewrote two stale unit tests (`test_animation_raii`, `test_animation_property_locking`) that called the removed `mcrfpy.Animation(...)` constructor to use `drawable.animate(...)` -- preserving the suite's only `conflict_mode` (#120) coverage and the weak-target RAII checks. Suite now 297/297.
- **#317 / #318 / #319** -- The three code-level bugs surfaced by the #314 docstring-accuracy verify pass, fixed together. #317: `automation.scroll()` dropped the x of its position argument (the scroll delta now has its own `injectMouseEvent` parameter, so the real x/y is forwarded). #318: `GridView.texture` always returned `None` (a TODO stub) -- it now returns a `Texture` wrapper (and since `mcrfpy.Grid`/`mcrfpy.GridView` are one type post-#252, both names benefit). #319: `Entity.visible_entities(radius=None)` raised `TypeError` (the `i` format code rejects `None`) -- radius is now parsed as an object so `None`/omitted/`-1` mean "grid default". Regression tests for each; api-surface snapshot re-baselined and docs/stubs regenerated.
- **#316** -- Sparse (windowed) perspective writeback in `UIEntity::updateVisibility`. The demote+promote passes are now clipped to an AABB sized to `fov_radius` (with a `prev_fov` window cache so a moving entity leaves no trailing "ghost vision"), replacing two full-`W*H` walks per entity. The Phase 5.2 benchmark's flat ~25-36 ms/entity writeback overhead on a 1000x1000 grid collapses to single-digit microseconds (384x-6577x on the cheap algorithms; lost in timing noise on the rest). Adversarial verify caught a regression the happy-path test missed -- externally-assigned maps (the documented `from_bytes` load/resume path) need a one-shot full demote (`perspective_full_demote_pending`) since `prev_fov` only bounds engine-promoted cells; fixed and locked with a 7-section regression test.
- **#313** -- `UIEntity::grid` migrated from `shared_ptr<UIGrid>` to `shared_ptr<GridData>` (post-#252 refactor cleanup), adding a new public `entity.texture` read/write property. Merged to master.
- **#314** -- API audit follow-through complete. (1) Snapshot lock: a public API-surface regression test (`tests/unit/api_surface_snapshot_test.py`) enshrines the frozen contract. (2) **F15**: all 289 raw docstring slots across the 20 frozen binding files converted to `MCRF_*` macros (frozen surface 100% compliant), driven by two one-agent-per-file workflows with build/doc gates and an adversarial signature-accuracy verify pass. Property types now resolve to real types (not `Any`) and read-only flags are correct. (3) A strict frozen-docstring gate (`tools/check_frozen_docstrings.sh`, wired into `generate_all_docs.sh`) locks it against regression. Breaking-change findings (F1/F4/F6/F11/F13) closed earlier; F7/F8/F10 deferred as non-1.0. Code-level bugs surfaced by the verify pass filed as #317/#318/#319.
### Active Follow-Ups
- The memory-model review (#326-#338) is nearly closed out: the five tier1 freeze decisions (#326-#330), #331 (hot-getter fast path), #332 (GridData SoA), #333 (lazy TCOD map rebuild), #334 (DiscreteMap buffer protocol), and #335 (numpy layer views) are all **resolved** on master. Still open: **#338** (per-instance memory diet -- shipped as a safe subset, the `render_sprite` relocation deferred), **#336** (free-threading hardening), **#337** (numpy availability strategy), and **#352** (perspective-grid render early-out).
- The grid type-split bugs and the profiling/Gauntlet testability gaps are now **closed**: #369 (`.parent` identity churn), #372 (rotted `tests/demo/`, superseded by #374), #356 (doc/stub generators miss module-level dynamic attributes), #341 (get_metrics render counters read 0), and #350 (headless `step()` bypasses `updatePythonScenes()`).
- #349 (declarative scene serialization) is an open tier2 design proposal.
- Gauntlet baseline should be recaptured on a quiet system (`tests/benchmarks/gauntlet/run_gauntlet.py`) -- the committed first baseline is real but desktop-noisy for the callback-heavy trials. (#341, render counters read 0 in get_metrics, which blocked richer per-subsystem HUD attribution, is now closed.)
- 0.2.8 is released and published: commit `cf844f4` and the `0.2.8` tag are both pushed, and the distribution artifacts are uploaded. Master has since advanced past `36e74e0` (grid input revival + type split) through the July perf/grid sprints and the docs-as-tests thread; the `closes #...` commit trailers take their issues down on merge.
### Other Post-7DRL Priorities
- Progress on the r/roguelikedev tutorial series (#167)
- Better pip/virtualenv integration for adding packages to McRogueFace's embedded interpreter
**Current State**: Documentation system complete, TCOD integration urgent
**Latest Update**: Completed Phase 7 documentation infrastructure (2025-07-08)
**Branch**: alpha_streamline_2
**Open Issues**: ~46 remaining + URGENT TCOD/Tutorial work
---
## Engine Eras
## 📋 TCOD Integration Implementation Details
One engine, accumulating capabilities. Nothing is thrown away.
### Phase 1: Core UIGrid Integration (Day 1 Morning)
```cpp
// UIGrid.h additions
class UIGrid : public UIDrawable {
private:
TCODMap* world_state; // Add TCOD map
std::unordered_map<int, UIGridPointState*> entity_perspectives;
bool batch_mode = false;
std::vector<CellUpdate> pending_updates;
```
| Era | Focus | Status |
|-----|-------|--------|
| **McRogueFace** | 2D tiles, roguelike systems, procgen | Active -- approaching 1.0 |
| **McVectorFace** | Sparse grids, vector graphics, physics | Planned |
| **McVoxelFace** | Voxel terrain, 3D gameplay | Proof-of-concept complete |
### Phase 2: Python Bindings (Day 1 Afternoon)
```python
# New API surface
grid = mcrfpy.Grid(100, 100)
grid.compute_fov(player.x, player.y, radius=10) # Returns visible cells
grid.at((x, y)).walkable = False # Ergonomic access
with grid.batch_update(): # Context manager for performance
# All updates batched
```
### Phase 3: Entity Integration (Day 2 Morning)
```python
# UIEntity additions
entity.path_to(target_x, target_y) # A* pathfinding
entity.flee_from(threat) # Dijkstra map
entity.can_see(other_entity) # FOV check
```
### Critical Success Factors:
1. **Batch everything** - Never update single cells in loops
2. **Lazy evaluation** - Only compute FOV for entities that need it
3. **Sparse storage** - Don't store full grids per entity
4. **Profile early** - Find the 20% of code taking 80% of time
---
## 3D/Voxel Pipeline (Experimental)
## Recent Achievements
The 3D pipeline is proof-of-concept scouting for the McVoxelFace era. It works and is tested but is explicitly **not** part of the 1.0 API freeze.
### 2025-07-10: Complete FOV, A* Pathfinding & GUI Text Widgets! 👁️🗺️⌨️
**Engine Feature Sprint - Major Capabilities Added**
- ✅ Complete FOV (Field of View) system with perspective rendering
- UIGrid.perspective property controls which entity's view to render
- Three-layer overlay system: unexplored (black), explored (dark), visible (normal)
- Per-entity visibility state tracking with UIGridPointState
- Perfect knowledge updates - only explored areas persist
- ✅ A* Pathfinding implementation
- Entity.path_to(x, y) method for direct pathfinding
- UIGrid compute_astar() and get_astar_path() methods
- Path caching in entities for performance
- Complete test suite comparing A* vs Dijkstra performance
- ✅ GUI Text Input Widget System
- Full-featured TextInputWidget class with cursor, selection, scrolling
- Improved widget with proper text rendering and multi-line support
- Example showcase demonstrating multiple input fields
- Foundation for in-game consoles, chat systems, and text entry
- ✅ Sizzle Reel Demos
- path_vision_sizzle_reel.py combines pathfinding with FOV
- Interactive visibility demos showing real-time FOV updates
- Performance demonstrations with multiple entities
**What exists**: Viewport3D, Camera3D, Entity3D, MeshLayer, Model3D (glTF), Billboard, Shader3D, VoxelGrid with greedy meshing, face culling, RLE serialization, and navigation projection.
### 2025-07-09: Dijkstra Pathfinding & Critical Bug Fix! 🗺️
**TCOD Integration Sprint - Major Progress**
- ✅ Complete Dijkstra pathfinding implementation in UIGrid
- compute_dijkstra(), get_dijkstra_distance(), get_dijkstra_path() methods
- Full TCODMap and TCODDijkstra integration with proper memory management
- Comprehensive test suite with both headless and interactive demos
- ✅ **CRITICAL FIX**: PyArg bug in UIGridPoint color setter
- Now supports both mcrfpy.Color objects and (r,g,b,a) tuples
- Eliminated mysterious "SystemError: new style getargs format" crashes
- Proper error handling and exception propagation
- ✅ mcrfpy.libtcod submodule with Python bindings
- dijkstra_compute(), dijkstra_get_distance(), dijkstra_get_path()
- line() function for corridor generation
- Foundation ready for FOV implementation
- ✅ Test consolidation: 6 broken demos → 2 clean, working versions
**Known gaps**: Some Entity3D collection methods, animation stubs, shader pipeline incomplete.
### 2025-07-08: PyArgHelpers Infrastructure Complete! 🔧
**Standardized Python API Argument Parsing**
- Unified position handling: (x, y) tuples or separate x, y args
- Consistent size parsing: (w, h) tuples or width, height args
- Grid-specific helpers for tile-based positioning
- Proper conflict detection between positional and keyword args
- All UI components migrated: Frame, Caption, Sprite, Grid, Entity
- Improved error messages: "Value must be a number (int or float)"
- Foundation for Phase 7 documentation efforts
**Maturity track**: These modules will mature on their own timeline, driven by games that need 3D. They won't block 2D stability.
### 2025-07-05: ALPHA 0.1 ACHIEVED! 🎊🍾
**All Alpha Blockers Resolved!**
- Z-order rendering with performance optimization (Issue #63)
- Python Sequence Protocol for collections (Issue #69)
- Comprehensive Animation System (Issue #59)
- Moved RenderTexture to Beta (not needed for Alpha)
- **McRogueFace is ready for Alpha release!**
### 2025-07-05: Z-order Rendering Complete! 🎉
**Issue #63 Resolved**: Consistent z-order rendering with performance optimization
- Dirty flag pattern prevents unnecessary per-frame sorting
- Lazy sorting for both Scene elements and Frame children
- Frame children now respect z_index (fixed inconsistency)
- Automatic dirty marking on z_index changes and collection modifications
- Performance: O(1) check for static scenes vs O(n log n) every frame
### 2025-07-05: Python Sequence Protocol Complete! 🎉
**Issue #69 Resolved**: Full sequence protocol implementation for collections
- Complete __setitem__, __delitem__, __contains__ support
- Slice operations with extended slice support (step != 1)
- Concatenation (+) and in-place concatenation (+=) with validation
- Negative indexing throughout, index() and count() methods
- Type safety: UICollection (Frame/Caption/Sprite/Grid), EntityCollection (Entity only)
- Default value support: None for texture/font parameters uses engine defaults
### 2025-07-05: Animation System Complete! 🎉
**Issue #59 Resolved**: Comprehensive animation system with 30+ easing functions
- Property-based animations for all UI classes (Frame, Caption, Sprite, Grid, Entity)
- Individual color component animation (r/g/b/a)
- Sprite sequence animation and text typewriter effects
- Pure C++ execution without Python callbacks
- Delta animation support for relative values
### 2025-01-03: Major Stability Update
**Major Cleanup**: Removed deprecated registerPyAction system (-180 lines)
**Bug Fixes**: 12 critical issues including Grid segfault, Issue #78 (middle click), Entity setters
**New Features**: Entity.index() (#73), EntityCollection.extend() (#27), Sprite validation (#33)
**Test Coverage**: Comprehensive test suite with timer callback pattern established
---
## Future Directions
## 🔧 CURRENT WORK: Alpha Streamline 2 - Major Architecture Improvements
These are ideas on the horizon -- not yet concrete enough for issues, but worth capturing.
### Recent Completions:
- ✅ **Phase 1-4 Complete** - Foundation, API Polish, Entity Lifecycle, Visibility/Performance
- ✅ **Phase 5 Complete** - Window/Scene Architecture fully implemented!
- Window singleton with properties (#34)
- OOP Scene support with lifecycle methods (#61)
- Window resize events (#1)
- Scene transitions with animations (#105)
- ✅ **Phase 6 Complete** - Rendering Revolution achieved!
- Grid background colors (#50) ✅
- RenderTexture overhaul (#6) ✅
- UIFrame clipping support ✅
- Viewport-based rendering (#8) ✅
### McRogueFace Lite
A spiritual port to MicroPython targeting the PicoCalc and other microcontrollers. Could provide a migration path to retro ROMs or compete in the Pico-8 space. The core idea: strip McRogueFace down to its essential tile/entity/scene model and run it on constrained hardware.
### Active Development:
- **Branch**: alpha_streamline_2
- **Current Phase**: Phase 7 - Documentation & Distribution
- **Achievement**: PyArgHelpers infrastructure complete - standardized Python API
- **Strategic Vision**: See STRATEGIC_VISION.md for platform roadmap
- **Latest**: All UI components now use consistent argument parsing patterns!
### McVectorFace Era
The next major capability expansion. Sparse grid layers, a polygon/shape rendering class, and eventually physics integration. This would support games that aren't purely tile-based -- top-down action, strategy maps with irregular regions, or hybrid tile+vector visuals. See the [Strategic Direction](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Strategic-Direction) wiki for the full era model.
### 🏗️ Architectural Dependencies Map
### McRogueFace Standard Library
A built-in collection of reusable GUI widgets and game UI patterns: menus, dialogs, inventory screens, stat bars, text input fields, scrollable lists. These would ship with the engine as importable Python modules, saving every game from reimplementing the same UI primitives. Think of it as `mcrfpy.widgets` -- batteries included.
```
Foundation Layer:
├── #71 Base Class (_Drawable)
│ ├── #10 Visibility System (needs AABB from base)
│ ├── #87 visible property
│ └── #88 opacity property
├── #7 Safe Constructors (affects all classes)
│ └── Blocks any new class creation until resolved
└── #30 Entity/Grid Integration (lifecycle management)
└── Enables reliable entity management
### Pip/Virtualenv Integration
Rather than inverting the architecture to make McRogueFace a pip-installable package, the nearer-term goal is better integration in the other direction: making it easy to install and use third-party Python packages within McRogueFace's embedded interpreter. This could mean virtualenv awareness, a `mcrf install` command, or bundling pip itself.
Window/Scene Layer:
├── #34 Window Object
│ ├── #61 Scene Object (depends on Window)
│ ├── #14 SFML Exposure (helps implement Window)
│ └── Future: Multi-window support
Rendering Layer:
└── #6 RenderTexture Overhaul
├── Enables clipping
├── Off-screen rendering
└── Post-processing effects
```
## 🚀 Alpha Streamline 2 - Comprehensive Phase Plan
### Phase 1: Foundation Stabilization (1-2 weeks)
**Goal**: Safe, predictable base for all future work
```
1. #7 - Audit and fix unsafe constructors (CRITICAL - do first!)
- Find all manually implemented no-arg constructors
- Verify map compatibility requirements
- Make pointer-safe or remove
2. #71 - _Drawable base class implementation
- Common properties: x, y, w, h, visible, opacity
- Virtual methods: get_bounds(), render()
- Proper Python inheritance setup
3. #87 - visible property
- Add to base class
- Update all render methods to check
4. #88 - opacity property (depends on #87)
- 0.0-1.0 float range
- Apply in render methods
5. #89 - get_bounds() method
- Virtual method returning (x, y, w, h)
- Override in each UI class
6. #98 - move()/resize() convenience methods
- move(dx, dy) - relative movement
- resize(w, h) - absolute sizing
```
*Rationale*: Can't build on unsafe foundations. Base class enables all UI improvements.
### Phase 2: Constructor & API Polish (1 week)
**Goal**: Pythonic, intuitive API
```
1. #101 - Standardize (0,0) defaults for all positions
2. #38 - Frame children parameter: Frame(children=[...])
3. #42 - Click handler in __init__: Button(click=callback)
4. #90 - Grid size tuple: Grid(grid_size=(10, 10))
5. #19 - Sprite texture swapping: sprite.texture = new_texture
6. #52 - Grid skip out-of-bounds entities (performance)
```
*Rationale*: Quick wins that make the API more pleasant before bigger changes.
### Phase 3: Entity Lifecycle Management (1 week)
**Goal**: Bulletproof entity/grid relationships
```
1. #30 - Entity.die() and grid association
- Grid.entities.append(e) sets e.grid = self
- Grid.entities.remove(e) sets e.grid = None
- Entity.die() calls self.grid.remove(self)
- Entity can only be in 0 or 1 grid
2. #93 - Vector arithmetic methods
- add, subtract, multiply, divide
- distance, normalize, dot product
3. #94 - Color helper methods
- from_hex("#FF0000"), to_hex()
- lerp(other_color, t) for interpolation
4. #103 - Timer objects
timer = mcrfpy.Timer("my_timer", callback, 1000)
timer.pause()
timer.resume()
timer.cancel()
```
*Rationale*: Games need reliable entity management. Timer objects enable entity AI.
### Phase 4: Visibility & Performance (1-2 weeks)
**Goal**: Only render/process what's needed
```
1. #10 - [UNSCHEDULED] Full visibility system with AABB
- Postponed: UIDrawables can exist in multiple collections
- Cannot reliably determine screen position due to multiple render contexts
- Needs architectural solution for parent-child relationships
2. #52 - Grid culling (COMPLETED in Phase 2)
3. #39/40/41 - Name system for finding elements
- name="button1" property on all UIDrawables
- only_one=True for unique names
- scene.find("button1") returns element
- collection.find("enemy*") returns list
4. #104 - Basic profiling/metrics
- Frame time tracking
- Draw call counting
- Python vs C++ time split
```
*Rationale*: Performance is feature. Finding elements by name is huge QoL.
### Phase 5: Window/Scene Architecture ✅ COMPLETE! (2025-07-06)
**Goal**: Modern, flexible architecture
```
1. ✅ #34 - Window object (singleton first)
window = mcrfpy.Window.get()
window.resolution = (1920, 1080)
window.fullscreen = True
window.vsync = True
2. ✅ #1 - Window resize events
scene.on_resize(self, width, height) callback implemented
3. ✅ #61 - Scene object (OOP scenes)
class MenuScene(mcrfpy.Scene):
def on_keypress(self, key, state):
# handle input
def on_enter(self):
# setup UI
def on_exit(self):
# cleanup
def update(self, dt):
# frame update
4. ✅ #14 - SFML exposure research
- Completed comprehensive analysis
- Recommendation: Direct integration as mcrfpy.sfml
- SFML 3.0 migration deferred to late 2025
5. ✅ #105 - Scene transitions
mcrfpy.setScene("menu", "fade", 1.0)
# Supports: fade, slide_left, slide_right, slide_up, slide_down
```
*Result*: Entire window/scene system modernized with OOP design!
### Phase 6: Rendering Revolution (3-4 weeks) ✅ COMPLETE!
**Goal**: Professional rendering capabilities
```
1. ✅ #50 - Grid background colors [COMPLETED]
grid.background_color = mcrfpy.Color(50, 50, 50)
- Added background_color property with animation support
- Default dark gray background (8, 8, 8, 255)
2. ✅ #6 - RenderTexture overhaul [COMPLETED]
✅ Base infrastructure in UIDrawable
✅ UIFrame clip_children property
✅ Dirty flag optimization system
✅ Nested clipping support
✅ UIGrid already has appropriate RenderTexture implementation
❌ UICaption/UISprite clipping not needed (no children)
3. ✅ #8 - Viewport-based rendering [COMPLETED]
- Fixed game resolution (window.game_resolution)
- Three scaling modes: "center", "stretch", "fit"
- Window to game coordinate transformation
- Mouse input properly scaled with windowToGameCoords()
- Python API fully integrated
- Tests: test_viewport_simple.py, test_viewport_visual.py, test_viewport_scaling.py
4. #106 - Shader support [DEFERRED TO POST-PHASE 7]
sprite.shader = mcrfpy.Shader.load("glow.frag")
frame.shader_params = {"intensity": 0.5}
5. #107 - Particle system [DEFERRED TO POST-PHASE 7]
emitter = mcrfpy.ParticleEmitter()
emitter.texture = spark_texture
emitter.emission_rate = 100
emitter.lifetime = (0.5, 2.0)
```
**Phase 6 Achievement Summary**:
- Grid backgrounds (#50) ✅ - Customizable background colors with animation
- RenderTexture overhaul (#6) ✅ - UIFrame clipping with opt-in architecture
- Viewport rendering (#8) ✅ - Three scaling modes with coordinate transformation
- UIGrid already had optimal RenderTexture implementation for its use case
- UICaption/UISprite clipping unnecessary (no children to clip)
- Performance optimized with dirty flag system
- Backward compatibility preserved throughout
- Effects/Shader/Particle systems deferred for focused delivery
*Rationale*: This unlocks professional visual effects but is complex.
### Phase 7: Documentation & Distribution (1-2 weeks)
**Goal**: Ready for the world
```
1. ✅ #85 - Replace all "docstring" placeholders [COMPLETED 2025-07-08]
2. ✅ #86 - Add parameter documentation [COMPLETED 2025-07-08]
3. ✅ #108 - Generate .pyi type stubs for IDE support [COMPLETED 2025-07-08]
4. ❌ #70 - PyPI wheel preparation [CANCELLED - Architectural mismatch]
5. API reference generator tool
```
## 📋 Critical Path & Parallel Tracks
### 🔴 **Critical Path** (Must do in order)
**Safe Constructors (#7)** → **Base Class (#71)****Visibility (#10)****Window (#34)** → **Scene (#61)**
### 🟡 **Parallel Tracks** (Can be done alongside critical path)
**Track A: Entity Systems**
- Entity/Grid integration (#30)
- Timer objects (#103)
- Vector/Color helpers (#93, #94)
**Track B: API Polish**
- Constructor improvements (#101, #38, #42, #90)
- Sprite texture swap (#19)
- Name/search system (#39/40/41)
**Track C: Performance**
- Grid culling (#52)
- Visibility culling (part of #10)
- Profiling tools (#104)
### 💎 **Quick Wins to Sprinkle Throughout**
1. Color helpers (#94) - 1 hour
2. Vector methods (#93) - 1 hour
3. Grid backgrounds (#50) - 30 minutes
4. Default positions (#101) - 30 minutes
### 🎯 **Recommended Execution Order**
**Week 1-2**: Foundation (Critical constructors + base class)
**Week 3**: Entity lifecycle + API polish
**Week 4**: Visibility system + performance
**Week 5-6**: Window/Scene architecture
**Week 7-9**: Rendering revolution (or defer to gamma)
**Week 10**: Documentation + release prep
### 🆕 **New Issues to Create/Track**
1. [x] **Timer Objects** - Pythonic timer management (#103) - *Completed Phase 3*
2. [ ] **Event System Enhancement** - Mouse enter/leave, drag, right-click
3. [ ] **Resource Manager** - Centralized asset loading
4. [ ] **Serialization System** - Save/load game state
5. [x] **Scene Transitions** - Fade, slide, custom effects (#105) - *Completed Phase 5*
6. [x] **Profiling Tools** - Performance metrics (#104) - *Completed Phase 4*
7. [ ] **Particle System** - Visual effects framework (#107)
8. [ ] **Shader Support** - Custom rendering effects (#106)
---
## Open Issues by Area
## 📋 Phase 6 Implementation Strategy
26 open issues across the tracker. Key groupings:
### RenderTexture Overhaul (#6) - Technical Approach
- **Docs-as-tests fallout** (#380, #374) -- shipped `templates/*/` example programs are gated by nothing; 19 orphan scripts under `tests/demo/screens/` need adoption or retirement. Both spun out of the July snippets-as-tests thread (the docs site's code samples are now in the gated suite). The earlier bug batch (#369 `.parent` identity, #372 rotted `tests/demo/`, #356 doc generators miss module attrs, #341 get_metrics counters, #350 headless `step()` bypass) is **closed**.
- **Memory-model review, July 2026** (#336, #337, #338 remaining; #326-#335 resolved) -- per-instance memory diet, free-threading hardening, numpy availability strategy
- **Grid / rendering** (#352, #152, #67, #124, #107, #347) -- perspective-grid render early-out; sparse layers; infinite worlds; grid point animation; particle system; SFML vs SDL2/WebGL renderer parity
- **Design proposals** (#349) -- hybrid declarative scene serialization (test oracle + save/load)
- **Demos / tutorials** (#167, #248, #154, #156, #55) -- r/roguelikedev series, Crypt of Sokoban remaster, LLM agent simulations
- **Platform/distribution** (#70, #54, #62, #53) -- Packaging without the embedded interpreter, Jupyter, multiple windows, input methods
- **WASM tooling** (#239, #346) -- Automated browser testing; web-build profiling docs (sibling to #345)
- **Deferred** (#220, #46, #45) -- Subinterpreter support / tests, accessibility modes
**Current State**:
- UIGrid already uses RenderTexture for entity rendering
- Scene transitions use RenderTextures for smooth animations
- Direct rendering to window for Frame, Caption, Sprite
See the [Forgejo issue tracker](https://dev.ffwf.net/forgejo/john/McRogueFace/issues) for current status.
**Implementation Plan**:
1. **Base Infrastructure**:
- Add `sf::RenderTexture* target` to UIDrawable base
- Modify `render()` to check if target exists
- If target: render to texture, then draw texture to parent
- If no target: render directly (backward compatible)
2. **Clipping Support**:
- Frame enforces bounds on children via RenderTexture
- Children outside bounds are automatically clipped
- Nested frames create render texture hierarchy
3. **Performance Optimization**:
- Lazy RenderTexture creation (only when needed)
- Dirty flag system (only re-render when changed)
- Texture pooling for commonly used sizes
4. **Integration Points**:
- Scene transitions already working with RenderTextures
- UIGrid can be reference implementation
- Test with deeply nested UI structures
**Quick Wins Before Core Work**:
1. **Grid Background (#50)** - 30 min implementation
- Add `background_color` and `background_texture` properties
- Render before entities in UIGrid::render()
- Good warm-up before tackling RenderTexture
2. **Research Tasks**:
- Study UIGrid's current RenderTexture usage
- Profile scene transition performance
- Identify potential texture size limits
---
## Resources
## 🚀 NEXT PHASE: Beta Features & Polish
- **Issue Tracker**: [Forgejo Issues](https://dev.ffwf.net/forgejo/john/McRogueFace/issues)
- **Wiki**: [Strategic Direction](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Strategic-Direction), [Issue Roadmap](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Issue-Roadmap), [Development Workflow](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Development-Workflow)
- **Build Guide**: See `CLAUDE.md` for build instructions
- **Tutorial**: `roguelike_tutorial/` for implementation examples
### Alpha Complete! Moving to Beta Priorities:
1. ~~**#69** - Python Sequence Protocol for collections~~ - *Completed! (2025-07-05)*
2. ~~**#63** - Z-order rendering for UIDrawables~~ - *Completed! (2025-07-05)*
3. ~~**#59** - Animation system~~ - *Completed! (2025-07-05)*
4. **#6** - RenderTexture concept - *Extensive Overhaul*
5. ~~**#47** - New README.md for Alpha release~~ - *Completed*
- [x] **#78** - Middle Mouse Click sends "C" keyboard event - *Fixed*
- [x] **#77** - Fix error message copy/paste bug - *Fixed*
- [x] **#74** - Add missing `Grid.grid_y` property - *Fixed*
- [ ] **#37** - Fix Windows build module import from "scripts" directory - *Isolated Fix*
Issue #37 is **on hold** until we have a Windows build environment available. I actually suspect this is already fixed by the updates to the makefile, anyway.
- [x] **Entity Property Setters** - Fix "new style getargs format" error - *Fixed*
- [x] **Sprite Texture Setter** - Fix "error return without exception set" - *Fixed*
- [x] **keypressScene() Validation** - Add proper error handling - *Fixed*
### 🔄 Complete Iterator System
**Status**: Core iterators complete (#72 closed), Grid point iterators still pending
- [ ] **Grid Point Iterator Implementation** - Complete the remaining grid iteration work
- [x] **#73** - Add `entity.index()` method for collection removal - *Fixed*
- [x] **#69** ⚠️ **Alpha Blocker** - Refactor all collections to use Python Sequence Protocol - *Completed! (2025-07-05)*
**Dependencies**: Grid point iterators → #73 entity.index() → #69 Sequence Protocol overhaul
---
## 🗂 ISSUE TRIAGE BY SYSTEM (78 Total Issues)
### 🎮 Core Engine Systems
#### Iterator/Collection System (2 issues)
- [x] **#73** - Entity index() method for removal - *Fixed*
- [x] **#69** ⚠️ **Alpha Blocker** - Sequence Protocol refactor - *Completed! (2025-07-05)*
#### Python/C++ Integration (7 issues)
- [x] **#76** - UIEntity derived type preservation in collections - *Multiple Integrations*
- [ ] **#71** - Drawable base class hierarchy - *Extensive Overhaul*
- [ ] **#70** - PyPI wheel distribution - *Extensive Overhaul*
- [~] **#32** - Executable behave like `python` command - *Extensive Overhaul* *(90% Complete: -h, -V, -c, -m, -i, script execution, sys.argv, --exec all implemented. Only stdin (-) support missing)*
- [ ] **#35** - TCOD as built-in module - *Extensive Overhaul*
- [~] **#14** - Expose SFML as built-in module - *Research Complete, Implementation Pending*
- [ ] **#46** - Subinterpreter threading tests - *Multiple Integrations*
#### UI/Rendering System (12 issues)
- [x] **#63** ⚠️ **Alpha Blocker** - Z-order for UIDrawables - *Multiple Integrations*
- [x] **#59** ⚠️ **Alpha Blocker** - Animation system - *Completed! (2025-07-05)*
- [ ] **#6** ⚠️ **Alpha Blocker** - RenderTexture for all UIDrawables - *Extensive Overhaul*
- [ ] **#10** - UIDrawable visibility/AABB system - *Extensive Overhaul*
- [ ] **#8** - UIGrid RenderTexture viewport sizing - *Multiple Integrations*
- [x] **#9** - UIGrid RenderTexture resize handling - *Multiple Integrations*
- [ ] **#52** - UIGrid skip out-of-bounds entities - *Isolated Fix*
- [ ] **#50** - UIGrid background color field - *Isolated Fix*
- [ ] **#19** - Sprite get/set texture methods - *Multiple Integrations*
- [ ] **#17** - Move UISprite position into sf::Sprite - *Isolated Fix*
- [x] **#33** - Sprite index validation against texture range - *Fixed*
#### Grid/Entity System (6 issues)
- [ ] **#30** - Entity/Grid association management (.die() method) - *Extensive Overhaul*
- [ ] **#16** - Grid strict mode for entity knowledge/visibility - *Extensive Overhaul*
- [ ] **#67** - Grid stitching for infinite worlds - *Extensive Overhaul*
- [ ] **#15** - UIGridPointState cleanup and standardization - *Multiple Integrations*
- [ ] **#20** - UIGrid get_grid_size standardization - *Multiple Integrations*
- [x] **#12** - GridPoint/GridPointState forbid direct init - *Isolated Fix*
#### Scene/Window Management (5 issues)
- [x] **#61** - Scene object encapsulating key callbacks - *Completed Phase 5*
- [x] **#34** - Window object for resolution/scaling - *Completed Phase 5*
- [ ] **#62** - Multiple windows support - *Extensive Overhaul*
- [ ] **#49** - Window resolution & viewport controls - *Multiple Integrations*
- [x] **#1** - Scene resize event handling - *Completed Phase 5*
### 🔧 Quality of Life Features
#### UI Enhancement Features (8 issues)
- [ ] **#39** - Name field on UIDrawables - *Multiple Integrations*
- [ ] **#40** - `only_one` arg for unique naming - *Multiple Integrations*
- [ ] **#41** - `.find(name)` method for collections - *Multiple Integrations*
- [ ] **#38** - `children` arg for Frame initialization - *Isolated Fix*
- [ ] **#42** - Click callback arg for UIDrawable init - *Isolated Fix*
- [x] **#27** - UIEntityCollection.extend() method - *Fixed*
- [ ] **#28** - UICollectionIter for scene ui iteration - *Isolated Fix*
- [ ] **#26** - UIEntityCollectionIter implementation - *Isolated Fix*
### 🧹 Refactoring & Cleanup
#### Code Cleanup (7 issues)
- [x] **#3** ⚠️ **Alpha Blocker** - Remove `McRFPy_API::player_input` - *Completed*
- [x] **#2** ⚠️ **Alpha Blocker** - Review `registerPyAction` necessity - *Completed*
- [ ] **#7** - Remove unsafe no-argument constructors - *Multiple Integrations*
- [ ] **#21** - PyUIGrid dealloc cleanup - *Isolated Fix*
- [ ] **#75** - REPL thread separation from SFML window - *Multiple Integrations*
### 📚 Demo & Documentation
#### Documentation (2 issues)
- [x] **#47** ⚠️ **Alpha Blocker** - Alpha release README.md - *Isolated Fix*
- [ ] **#48** - Dependency compilation documentation - *Isolated Fix*
#### Demo Projects (6 issues)
- [ ] **#54** - Jupyter notebook integration demo - *Multiple Integrations*
- [ ] **#55** - Hunt the Wumpus AI demo - *Multiple Integrations*
- [ ] **#53** - Web interface input demo - *Multiple Integrations* *(New automation API could help)*
- [ ] **#45** - Accessibility mode demos - *Multiple Integrations* *(New automation API could help test)*
- [ ] **#36** - Dear ImGui integration tests - *Extensive Overhaul*
- [ ] **#65** - Python Explorer scene (replaces uitest) - *Extensive Overhaul*
---
## 🎮 STRATEGIC DIRECTION
### Engine Philosophy Maintained
- **C++ First**: Performance-critical code stays in C++
- **Python Close Behind**: Rich scripting without frame-rate impact
- **Game-Ready**: Each improvement should benefit actual game development
### Architecture Goals
1. **Clean Inheritance**: Drawable → UI components, proper type preservation
2. **Collection Consistency**: Uniform iteration, indexing, and search patterns
3. **Resource Management**: RAII everywhere, proper lifecycle handling
4. **Multi-Platform**: Windows/Linux feature parity maintained
---
## 📚 REFERENCES & CONTEXT
**Issue Dependencies** (Key Chains):
- Iterator System: Grid points → #73#69 (Alpha Blocker)
- UI Hierarchy: #71#63 (Alpha Blocker)
- Rendering: #6 (Alpha Blocker) → #8, #9#10
- Entity System: #30#16#67
- Window Management: #34#49, #61#62
**Commit References**:
- 167636c: Iterator improvements (UICollection/UIEntityCollection complete)
- Recent work: 7DRL 2025 completion, RPATH updates, console improvements
**Architecture Files**:
- Iterator patterns: src/UICollection.cpp, src/UIGrid.cpp
- Python integration: src/McRFPy_API.cpp, src/PyObjectUtils.h
- Game implementation: src/scripts/ (Crypt of Sokoban complete game)
---
## 🔮 FUTURE VISION: Pure Python Extension Architecture
### Concept: McRogueFace as a Traditional Python Package
**Status**: Unscheduled - Long-term vision
**Complexity**: Major architectural overhaul
Instead of being a C++ application that embeds Python, McRogueFace could be redesigned as a pure Python extension module that can be installed via `pip install mcrogueface`.
### Technical Approach
1. **Separate Core Engine from Python Embedding**
- Extract SFML rendering, audio, and input into C++ extension modules
- Remove embedded CPython interpreter
- Use Python's C API to expose functionality
2. **Module Structure**
```
mcrfpy/
├── __init__.py # Pure Python coordinator
├── _core.so # C++ rendering/game loop extension
├── _sfml.so # SFML bindings
├── _audio.so # Audio system bindings
└── engine.py # Python game engine logic
```
3. **Inverted Control Flow**
- Python drives the main loop instead of C++
- C++ extensions handle performance-critical operations
- Python manages game logic, scenes, and entity systems
### Benefits
- **Standard Python Packaging**: `pip install mcrogueface`
- **Virtual Environment Support**: Works with venv, conda, poetry
- **Better IDE Integration**: Standard Python development workflow
- **Easier Testing**: Use pytest, standard Python testing tools
- **Cross-Python Compatibility**: Support multiple Python versions
- **Modular Architecture**: Users can import only what they need
### Challenges
- **Major Refactoring**: Complete restructure of codebase
- **Performance Considerations**: Python-driven main loop overhead
- **Build Complexity**: Multiple extension modules to compile
- **Platform Support**: Need wheels for many platform/Python combinations
- **API Stability**: Would need careful design to maintain compatibility
### Implementation Phases (If Pursued)
1. **Proof of Concept**: Simple SFML binding as Python extension
2. **Core Extraction**: Separate rendering from Python embedding
3. **Module Design**: Define clean API boundaries
4. **Incremental Migration**: Move systems one at a time
5. **Compatibility Layer**: Support existing games during transition
### Example Usage (Future Vision)
```python
import mcrfpy
from mcrfpy import Scene, Frame, Sprite, Grid
# Create game directly in Python
game = mcrfpy.Game(width=1024, height=768)
# Define scenes using Python classes
class MainMenu(Scene):
def on_enter(self):
self.ui.append(Frame(100, 100, 200, 50))
self.ui.append(Sprite("logo.png", x=400, y=100))
def on_keypress(self, key, pressed):
if key == "ENTER" and pressed:
self.game.set_scene("game")
# Run the game
game.add_scene("menu", MainMenu())
game.run()
```
This architecture would make McRogueFace a first-class Python citizen, following standard Python packaging conventions while maintaining high performance through C++ extensions.
---
## 🚀 IMMEDIATE NEXT STEPS (Priority Order)
### Today (July 9) - EXECUTE NOW:
1. **Start Tutorial Part 1** - Basic setup and @ movement (2 hours)
2. **Implement UIGrid.at((x,y))** - CellView pattern (1 hour)
3. **Create Grid demo** for sizzle reel (1 hour)
4. **Fix any blocking bugs** discovered during tutorial writing
### Tomorrow (July 10) - CRITICAL PATH:
1. **Tutorial Parts 2-4** - Map drawing, entities, FOV, combat
2. **Implement compute_fov()** in UIGrid
3. **Add batch_update context manager**
4. **Expand sizzle reel** with entity choreography
### July 11 - ANNOUNCEMENT DAY:
1. **Polish 4 tutorial parts**
2. **Create announcement post** for r/roguelikedev
3. **Record sizzle reel video**
4. **Submit announcement** by end of day
### Architecture Decision Log:
- **DECIDED**: Use three-layer architecture (visual/world/perspective)
- **DECIDED**: Spatial hashing over quadtrees for entities
- **DECIDED**: Batch operations are mandatory, not optional
- **DECIDED**: TCOD integration as mcrfpy.libtcod submodule
- **DECIDED**: Tutorial must showcase McRogueFace strengths, not mimic TCOD
### Risk Mitigation:
- **If TCOD integration delays**: Use pure Python FOV for tutorial
- **If performance issues**: Focus on <100x100 maps for demos
- **If tutorial incomplete**: Ship with 4 solid parts + roadmap
- **If bugs block progress**: Document as "known issues" and continue
---
*Last Updated: 2025-07-09 (URGENT SPRINT MODE)*
*Next Review: July 11 after announcement*

View file

@ -0,0 +1,257 @@
# SFML 3.0 Migration Research for McRogueFace
## Executive Summary
SFML 3.0 was released on December 21, 2024, marking the first major version in 12 years. While it offers significant improvements in type safety, modern C++ features, and API consistency, migrating McRogueFace would require substantial effort. Given our plans for `mcrfpy.sfml`, I recommend **deferring migration to SFML 3.0** until after implementing the initial `mcrfpy.sfml` module with SFML 2.6.1.
## SFML 3.0 Overview
### Release Highlights
- **Release Date**: December 21, 2024
- **Development**: 3 years, 1,100+ commits, 41 new contributors
- **Major Feature**: C++17 support (now required)
- **Audio Backend**: Replaced OpenAL with miniaudio
- **Test Coverage**: Expanded to 57%
- **New Features**: Scissor and stencil testing
### Key Breaking Changes
#### 1. C++ Standard Requirements
- **Minimum**: C++17 (was C++03)
- **Compilers**: MSVC 16 (VS 2019), GCC 9, Clang 9, AppleClang 12
#### 2. Event System Overhaul
```cpp
// SFML 2.x
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
window.close();
break;
case sf::Event::KeyPressed:
handleKey(event.key.code);
break;
}
}
// SFML 3.0
while (const std::optional event = window.pollEvent()) {
if (event->is<sf::Event::Closed>()) {
window.close();
}
else if (const auto* keyPressed = event->getIf<sf::Event::KeyPressed>()) {
handleKey(keyPressed->code);
}
}
```
#### 3. Scoped Enumerations
```cpp
// SFML 2.x
sf::Keyboard::A
sf::Mouse::Left
// SFML 3.0
sf::Keyboard::Key::A
sf::Mouse::Button::Left
```
#### 4. Resource Loading
```cpp
// SFML 2.x
sf::Texture texture;
if (!texture.loadFromFile("image.png")) {
// Handle error
}
// SFML 3.0
try {
sf::Texture texture("image.png");
} catch (const std::exception& e) {
// Handle error
}
```
#### 5. Geometry Changes
```cpp
// SFML 2.x
sf::FloatRect rect(left, top, width, height);
// SFML 3.0
sf::FloatRect rect({left, top}, {width, height});
// Now uses position and size vectors
```
#### 6. CMake Changes
```cmake
# SFML 2.x
find_package(SFML 2.6 COMPONENTS graphics window system audio REQUIRED)
target_link_libraries(app sfml-graphics sfml-window sfml-system sfml-audio)
# SFML 3.0
find_package(SFML 3.0 COMPONENTS Graphics Window System Audio REQUIRED)
target_link_libraries(app SFML::Graphics SFML::Window SFML::System SFML::Audio)
```
## McRogueFace SFML Usage Analysis
### Current Usage Statistics
- **SFML Version**: 2.6.1
- **Integration Level**: Moderate to Heavy
- **Affected Files**: ~40+ source files
### Major Areas Requiring Changes
#### 1. Event Handling (High Impact)
- **Files**: `GameEngine.cpp`, `PyScene.cpp`
- **Changes**: Complete rewrite of event loops
- **Effort**: High
#### 2. Enumerations (Medium Impact)
- **Files**: `ActionCode.h`, all input handling
- **Changes**: Update all keyboard/mouse enum references
- **Effort**: Medium (mostly find/replace)
#### 3. Resource Loading (Medium Impact)
- **Files**: `PyTexture.cpp`, `PyFont.cpp`, `McRFPy_API.cpp`
- **Changes**: Constructor-based loading with exception handling
- **Effort**: Medium
#### 4. Geometry (Low Impact)
- **Files**: Various UI classes
- **Changes**: Update Rect construction
- **Effort**: Low
#### 5. CMake Build System (Low Impact)
- **Files**: `CMakeLists.txt`
- **Changes**: Update find_package and target names
- **Effort**: Low
### Code Examples from McRogueFace
#### Current Event Loop (GameEngine.cpp)
```cpp
sf::Event event;
while (window && window->pollEvent(event)) {
processEvent(event);
if (event.type == sf::Event::Closed) {
running = false;
}
}
```
#### Current Key Mapping (ActionCode.h)
```cpp
{sf::Keyboard::Key::A, KEY_A},
{sf::Keyboard::Key::Left, KEY_LEFT},
{sf::Mouse::Left, MOUSEBUTTON_LEFT}
```
## Impact on mcrfpy.sfml Module Plans
### Option 1: Implement with SFML 2.6.1 First (Recommended)
**Pros**:
- Faster initial implementation
- Stable, well-tested SFML version
- Can provide value immediately
- Migration can be done later
**Cons**:
- Will require migration work later
- API might need changes for SFML 3.0
### Option 2: Wait and Implement with SFML 3.0
**Pros**:
- Future-proof implementation
- Modern C++ features
- No migration needed later
**Cons**:
- Delays `mcrfpy.sfml` implementation
- SFML 3.0 is very new (potential bugs)
- Less documentation/examples available
### Option 3: Dual Support
**Pros**:
- Maximum flexibility
- Gradual migration path
**Cons**:
- Significant additional complexity
- Maintenance burden
- Conditional compilation complexity
## Migration Strategy Recommendation
### Phase 1: Current State (Now)
1. Continue with SFML 2.6.1
2. Implement `mcrfpy.sfml` module as planned
3. Design module API to minimize future breaking changes
### Phase 2: Preparation (3-6 months)
1. Monitor SFML 3.0 stability and adoption
2. Create migration branch for testing
3. Update development environment to C++17
### Phase 3: Migration (6-12 months)
1. Migrate McRogueFace core to SFML 3.0
2. Update `mcrfpy.sfml` to match
3. Provide migration guide for users
### Phase 4: Deprecation (12-18 months)
1. Deprecate SFML 2.6.1 support
2. Focus on SFML 3.0 features
## Specific Migration Tasks
### Prerequisites
- [ ] Update to C++17 compatible compiler
- [ ] Update CMake to 3.16+
- [ ] Review all SFML usage locations
### Core Changes
- [ ] Rewrite all event handling loops
- [ ] Update all enum references
- [ ] Convert resource loading to constructors
- [ ] Update geometry construction
- [ ] Update CMake configuration
### mcrfpy.sfml Considerations
- [ ] Design API to be version-agnostic where possible
- [ ] Use abstraction layer for version-specific code
- [ ] Document version requirements clearly
## Risk Assessment
### High Risk Areas
1. **Event System**: Complete paradigm shift
2. **Exception Handling**: New resource loading model
3. **Third-party Dependencies**: May not support SFML 3.0 yet
### Medium Risk Areas
1. **Performance**: New implementations may differ
2. **Platform Support**: New version may have issues
3. **Documentation**: Less community knowledge
### Low Risk Areas
1. **Basic Rendering**: Core concepts unchanged
2. **CMake**: Straightforward updates
3. **Enums**: Mechanical changes
## Conclusion
While SFML 3.0 offers significant improvements, the migration effort is substantial. Given that:
1. SFML 3.0 is very new (released December 2024)
2. McRogueFace has heavy SFML integration
3. We plan to implement `mcrfpy.sfml` soon
4. The event system requires complete rewriting
**I recommend deferring SFML 3.0 migration** until after successfully implementing `mcrfpy.sfml` with SFML 2.6.1. This allows us to:
- Deliver value sooner with `mcrfpy.sfml`
- Learn from early adopters of SFML 3.0
- Design our module API with migration in mind
- Migrate when SFML 3.0 is more mature
The migration should be revisited in 6-12 months when SFML 3.0 has proven stability and wider adoption.

200
SFML_EXPOSURE_RESEARCH.md Normal file
View file

@ -0,0 +1,200 @@
# SFML Exposure Research (#14)
## Executive Summary
After thorough research, I recommend **Option 3: Direct Integration** - implementing our own `mcrfpy.sfml` module with API compatibility to existing python-sfml bindings. This approach gives us full control while maintaining familiarity for developers who have used python-sfml.
## Current State Analysis
### McRogueFace SFML Usage
**Version**: SFML 2.6.1 (confirmed in `modules/SFML/include/SFML/Config.hpp`)
**Integration Level**: Moderate to Heavy
- SFML types appear in most header files
- Core rendering depends on `sf::RenderTarget`
- Event system uses `sf::Event` directly
- Input mapping uses SFML enums
**SFML Modules Used**:
- Graphics (sprites, textures, fonts, shapes)
- Window (events, keyboard, mouse)
- System (vectors, time, clocks)
- Audio (sound effects, music)
**Already Exposed to Python**:
- `mcrfpy.Color``sf::Color`
- `mcrfpy.Vector``sf::Vector2f`
- `mcrfpy.Font``sf::Font`
- `mcrfpy.Texture``sf::Texture`
### Python-SFML Status
**Official python-sfml (pysfml)**:
- Last version: 2.3.2 (supports SFML 2.3.2)
- Last meaningful update: ~2019
- Not compatible with SFML 2.6.1
- Project appears abandoned (domain redirects elsewhere)
- GitHub repo has 43 forks but no active maintained fork
**Alternatives**:
- No other major Python SFML bindings found
- Most alternatives were archived by 2021
## Option Analysis
### Option 1: Use Existing python-sfml
**Pros**:
- No development work needed
- Established API
**Cons**:
- Incompatible with SFML 2.6.1
- Would require downgrading to SFML 2.3.2
- Abandoned project (security/bug risks)
- Installation issues reported
**Verdict**: Not viable due to version incompatibility and abandonment
### Option 2: Fork and Update python-sfml
**Pros**:
- Leverage existing codebase
- Maintain API compatibility
**Cons**:
- Significant work to update from 2.3.2 to 2.6.1
- Cython complexity
- Maintenance burden of external codebase
- Still requires users to pip install separately
**Verdict**: High effort with limited benefit
### Option 3: Direct Integration (Recommended)
**Pros**:
- Full control over implementation
- Tight integration with McRogueFace
- No external dependencies
- Can expose exactly what we need
- Built-in module (no pip install)
- Can maintain API compatibility with python-sfml
**Cons**:
- Development effort required
- Need to maintain bindings
**Verdict**: Best long-term solution
## Implementation Plan for Direct Integration
### 1. Module Structure
```python
# Built-in module: mcrfpy.sfml
import mcrfpy.sfml as sf
# Maintain compatibility with python-sfml API
window = sf.RenderWindow(sf.VideoMode(800, 600), "My Window")
sprite = sf.Sprite()
texture = sf.Texture()
```
### 2. Priority Classes to Expose
**Phase 1 - Core Types** (Already partially done):
- [x] `sf::Vector2f`, `sf::Vector2i`
- [x] `sf::Color`
- [ ] `sf::Rect` (FloatRect, IntRect)
- [ ] `sf::VideoMode`
- [ ] `sf::Time`, `sf::Clock`
**Phase 2 - Graphics**:
- [x] `sf::Texture` (partial)
- [x] `sf::Font` (partial)
- [ ] `sf::Sprite` (full exposure)
- [ ] `sf::Text`
- [ ] `sf::Shape` hierarchy
- [ ] `sf::View`
- [ ] `sf::RenderWindow` (carefully managed)
**Phase 3 - Window/Input**:
- [ ] `sf::Event` and event types
- [ ] `sf::Keyboard` enums
- [ ] `sf::Mouse` enums
- [ ] `sf::Joystick`
**Phase 4 - Audio** (lower priority):
- [ ] `sf::SoundBuffer`
- [ ] `sf::Sound`
- [ ] `sf::Music`
### 3. Design Principles
1. **API Compatibility**: Match python-sfml's API where possible
2. **Memory Safety**: Use shared_ptr for resource management
3. **Thread Safety**: Consider GIL implications
4. **Integration**: Allow mixing with existing mcrfpy types
5. **Documentation**: Comprehensive docstrings
### 4. Technical Considerations
**Resource Sharing**:
- McRogueFace already manages SFML resources
- Need to share textures/fonts between mcrfpy and sfml modules
- Use the same underlying SFML objects
**Window Management**:
- McRogueFace owns the main window
- Expose read-only access or controlled modification
- Prevent users from closing/destroying the game window
**Event Handling**:
- Game engine processes events in main loop
- Need mechanism to expose events to Python safely
- Consider callback system or event queue
### 5. Implementation Phases
**Phase 1** (1-2 weeks):
- Create `mcrfpy.sfml` module structure
- Implement basic types (Vector, Color, Rect)
- Add comprehensive tests
**Phase 2** (2-3 weeks):
- Expose graphics classes
- Implement resource sharing with mcrfpy
- Create example scripts
**Phase 3** (2-3 weeks):
- Add window/input functionality
- Integrate with game event loop
- Performance optimization
**Phase 4** (1 week):
- Audio support
- Documentation
- PyPI packaging of mcrfpy.sfml separately
## Benefits of Direct Integration
1. **No Version Conflicts**: Always in sync with our SFML version
2. **Better Performance**: Direct C++ bindings without Cython overhead
3. **Selective Exposure**: Only expose what makes sense for game scripting
4. **Integrated Documentation**: Part of McRogueFace docs
5. **Future-Proof**: We control the implementation
## Migration Path for Users
Users familiar with python-sfml can easily migrate:
```python
# Old python-sfml code
import sfml as sf
# New McRogueFace code
import mcrfpy.sfml as sf
# Most code remains the same!
```
## Conclusion
Direct integration as `mcrfpy.sfml` provides the best balance of control, compatibility, and user experience. While it requires development effort, it ensures long-term maintainability and tight integration with McRogueFace's architecture.
The abandoned state of python-sfml actually presents an opportunity: we can provide a modern, maintained SFML binding for Python as part of McRogueFace, potentially attracting users who need SFML 2.6+ support.

226
STRATEGIC_VISION.md Normal file
View file

@ -0,0 +1,226 @@
# McRogueFace Strategic Vision: Beyond Alpha
## 🎯 Three Transformative Directions
### 1. **The Roguelike Operating System** 🖥️
Transform McRogueFace into a platform where games are apps:
#### Core Platform Features
- **Game Package Manager**: `mcrf install dungeon-crawler`
- **Hot-swappable Game Modules**: Switch between games without restarting
- **Shared Asset Library**: Common sprites, sounds, and UI components
- **Cross-Game Saves**: Universal character/inventory system
- **Multi-Game Sessions**: Run multiple roguelikes simultaneously in tabs
#### Technical Implementation
```python
# Future API Example
import mcrfpy.platform as platform
# Install and launch games
platform.install("nethack-remake")
platform.install("pixel-dungeon-port")
# Create multi-game session
session = platform.MultiGameSession()
session.add_tab("nethack-remake", save_file="warrior_lvl_15.sav")
session.add_tab("pixel-dungeon-port", new_game=True)
session.run()
```
### 2. **AI-Native Game Development** 🤖
Position McRogueFace as the first **AI-first roguelike engine**:
#### Integrated AI Features
- **GPT-Powered NPCs**: Dynamic dialogue and quest generation
- **Procedural Content via LLMs**: Describe a dungeon, AI generates it
- **AI Dungeon Master**: Adaptive difficulty and narrative
- **Code Assistant Integration**: Built-in AI helps write game logic
#### Revolutionary Possibilities
```python
# AI-Assisted Game Creation
from mcrfpy import ai_tools
# Natural language level design
dungeon = ai_tools.generate_dungeon("""
Create a haunted library with 3 floors.
First floor: Reading rooms with ghost librarians
Second floor: Restricted section with magical traps
Third floor: Ancient archive with boss encounter
""")
# AI-driven NPCs
npc = ai_tools.create_npc(
personality="Grumpy dwarf merchant who secretly loves poetry",
knowledge=["local rumors", "item prices", "hidden treasures"],
dynamic_dialogue=True
)
```
### 3. **Web-Native Multiplayer Platform** 🌐
Make McRogueFace the **Discord of Roguelikes**:
#### Multiplayer Revolution
- **Seamless Co-op**: Drop-in/drop-out multiplayer
- **Competitive Modes**: Racing, PvP arenas, daily challenges
- **Spectator System**: Watch and learn from others
- **Cloud Saves**: Play anywhere, sync everywhere
- **Social Features**: Guilds, tournaments, leaderboards
#### WebAssembly Future
```python
# Future Web API
import mcrfpy.web as web
# Host a game room
room = web.create_room("Epic Dungeon Run", max_players=4)
room.set_rules(friendly_fire=False, shared_loot=True)
room.open_to_public()
# Stream gameplay
stream = web.GameStream(room)
stream.to_twitch(channel="awesome_roguelike")
```
## 🏗️ Architecture Evolution Roadmap
### Phase 1: Beta Foundation (3-4 months)
**Focus**: Stability and Polish
- Complete RenderTexture system (#6)
- Implement save/load system
- Add audio mixing and 3D sound
- Create plugin architecture
- **Deliverable**: Beta release with plugin support
### Phase 2: Platform Infrastructure (6-8 months)
**Focus**: Multi-game Support
- Game package format specification
- Resource sharing system
- Inter-game communication API
- Cloud save infrastructure
- **Deliverable**: McRogueFace Platform 1.0
### Phase 3: AI Integration (8-12 months)
**Focus**: AI-Native Features
- LLM integration framework
- Procedural content pipelines
- Natural language game scripting
- AI behavior trees
- **Deliverable**: McRogueFace AI Studio
### Phase 4: Web Deployment (12-18 months)
**Focus**: Browser-based Gaming
- WebAssembly compilation
- WebRTC multiplayer
- Cloud computation for AI
- Mobile touch controls
- **Deliverable**: play.mcrogueface.com
## 🎮 Killer App Ideas
### 1. **Roguelike Maker** (Like Mario Maker)
- Visual dungeon editor
- Share levels online
- Play-test with AI
- Community ratings
### 2. **The Infinite Dungeon**
- Persistent world all players explore
- Procedurally expands based on player actions
- AI Dungeon Master creates personalized quests
- Cross-platform play
### 3. **Roguelike Battle Royale**
- 100 players start in connected dungeons
- Dungeons collapse, forcing encounters
- Last adventurer standing wins
- AI-generated commentary
## 🛠️ Technical Innovations to Pursue
### 1. **Temporal Debugging**
- Rewind game state
- Fork timelines for "what-if" scenarios
- Visual debugging of entity histories
### 2. **Neural Tileset Generation**
- Train on existing tilesets
- Generate infinite variations
- Style transfer between games
### 3. **Quantum Roguelike Mechanics**
- Superposition states for entities
- Probability-based combat
- Observer-effect puzzles
## 🌍 Community Building Strategy
### 1. **Education First**
- University partnerships
- Free curriculum: "Learn Python with Roguelikes"
- Summer of Code participation
- Student game jams
### 2. **Open Core Model**
- Core engine: MIT licensed
- Premium platforms: Cloud, AI, multiplayer
- Revenue sharing for content creators
- Sponsored tournaments
### 3. **Developer Ecosystem**
- Comprehensive API documentation
- Example games and tutorials
- Asset marketplace
- GitHub integration for mods
## 🎯 Success Metrics
### Year 1 Goals
- 1,000+ games created on platform
- 10,000+ monthly active developers
- 3 AAA-quality showcase games
- University curriculum adoption
### Year 2 Goals
- 100,000+ monthly active players
- $1M in platform transactions
- Major game studio partnership
- Native VR support
### Year 3 Goals
- #1 roguelike development platform
- IPO or acquisition readiness
- 1M+ monthly active players
- Industry standard for roguelikes
## 🚀 Next Immediate Actions
1. **Finish Beta Polish**
- Merge alpha_streamline_2 → master
- Complete RenderTexture (#6)
- Implement basic save/load
2. **Build Community**
- Launch Discord server
- Create YouTube tutorials
- Host first game jam
3. **Prototype AI Features**
- Simple GPT integration
- Procedural room descriptions
- Dynamic NPC dialogue
4. **Plan Platform Architecture**
- Design plugin system
- Spec game package format
- Cloud infrastructure research
---
*"McRogueFace: Not just an engine, but a universe of infinite dungeons."*
Remember: The best platforms create possibilities their creators never imagined. Build for the community you want to see, and they will create wonders.

16
_test.py Normal file
View file

@ -0,0 +1,16 @@
import mcrfpy
# Create a new scene
mcrfpy.createScene("intro")
# Add a text caption
caption = mcrfpy.Caption((50, 50), "Welcome to McRogueFace!")
caption.size = 48
caption.fill_color = (255, 255, 255)
# Add to scene
mcrfpy.sceneUI("intro").append(caption)
# Switch to the scene
mcrfpy.setScene("intro")

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
assets/Sprite-0001.ase Normal file

Binary file not shown.

BIN
assets/Sprite-0001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/alives_other.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

BIN
assets/boom.wav Normal file

Binary file not shown.

BIN
assets/custom_player.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
assets/gamescale_decor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
assets/kenney_TD_MR_IP.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

BIN
assets/sfx/splat1.ogg Normal file

Binary file not shown.

BIN
assets/sfx/splat2.ogg Normal file

Binary file not shown.

BIN
assets/sfx/splat3.ogg Normal file

Binary file not shown.

BIN
assets/sfx/splat4.ogg Normal file

Binary file not shown.

BIN
assets/sfx/splat5.ogg Normal file

Binary file not shown.

BIN
assets/sfx/splat6.ogg Normal file

Binary file not shown.

BIN
assets/sfx/splat7.ogg Normal file

Binary file not shown.

BIN
assets/sfx/splat8.ogg Normal file

Binary file not shown.

BIN
assets/sfx/splat9.ogg Normal file

Binary file not shown.

BIN
assets/temp_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

BIN
assets/terrain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
assets/terrain_alpha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
assets/test_portraits.ase Normal file

Binary file not shown.

BIN
assets/test_portraits.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

127
automation_example.py Normal file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""
McRogueFace Automation API Example
This demonstrates how to use the automation API for testing game UIs.
The API is PyAutoGUI-compatible for easy migration of existing tests.
"""
from mcrfpy import automation
import mcrfpy
import time
def automation_demo():
"""Demonstrate all automation API features"""
print("=== McRogueFace Automation API Demo ===\n")
# 1. Screen Information
print("1. Screen Information:")
screen_size = automation.size()
print(f" Screen size: {screen_size[0]}x{screen_size[1]}")
mouse_pos = automation.position()
print(f" Current mouse position: {mouse_pos}")
on_screen = automation.onScreen(100, 100)
print(f" Is (100, 100) on screen? {on_screen}")
print()
# 2. Mouse Movement
print("2. Mouse Movement:")
print(" Moving to center of screen...")
center_x, center_y = screen_size[0]//2, screen_size[1]//2
automation.moveTo(center_x, center_y, duration=0.5)
print(" Moving relative by (100, 100)...")
automation.moveRel(100, 100, duration=0.5)
print()
# 3. Mouse Clicks
print("3. Mouse Clicks:")
print(" Single click...")
automation.click()
time.sleep(0.2)
print(" Double click...")
automation.doubleClick()
time.sleep(0.2)
print(" Right click...")
automation.rightClick()
time.sleep(0.2)
print(" Triple click...")
automation.tripleClick()
print()
# 4. Keyboard Input
print("4. Keyboard Input:")
print(" Typing message...")
automation.typewrite("Hello from McRogueFace automation!", interval=0.05)
print(" Pressing Enter...")
automation.keyDown("enter")
automation.keyUp("enter")
print(" Hotkey Ctrl+A (select all)...")
automation.hotkey("ctrl", "a")
print()
# 5. Drag Operations
print("5. Drag Operations:")
print(" Dragging from current position to (500, 500)...")
automation.dragTo(500, 500, duration=1.0)
print(" Dragging relative by (-100, -100)...")
automation.dragRel(-100, -100, duration=0.5)
print()
# 6. Scroll Operations
print("6. Scroll Operations:")
print(" Scrolling up 5 clicks...")
automation.scroll(5)
time.sleep(0.5)
print(" Scrolling down 5 clicks...")
automation.scroll(-5)
print()
# 7. Screenshots
print("7. Screenshots:")
print(" Taking screenshot...")
success = automation.screenshot("automation_demo_screenshot.png")
print(f" Screenshot saved: {success}")
print()
print("=== Demo Complete ===")
def create_test_ui():
"""Create a simple UI for testing automation"""
print("Creating test UI...")
# Create a test scene
mcrfpy.createScene("automation_test")
mcrfpy.setScene("automation_test")
# Add some UI elements
ui = mcrfpy.sceneUI("automation_test")
# Add a frame
frame = mcrfpy.Frame(50, 50, 300, 200)
ui.append(frame)
# Add a caption
caption = mcrfpy.Caption(60, 60, "Automation Test UI")
ui.append(caption)
print("Test UI created!")
if __name__ == "__main__":
# Create test UI first
create_test_ui()
# Run automation demo
automation_demo()
print("\nYou can now use the automation API to test your game!")

336
automation_exec_examples.py Normal file
View file

@ -0,0 +1,336 @@
#!/usr/bin/env python3
"""
Examples of automation patterns using the proposed --exec flag
Usage:
./mcrogueface game.py --exec automation_basic.py
./mcrogueface game.py --exec automation_stress.py --exec monitor.py
"""
# ===== automation_basic.py =====
# Basic automation that runs alongside the game
import mcrfpy
from mcrfpy import automation
import time
class GameAutomation:
"""Automated testing that runs periodically"""
def __init__(self):
self.test_count = 0
self.test_results = []
def run_test_suite(self):
"""Called by timer - runs one test per invocation"""
test_name = f"test_{self.test_count}"
try:
if self.test_count == 0:
# Test main menu
self.test_main_menu()
elif self.test_count == 1:
# Test inventory
self.test_inventory()
elif self.test_count == 2:
# Test combat
self.test_combat()
else:
# All tests complete
self.report_results()
return
self.test_results.append((test_name, "PASS"))
except Exception as e:
self.test_results.append((test_name, f"FAIL: {e}"))
self.test_count += 1
def test_main_menu(self):
"""Test main menu interactions"""
automation.screenshot("test_main_menu_before.png")
automation.click(400, 300) # New Game button
time.sleep(0.5)
automation.screenshot("test_main_menu_after.png")
def test_inventory(self):
"""Test inventory system"""
automation.hotkey("i") # Open inventory
time.sleep(0.5)
automation.screenshot("test_inventory_open.png")
# Drag item
automation.moveTo(100, 200)
automation.dragTo(200, 200, duration=0.5)
automation.hotkey("i") # Close inventory
def test_combat(self):
"""Test combat system"""
# Move character
automation.keyDown("w")
time.sleep(0.5)
automation.keyUp("w")
# Attack
automation.click(500, 400)
automation.screenshot("test_combat.png")
def report_results(self):
"""Generate test report"""
print("\n=== Automation Test Results ===")
for test, result in self.test_results:
print(f"{test}: {result}")
print(f"Total: {len(self.test_results)} tests")
# Stop the timer
mcrfpy.delTimer("automation_suite")
# Create automation instance and register timer
auto = GameAutomation()
mcrfpy.setTimer("automation_suite", auto.run_test_suite, 2000) # Run every 2 seconds
print("Game automation started - tests will run every 2 seconds")
# ===== automation_stress.py =====
# Stress testing with random inputs
import mcrfpy
from mcrfpy import automation
import random
class StressTester:
"""Randomly interact with the game to find edge cases"""
def __init__(self):
self.action_count = 0
self.errors = []
def random_action(self):
"""Perform a random UI action"""
try:
action = random.choice([
self.random_click,
self.random_key,
self.random_drag,
self.random_hotkey
])
action()
self.action_count += 1
# Periodic screenshot
if self.action_count % 50 == 0:
automation.screenshot(f"stress_test_{self.action_count}.png")
print(f"Stress test: {self.action_count} actions performed")
except Exception as e:
self.errors.append((self.action_count, str(e)))
def random_click(self):
x = random.randint(0, 1024)
y = random.randint(0, 768)
button = random.choice(["left", "right"])
automation.click(x, y, button=button)
def random_key(self):
key = random.choice([
"a", "b", "c", "d", "w", "s",
"space", "enter", "escape",
"1", "2", "3", "4", "5"
])
automation.keyDown(key)
automation.keyUp(key)
def random_drag(self):
x1 = random.randint(0, 1024)
y1 = random.randint(0, 768)
x2 = random.randint(0, 1024)
y2 = random.randint(0, 768)
automation.moveTo(x1, y1)
automation.dragTo(x2, y2, duration=0.2)
def random_hotkey(self):
modifier = random.choice(["ctrl", "alt", "shift"])
key = random.choice(["a", "s", "d", "f"])
automation.hotkey(modifier, key)
# Create stress tester and run frequently
stress = StressTester()
mcrfpy.setTimer("stress_test", stress.random_action, 100) # Every 100ms
print("Stress testing started - random actions every 100ms")
# ===== monitor.py =====
# Performance and state monitoring
import mcrfpy
from mcrfpy import automation
import json
import time
class PerformanceMonitor:
"""Monitor game performance and state"""
def __init__(self):
self.samples = []
self.start_time = time.time()
def collect_sample(self):
"""Collect performance data"""
sample = {
"timestamp": time.time() - self.start_time,
"fps": mcrfpy.getFPS() if hasattr(mcrfpy, 'getFPS') else 60,
"scene": mcrfpy.currentScene(),
"memory": self.estimate_memory_usage()
}
self.samples.append(sample)
# Log every 10 samples
if len(self.samples) % 10 == 0:
avg_fps = sum(s["fps"] for s in self.samples[-10:]) / 10
print(f"Average FPS (last 10 samples): {avg_fps:.1f}")
# Save data every 100 samples
if len(self.samples) % 100 == 0:
self.save_report()
def estimate_memory_usage(self):
"""Estimate memory usage based on scene complexity"""
# This is a placeholder - real implementation would use psutil
ui_count = len(mcrfpy.sceneUI(mcrfpy.currentScene()))
return ui_count * 1000 # Rough estimate in KB
def save_report(self):
"""Save performance report"""
with open("performance_report.json", "w") as f:
json.dump({
"samples": self.samples,
"summary": {
"total_samples": len(self.samples),
"duration": time.time() - self.start_time,
"avg_fps": sum(s["fps"] for s in self.samples) / len(self.samples)
}
}, f, indent=2)
print(f"Performance report saved ({len(self.samples)} samples)")
# Create monitor and start collecting
monitor = PerformanceMonitor()
mcrfpy.setTimer("performance_monitor", monitor.collect_sample, 1000) # Every second
print("Performance monitoring started - sampling every second")
# ===== automation_replay.py =====
# Record and replay user actions
import mcrfpy
from mcrfpy import automation
import json
import time
class ActionRecorder:
"""Record user actions for replay"""
def __init__(self):
self.recording = False
self.actions = []
self.start_time = None
def start_recording(self):
"""Start recording user actions"""
self.recording = True
self.actions = []
self.start_time = time.time()
print("Recording started - perform actions to record")
# Register callbacks for all input types
mcrfpy.registerPyAction("record_click", self.record_click)
mcrfpy.registerPyAction("record_key", self.record_key)
# Map all mouse buttons
for button in range(3):
mcrfpy.registerInputAction(8192 + button, "record_click")
# Map common keys
for key in range(256):
mcrfpy.registerInputAction(4096 + key, "record_key")
def record_click(self, action_type):
"""Record mouse click"""
if not self.recording or action_type != "start":
return
pos = automation.position()
self.actions.append({
"type": "click",
"time": time.time() - self.start_time,
"x": pos[0],
"y": pos[1]
})
def record_key(self, action_type):
"""Record key press"""
if not self.recording or action_type != "start":
return
# This is simplified - real implementation would decode the key
self.actions.append({
"type": "key",
"time": time.time() - self.start_time,
"key": "unknown"
})
def stop_recording(self):
"""Stop recording and save"""
self.recording = False
with open("recorded_actions.json", "w") as f:
json.dump(self.actions, f, indent=2)
print(f"Recording stopped - {len(self.actions)} actions saved")
def replay_actions(self):
"""Replay recorded actions"""
print("Replaying recorded actions...")
with open("recorded_actions.json", "r") as f:
actions = json.load(f)
start_time = time.time()
action_index = 0
def replay_next():
nonlocal action_index
if action_index >= len(actions):
print("Replay complete")
mcrfpy.delTimer("replay")
return
action = actions[action_index]
current_time = time.time() - start_time
# Wait until it's time for this action
if current_time >= action["time"]:
if action["type"] == "click":
automation.click(action["x"], action["y"])
elif action["type"] == "key":
automation.keyDown(action["key"])
automation.keyUp(action["key"])
action_index += 1
mcrfpy.setTimer("replay", replay_next, 10) # Check every 10ms
# Example usage - would be controlled by UI
recorder = ActionRecorder()
# To start recording:
# recorder.start_recording()
# To stop and save:
# recorder.stop_recording()
# To replay:
# recorder.replay_actions()
print("Action recorder ready - call recorder.start_recording() to begin")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Before After
Before After

View file

@ -1,36 +0,0 @@
@echo off
REM Windows build script for McRogueFace
REM Run this over SSH without Visual Studio GUI
echo Building McRogueFace for Windows...
REM Clean previous build
if exist build_win rmdir /s /q build_win
mkdir build_win
cd build_win
REM Generate Visual Studio project files with CMake
REM Use -G to specify generator, -A for architecture
REM Visual Studio 2022 = "Visual Studio 17 2022"
REM Visual Studio 2019 = "Visual Studio 16 2019"
cmake -G "Visual Studio 17 2022" -A x64 ..
if errorlevel 1 (
echo CMake configuration failed!
exit /b 1
)
REM Build using MSBuild (comes with Visual Studio)
REM You can also use cmake --build . --config Release
msbuild McRogueFace.sln /p:Configuration=Release /p:Platform=x64 /m
if errorlevel 1 (
echo Build failed!
exit /b 1
)
echo Build completed successfully!
echo Executable location: build_win\Release\mcrogueface.exe
REM Alternative: Using cmake to build (works with any generator)
REM cmake --build . --config Release --parallel
cd ..

View file

@ -1,42 +0,0 @@
@echo off
REM Windows build script using cmake --build (generator-agnostic)
REM This version works with any CMake generator
echo Building McRogueFace for Windows using CMake...
REM Set build directory
set BUILD_DIR=build_win
set CONFIG=Release
REM Clean previous build
if exist %BUILD_DIR% rmdir /s /q %BUILD_DIR%
mkdir %BUILD_DIR%
cd %BUILD_DIR%
REM Configure with CMake
REM You can change the generator here if needed:
REM -G "Visual Studio 17 2022" (VS 2022)
REM -G "Visual Studio 16 2019" (VS 2019)
REM -G "MinGW Makefiles" (MinGW)
REM -G "Ninja" (Ninja build system)
cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=%CONFIG% ..
if errorlevel 1 (
echo CMake configuration failed!
cd ..
exit /b 1
)
REM Build using cmake (works with any generator)
cmake --build . --config %CONFIG% --parallel
if errorlevel 1 (
echo Build failed!
cd ..
exit /b 1
)
echo.
echo Build completed successfully!
echo Executable: %BUILD_DIR%\%CONFIG%\mcrogueface.exe
echo.
cd ..

BIN
caption_invisible.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
caption_moved.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
caption_opacity_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
caption_opacity_25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
caption_opacity_50.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
caption_visible.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

33
clean.sh Executable file
View file

@ -0,0 +1,33 @@
#!/bin/bash
# Clean script for McRogueFace - removes build artifacts
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${YELLOW}Cleaning McRogueFace build artifacts...${NC}"
# Remove build directory
if [ -d "build" ]; then
echo "Removing build directory..."
rm -rf build
fi
# Remove CMake artifacts from project root
echo "Removing CMake artifacts from project root..."
rm -f CMakeCache.txt
rm -f cmake_install.cmake
rm -f Makefile
rm -rf CMakeFiles
# Remove compiled executable from project root
rm -f mcrogueface
# Remove any test artifacts
rm -f test_script.py
rm -rf test_venv
rm -f python3 # symlink
echo -e "${GREEN}Clean complete!${NC}"

View file

@ -1,34 +0,0 @@
# CMake toolchain file for cross-compiling to Windows using MinGW-w64
# Usage: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64-x86_64.cmake ..
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
# Specify the cross-compiler (use posix variant for std::mutex support)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc-posix)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++-posix)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
# Target environment location
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
# Add MinGW system include directories for Windows headers
include_directories(SYSTEM /usr/x86_64-w64-mingw32/include)
# Adjust search behavior
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# Static linking of libgcc and libstdc++ to avoid runtime dependency issues
# Enable auto-import for Python DLL data symbols
set(CMAKE_EXE_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++ -Wl,--enable-auto-import")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++ -Wl,--enable-auto-import")
# Windows-specific defines
add_definitions(-DWIN32 -D_WIN32 -D_WINDOWS)
add_definitions(-DMINGW_HAS_SECURE_API)
# Disable console window for GUI applications (optional, can be overridden)
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mwindows")

80
compare_html_docs.py Normal file
View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Compare the original and improved HTML documentation."""
from pathlib import Path
def compare_docs():
"""Show key differences between the two HTML versions."""
print("HTML Documentation Improvements")
print("=" * 50)
# Read both files
original = Path("docs/api_reference.html")
improved = Path("docs/api_reference_improved.html")
if not original.exists() or not improved.exists():
print("Error: Documentation files not found")
return
with open(original, 'r') as f:
orig_content = f.read()
with open(improved, 'r') as f:
imp_content = f.read()
print("\n📊 File Size Comparison:")
print(f" Original: {len(orig_content):,} bytes")
print(f" Improved: {len(imp_content):,} bytes")
print("\n✅ Key Improvements:")
# Check newline handling
if '\\n' in orig_content and '\\n' not in imp_content:
print(" • Fixed literal \\n in documentation text")
# Check table of contents
if '[Classes](#classes)' in orig_content and '<a href="#classes">Classes</a>' in imp_content:
print(" • Converted markdown links to proper HTML anchors")
# Check headings
if '<h4>Args:</h4>' not in imp_content and '<strong>Arguments:</strong>' in imp_content:
print(" • Fixed Args/Attributes formatting (no longer H4 headings)")
# Check method descriptions
orig_count = orig_content.count('`Get bounding box')
imp_count = imp_content.count('get_bounds(...)')
if orig_count > imp_count:
print(f" • Reduced duplicate method descriptions ({orig_count}{imp_count})")
# Check Entity inheritance
if 'Entity.*Inherits from: Drawable' not in imp_content:
print(" • Fixed Entity class (no longer shows incorrect inheritance)")
# Check styling
if '.container {' in imp_content and '.container {' not in orig_content:
print(" • Enhanced visual styling with better typography and layout")
# Check class documentation
if '<h4>Arguments:</h4>' in imp_content:
print(" • Added detailed constructor arguments for all classes")
# Check automation
if 'automation.click</code></h4>' in imp_content:
print(" • Improved automation module documentation formatting")
print("\n📋 Documentation Coverage:")
print(f" • Classes: {imp_content.count('class-section')} documented")
print(f" • Functions: {imp_content.count('function-section')} documented")
method_count = imp_content.count('<h5><code class="method">')
print(f" • Methods: {method_count} documented")
print("\n✨ Visual Enhancements:")
print(" • Professional color scheme with syntax highlighting")
print(" • Responsive layout with max-width container")
print(" • Clear visual hierarchy with styled headings")
print(" • Improved code block formatting")
print(" • Better spacing and typography")
if __name__ == '__main__':
compare_docs()

157
css_colors.txt Normal file
View file

@ -0,0 +1,157 @@
aqua #00FFFF
black #000000
blue #0000FF
fuchsia #FF00FF
gray #808080
green #008000
lime #00FF00
maroon #800000
navy #000080
olive #808000
purple #800080
red #FF0000
silver #C0C0C0
teal #008080
white #FFFFFF
yellow #FFFF00
aliceblue #F0F8FF
antiquewhite #FAEBD7
aqua #00FFFF
aquamarine #7FFFD4
azure #F0FFFF
beige #F5F5DC
bisque #FFE4C4
black #000000
blanchedalmond #FFEBCD
blue #0000FF
blueviolet #8A2BE2
brown #A52A2A
burlywood #DEB887
cadetblue #5F9EA0
chartreuse #7FFF00
chocolate #D2691E
coral #FF7F50
cornflowerblue #6495ED
cornsilk #FFF8DC
crimson #DC143C
cyan #00FFFF
darkblue #00008B
darkcyan #008B8B
darkgoldenrod #B8860B
darkgray #A9A9A9
darkgreen #006400
darkkhaki #BDB76B
darkmagenta #8B008B
darkolivegreen #556B2F
darkorange #FF8C00
darkorchid #9932CC
darkred #8B0000
darksalmon #E9967A
darkseagreen #8FBC8F
darkslateblue #483D8B
darkslategray #2F4F4F
darkturquoise #00CED1
darkviolet #9400D3
deeppink #FF1493
deepskyblue #00BFFF
dimgray #696969
dodgerblue #1E90FF
firebrick #B22222
floralwhite #FFFAF0
forestgreen #228B22
fuchsia #FF00FF
gainsboro #DCDCDC
ghostwhite #F8F8FF
gold #FFD700
goldenrod #DAA520
gray #7F7F7F
green #008000
greenyellow #ADFF2F
honeydew #F0FFF0
hotpink #FF69B4
indianred #CD5C5C
indigo #4B0082
ivory #FFFFF0
khaki #F0E68C
lavender #E6E6FA
lavenderblush #FFF0F5
lawngreen #7CFC00
lemonchiffon #FFFACD
lightblue #ADD8E6
lightcoral #F08080
lightcyan #E0FFFF
lightgoldenrodyellow #FAFAD2
lightgreen #90EE90
lightgrey #D3D3D3
lightpink #FFB6C1
lightsalmon #FFA07A
lightseagreen #20B2AA
lightskyblue #87CEFA
lightslategray #778899
lightsteelblue #B0C4DE
lightyellow #FFFFE0
lime #00FF00
limegreen #32CD32
linen #FAF0E6
magenta #FF00FF
maroon #800000
mediumaquamarine #66CDAA
mediumblue #0000CD
mediumorchid #BA55D3
mediumpurple #9370DB
mediumseagreen #3CB371
mediumslateblue #7B68EE
mediumspringgreen #00FA9A
mediumturquoise #48D1CC
mediumvioletred #C71585
midnightblue #191970
mintcream #F5FFFA
mistyrose #FFE4E1
moccasin #FFE4B5
navajowhite #FFDEAD
navy #000080
navyblue #9FAFDF
oldlace #FDF5E6
olive #808000
olivedrab #6B8E23
orange #FFA500
orangered #FF4500
orchid #DA70D6
palegoldenrod #EEE8AA
palegreen #98FB98
paleturquoise #AFEEEE
palevioletred #DB7093
papayawhip #FFEFD5
peachpuff #FFDAB9
peru #CD853F
pink #FFC0CB
plum #DDA0DD
powderblue #B0E0E6
purple #800080
red #FF0000
rosybrown #BC8F8F
royalblue #4169E1
saddlebrown #8B4513
salmon #FA8072
sandybrown #FA8072
seagreen #2E8B57
seashell #FFF5EE
sienna #A0522D
silver #C0C0C0
skyblue #87CEEB
slateblue #6A5ACD
slategray #708090
snow #FFFAFA
springgreen #00FF7F
steelblue #4682B4
tan #D2B48C
teal #008080
thistle #D8BFD8
tomato #FF6347
turquoise #40E0D0
violet #EE82EE
wheat #F5DEB3
white #FFFFFF
whitesmoke #F5F5F5
yellow #FFFF00
yellowgreen #9ACD32

View file

@ -1,54 +1,6 @@
#ifndef __PLATFORM
#define __PLATFORM
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 1
#ifdef __EMSCRIPTEN__
// WASM/Emscripten platform - no /proc filesystem, limited std::filesystem support
std::wstring executable_path()
{
// In WASM, the executable is at the root of the virtual filesystem
return L"/";
}
std::wstring executable_filename()
{
// In WASM, we use a fixed executable name
return L"/mcrogueface";
}
std::wstring working_path()
{
// In WASM, working directory is root of virtual filesystem
return L"/";
}
std::string narrow_string(std::wstring convertme)
{
// Simple conversion for ASCII/UTF-8 compatible strings
std::string result;
result.reserve(convertme.size());
for (wchar_t wc : convertme) {
if (wc < 128) {
result.push_back(static_cast<char>(wc));
} else {
// For non-ASCII, use a simple UTF-8 encoding
if (wc < 0x800) {
result.push_back(static_cast<char>(0xC0 | (wc >> 6)));
result.push_back(static_cast<char>(0x80 | (wc & 0x3F)));
} else {
result.push_back(static_cast<char>(0xE0 | (wc >> 12)));
result.push_back(static_cast<char>(0x80 | ((wc >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (wc & 0x3F)));
}
}
}
return result;
}
#else
// Native Linux platform
std::wstring executable_path()
{
/*
@ -60,7 +12,7 @@ std::wstring executable_path()
return exec_path.wstring();
//size_t path_index = exec_path.find_last_of('/');
//return exec_path.substr(0, path_index);
}
std::wstring executable_filename()
@ -85,6 +37,4 @@ std::string narrow_string(std::wstring convertme)
return converter.to_bytes(convertme);
}
#endif // __EMSCRIPTEN__
#endif // __PLATFORM
#endif

View file

@ -1,12 +1,12 @@
#ifndef __PLATFORM
#define __PLATFORM
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 1
#include <windows.h>
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 0
#include <Windows.h>
std::wstring executable_path()
{
wchar_t buffer[MAX_PATH];
GetModuleFileNameW(NULL, buffer, MAX_PATH); // Use explicit Unicode version
GetModuleFileName(NULL, buffer, MAX_PATH);
std::wstring exec_path = buffer;
size_t path_index = exec_path.find_last_of(L"\\/");
return exec_path.substr(0, path_index);
@ -15,7 +15,7 @@ std::wstring executable_path()
std::wstring executable_filename()
{
wchar_t buffer[MAX_PATH];
GetModuleFileNameW(NULL, buffer, MAX_PATH); // Use explicit Unicode version
GetModuleFileName(NULL, buffer, MAX_PATH);
std::wstring exec_path = buffer;
return exec_path;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Before After
Before After

852
docs/api_reference.html Normal file
View file

@ -0,0 +1,852 @@
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<title>McRogueFace API Reference</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6; color: #333; max-width: 900px; margin: 0 auto; padding: 20px; }
h1, h2, h3, h4, h5 { color: #2c3e50; margin-top: 24px; }
h1 { border-bottom: 2px solid #3498db; padding-bottom: 10px; }
h2 { border-bottom: 1px solid #ecf0f1; padding-bottom: 8px; }
code { background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 90%; }
pre { background: #f4f4f4; padding: 12px; border-radius: 5px; overflow-x: auto; }
pre code { background: none; padding: 0; }
blockquote { border-left: 4px solid #3498db; margin: 0; padding-left: 16px; color: #7f8c8d; }
hr { border: none; border-top: 1px solid #ecf0f1; margin: 24px 0; }
a { color: #3498db; text-decoration: none; }
a:hover { text-decoration: underline; }
.property { color: #27ae60; }
.method { color: #2980b9; }
.class-name { color: #8e44ad; font-weight: bold; }
ul { padding-left: 24px; }
li { margin: 4px 0; }
</style>
</head><body>
<h1>McRogueFace API Reference</h1>
<em>Generated on 2025-07-08 10:11:22</em>
<h2>Overview</h2>
<p>McRogueFace Python API\n\nCore game engine interface for creating roguelike games with Python.\n\nThis module provides:\n- Scene management (createScene, setScene, currentScene)\n- UI components (Frame, Caption, Sprite, Grid)\n- Entity system for game objects\n- Audio playback (sound effects and music)\n- Timer system for scheduled events\n- Input handling\n- Performance metrics\n\nExample:\n import mcrfpy\n \n # Create a new scene\n mcrfpy.createScene('game')\n mcrfpy.setScene('game')\n \n # Add UI elements\n frame = mcrfpy.Frame(10, 10, 200, 100)\n caption = mcrfpy.Caption('Hello World', 50, 50)\n mcrfpy.sceneUI().extend([frame, caption])\n</p>
<h2>Table of Contents</h2>
<ul>
<li>[Classes](#classes)</li>
<li>[Functions](#functions)</li>
<li>[Automation Module](#automation-module)</li>
</ul>
<h2>Classes</h2>
<h3>UI Components</h3>
<h3>class `Caption`</h3>
<em>Inherits from: Drawable</em>
<pre><code class="language-python">
Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)
</code></pre>
<p>A text display UI element with customizable font and styling.</p>
<p>Args:</p>
<p>text (str): The text content to display. Default: ''</p>
<p>x (float): X position in pixels. Default: 0</p>
<p>y (float): Y position in pixels. Default: 0</p>
<p>font (Font): Font object for text rendering. Default: engine default font</p>
<p>fill_color (Color): Text fill color. Default: (255, 255, 255, 255)</p>
<p>outline_color (Color): Text outline color. Default: (0, 0, 0, 255)</p>
<p>outline (float): Text outline thickness. Default: 0</p>
<p>click (callable): Click event handler. Default: None</p>
<p>Attributes:</p>
<p>text (str): The displayed text content</p>
<p>x, y (float): Position in pixels</p>
<p>font (Font): Font used for rendering</p>
<p>fill_color, outline_color (Color): Text appearance</p>
<p>outline (float): Outline thickness</p>
<p>click (callable): Click event handler</p>
<p>visible (bool): Visibility state</p>
<p>z_index (int): Rendering order</p>
<p>w, h (float): Read-only computed size based on text and font</p>
<h4>Methods</h4>
<h5>`Get bounding box as (x, y, width, height)`</h5>
<p>Get bounding box as (x, y, width, height)</p>
<h5>`Move by relative offset (dx, dy)`</h5>
<p>Move by relative offset (dx, dy)</p>
<h5>`Resize to new dimensions (width, height)`</h5>
<p>Resize to new dimensions (width, height)</p>
<hr>
<h3>class `Entity`</h3>
<em>Inherits from: Drawable</em>
<p>UIEntity objects</p>
<h4>Methods</h4>
<h5>`at(...)`</h5>
<h5>`die(...)`</h5>
<p>Remove this entity from its grid</p>
<h5>`Get bounding box as (x, y, width, height)`</h5>
<p>Get bounding box as (x, y, width, height)</p>
<h5>`index(...)`</h5>
<h5>`Move by relative offset (dx, dy)`</h5>
<p>Move by relative offset (dx, dy)</p>
<h5>`Resize to new dimensions (width, height)`</h5>
<p>Resize to new dimensions (width, height)</p>
<hr>
<h3>class `Frame`</h3>
<em>Inherits from: Drawable</em>
<pre><code class="language-python">
Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)
</code></pre>
<p>A rectangular frame UI element that can contain other drawable elements.</p>
<p>Args:</p>
<p>x (float): X position in pixels. Default: 0</p>
<p>y (float): Y position in pixels. Default: 0</p>
<p>w (float): Width in pixels. Default: 0</p>
<p>h (float): Height in pixels. Default: 0</p>
<p>fill_color (Color): Background fill color. Default: (0, 0, 0, 128)</p>
<p>outline_color (Color): Border outline color. Default: (255, 255, 255, 255)</p>
<p>outline (float): Border outline thickness. Default: 0</p>
<p>click (callable): Click event handler. Default: None</p>
<p>children (list): Initial list of child drawable elements. Default: None</p>
<p>Attributes:</p>
<p>x, y (float): Position in pixels</p>
<p>w, h (float): Size in pixels</p>
<p>fill_color, outline_color (Color): Visual appearance</p>
<p>outline (float): Border thickness</p>
<p>click (callable): Click event handler</p>
<p>children (list): Collection of child drawable elements</p>
<p>visible (bool): Visibility state</p>
<p>z_index (int): Rendering order</p>
<p>clip_children (bool): Whether to clip children to frame bounds</p>
<h4>Methods</h4>
<h5>`Get bounding box as (x, y, width, height)`</h5>
<p>Get bounding box as (x, y, width, height)</p>
<h5>`Move by relative offset (dx, dy)`</h5>
<p>Move by relative offset (dx, dy)</p>
<h5>`Resize to new dimensions (width, height)`</h5>
<p>Resize to new dimensions (width, height)</p>
<hr>
<h3>class `Grid`</h3>
<em>Inherits from: Drawable</em>
<pre><code class="language-python">
Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)
</code></pre>
<p>A grid-based tilemap UI element for rendering tile-based levels and game worlds.</p>
<p>Args:</p>
<p>x (float): X position in pixels. Default: 0</p>
<p>y (float): Y position in pixels. Default: 0</p>
<p>grid_size (tuple): Grid dimensions as (width, height) in tiles. Default: (20, 20)</p>
<p>texture (Texture): Texture atlas containing tile sprites. Default: None</p>
<p>tile_width (int): Width of each tile in pixels. Default: 16</p>
<p>tile_height (int): Height of each tile in pixels. Default: 16</p>
<p>scale (float): Grid scaling factor. Default: 1.0</p>
<p>click (callable): Click event handler. Default: None</p>
<p>Attributes:</p>
<p>x, y (float): Position in pixels</p>
<p>grid_size (tuple): Grid dimensions (width, height) in tiles</p>
<p>tile_width, tile_height (int): Tile dimensions in pixels</p>
<p>texture (Texture): Tile texture atlas</p>
<p>scale (float): Scale multiplier</p>
<p>points (list): 2D array of GridPoint objects for tile data</p>
<p>entities (list): Collection of Entity objects in the grid</p>
<p>background_color (Color): Grid background color</p>
<p>click (callable): Click event handler</p>
<p>visible (bool): Visibility state</p>
<p>z_index (int): Rendering order</p>
<h4>Methods</h4>
<h5>`at(...)`</h5>
<h5>`Get bounding box as (x, y, width, height)`</h5>
<p>Get bounding box as (x, y, width, height)</p>
<h5>`Move by relative offset (dx, dy)`</h5>
<p>Move by relative offset (dx, dy)</p>
<h5>`Resize to new dimensions (width, height)`</h5>
<p>Resize to new dimensions (width, height)</p>
<hr>
<h3>class `Sprite`</h3>
<em>Inherits from: Drawable</em>
<pre><code class="language-python">
Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)
</code></pre>
<p>A sprite UI element that displays a texture or portion of a texture atlas.</p>
<p>Args:</p>
<p>x (float): X position in pixels. Default: 0</p>
<p>y (float): Y position in pixels. Default: 0</p>
<p>texture (Texture): Texture object to display. Default: None</p>
<p>sprite_index (int): Index into texture atlas (if applicable). Default: 0</p>
<p>scale (float): Sprite scaling factor. Default: 1.0</p>
<p>click (callable): Click event handler. Default: None</p>
<p>Attributes:</p>
<p>x, y (float): Position in pixels</p>
<p>texture (Texture): The texture being displayed</p>
<p>sprite_index (int): Current sprite index in texture atlas</p>
<p>scale (float): Scale multiplier</p>
<p>click (callable): Click event handler</p>
<p>visible (bool): Visibility state</p>
<p>z_index (int): Rendering order</p>
<p>w, h (float): Read-only computed size based on texture and scale</p>
<h4>Methods</h4>
<h5>`Get bounding box as (x, y, width, height)`</h5>
<p>Get bounding box as (x, y, width, height)</p>
<h5>`Move by relative offset (dx, dy)`</h5>
<p>Move by relative offset (dx, dy)</p>
<h5>`Resize to new dimensions (width, height)`</h5>
<p>Resize to new dimensions (width, height)</p>
<hr>
<h3>Collections</h3>
<h3>class `EntityCollection`</h3>
<p>Iterable, indexable collection of Entities</p>
<h4>Methods</h4>
<h5>`append(...)`</h5>
<h5>`count(...)`</h5>
<h5>`extend(...)`</h5>
<h5>`index(...)`</h5>
<h5>`remove(...)`</h5>
<hr>
<h3>class `UICollection`</h3>
<p>Iterable, indexable collection of UI objects</p>
<h4>Methods</h4>
<h5>`append(...)`</h5>
<h5>`count(...)`</h5>
<h5>`extend(...)`</h5>
<h5>`index(...)`</h5>
<h5>`remove(...)`</h5>
<hr>
<h3>class `UICollectionIter`</h3>
<p>Iterator for a collection of UI objects</p>
<hr>
<h3>class `UIEntityCollectionIter`</h3>
<p>Iterator for a collection of UI objects</p>
<hr>
<h3>System Types</h3>
<h3>class `Color`</h3>
<p>SFML Color Object</p>
<h4>Methods</h4>
<h5>`Create Color from hex string (e.g., '#FF0000' or 'FF0000')`</h5>
<p>Create Color from hex string (e.g., '#FF0000' or 'FF0000')</p>
<h5>`lerp(...)`</h5>
<p>Linearly interpolate between this color and another</p>
<h5>`to_hex(...)`</h5>
<p>Convert Color to hex string</p>
<hr>
<h3>class `Font`</h3>
<p>SFML Font Object</p>
<hr>
<h3>class `Texture`</h3>
<p>SFML Texture Object</p>
<hr>
<h3>class `Vector`</h3>
<p>SFML Vector Object</p>
<h4>Methods</h4>
<h5>`angle(...)`</h5>
<h5>`copy(...)`</h5>
<h5>`distance_to(...)`</h5>
<p>Return the distance to another vector</p>
<h5>`dot(...)`</h5>
<h5>`magnitude(...)`</h5>
<p>Return the length of the vector</p>
<h5>`magnitude_squared(...)`</h5>
<p>Return the squared length of the vector</p>
<h5>`normalize(...)`</h5>
<p>Return a unit vector in the same direction</p>
<hr>
<h3>Other Classes</h3>
<h3>class `Animation`</h3>
<p>Animation object for animating UI properties</p>
<h4>Methods</h4>
<h5>`get_current_value(...)`</h5>
<p>Get the current interpolated value</p>
<h5>`start(...)`</h5>
<p>Start the animation on a target UIDrawable</p>
<h5>`Update the animation by deltaTime (returns True if still running)`</h5>
<p>Update the animation by deltaTime (returns True if still running)</p>
<hr>
<h3>class `Drawable`</h3>
<p>Base class for all drawable UI elements</p>
<h4>Methods</h4>
<h5>`Get bounding box as (x, y, width, height)`</h5>
<p>Get bounding box as (x, y, width, height)</p>
<h5>`Move by relative offset (dx, dy)`</h5>
<p>Move by relative offset (dx, dy)</p>
<h5>`Resize to new dimensions (width, height)`</h5>
<p>Resize to new dimensions (width, height)</p>
<hr>
<h3>class `GridPoint`</h3>
<p>UIGridPoint object</p>
<hr>
<h3>class `GridPointState`</h3>
<p>UIGridPointState object</p>
<hr>
<h3>class `Scene`</h3>
<p>Base class for object-oriented scenes</p>
<h4>Methods</h4>
<h5>`activate(...)`</h5>
<p>Make this the active scene</p>
<h5>`get_ui(...)`</h5>
<p>Get the UI element collection for this scene</p>
<h5>`Register a keyboard handler function (alternative to overriding on_keypress)`</h5>
<p>Register a keyboard handler function (alternative to overriding on_keypress)</p>
<hr>
<h3>class `Timer`</h3>
<p>Timer object for scheduled callbacks</p>
<h4>Methods</h4>
<h5>`cancel(...)`</h5>
<p>Cancel the timer and remove it from the system</p>
<h5>`pause(...)`</h5>
<p>Pause the timer</p>
<h5>`restart(...)`</h5>
<p>Restart the timer from the current time</p>
<h5>`resume(...)`</h5>
<p>Resume a paused timer</p>
<hr>
<h3>class `Window`</h3>
<p>Window singleton for accessing and modifying the game window properties</p>
<h4>Methods</h4>
<h5>`center(...)`</h5>
<p>Center the window on the screen</p>
<h5>`get(...)`</h5>
<p>Get the Window singleton instance</p>
<h5>`screenshot(...)`</h5>
<hr>
<h2>Functions</h2>
<h3>Scene Management</h3>
<h3>`createScene(name: str)`</h3>
<p>Create a new empty scene.</p>
<em>*Args:*</em>
<p>name: Unique name for the new scene</p>
<em>*Raises:*</em>
<p>ValueError: If a scene with this name already exists</p>
<em>*Note:*</em>
<p>The scene is created but not made active. Use setScene() to switch to it.</p>
<hr>
<h3>`currentScene()`</h3>
<p>Get the name of the currently active scene.</p>
<em>*Returns:*</em>
<p>str: Name of the current scene</p>
<hr>
<h3>`keypressScene(handler: callable)`</h3>
<p>Set the keyboard event handler for the current scene.</p>
<em>*Args:*</em>
<p>handler: Callable that receives (key_name: str, is_pressed: bool)</p>
<em>*Example:*</em>
<p>def on_key(key, pressed):</p>
<p>if key == 'A' and pressed:</p>
<p>print('A key pressed')</p>
<p>mcrfpy.keypressScene(on_key)</p>
<hr>
<h3>`sceneUI(scene: str = None)`</h3>
<p>Get all UI elements for a scene.</p>
<em>*Args:*</em>
<p>scene: Scene name. If None, uses current scene</p>
<em>*Returns:*</em>
<p>list: All UI elements (Frame, Caption, Sprite, Grid) in the scene</p>
<em>*Raises:*</em>
<p>KeyError: If the specified scene doesn't exist</p>
<hr>
<h3>`setScene(scene: str, transition: str = None, duration: float = 0.0)`</h3>
<p>Switch to a different scene with optional transition effect.</p>
<em>*Args:*</em>
<p>scene: Name of the scene to switch to</p>
<p>transition: Transition type ('fade', 'slide_left', 'slide_right', 'slide_up', 'slide_down')</p>
<p>duration: Transition duration in seconds (default: 0.0 for instant)</p>
<em>*Raises:*</em>
<p>KeyError: If the scene doesn't exist</p>
<p>ValueError: If the transition type is invalid</p>
<hr>
<h3>Audio</h3>
<h3>`createSoundBuffer(filename: str)`</h3>
<p>Load a sound effect from a file and return its buffer ID.</p>
<em>*Args:*</em>
<p>filename: Path to the sound file (WAV, OGG, FLAC)</p>
<em>*Returns:*</em>
<p>int: Buffer ID for use with playSound()</p>
<em>*Raises:*</em>
<p>RuntimeError: If the file cannot be loaded</p>
<hr>
<h3>`getMusicVolume()`</h3>
<p>Get the current music volume level.</p>
<em>*Returns:*</em>
<p>int: Current volume (0-100)</p>
<hr>
<h3>`getSoundVolume()`</h3>
<p>Get the current sound effects volume level.</p>
<em>*Returns:*</em>
<p>int: Current volume (0-100)</p>
<hr>
<h3>`loadMusic(filename: str)`</h3>
<p>Load and immediately play background music from a file.</p>
<em>*Args:*</em>
<p>filename: Path to the music file (WAV, OGG, FLAC)</p>
<em>*Note:*</em>
<p>Only one music track can play at a time. Loading new music stops the current track.</p>
<hr>
<h3>`playSound(buffer_id: int)`</h3>
<p>Play a sound effect using a previously loaded buffer.</p>
<em>*Args:*</em>
<p>buffer_id: Sound buffer ID returned by createSoundBuffer()</p>
<em>*Raises:*</em>
<p>RuntimeError: If the buffer ID is invalid</p>
<hr>
<h3>`setMusicVolume(volume: int)`</h3>
<p>Set the global music volume.</p>
<em>*Args:*</em>
<p>volume: Volume level from 0 (silent) to 100 (full volume)</p>
<hr>
<h3>`setSoundVolume(volume: int)`</h3>
<p>Set the global sound effects volume.</p>
<em>*Args:*</em>
<p>volume: Volume level from 0 (silent) to 100 (full volume)</p>
<hr>
<h3>UI Utilities</h3>
<h3>`find(name: str, scene: str = None)`</h3>
<p>Find the first UI element with the specified name.</p>
<em>*Args:*</em>
<p>name: Exact name to search for</p>
<p>scene: Scene to search in (default: current scene)</p>
<em>*Returns:*</em>
<p>Frame, Caption, Sprite, Grid, or Entity if found; None otherwise</p>
<em>*Note:*</em>
<p>Searches scene UI elements and entities within grids.</p>
<hr>
<h3>`findAll(pattern: str, scene: str = None)`</h3>
<p>Find all UI elements matching a name pattern.</p>
<em>*Args:*</em>
<p>pattern: Name pattern with optional wildcards (* matches any characters)</p>
<p>scene: Scene to search in (default: current scene)</p>
<em>*Returns:*</em>
<p>list: All matching UI elements and entities</p>
<em>*Example:*</em>
<p>findAll('enemy*') # Find all elements starting with 'enemy'</p>
<p>findAll('*_button') # Find all elements ending with '_button'</p>
<hr>
<h3>System</h3>
<h3>`delTimer(name: str)`</h3>
<p>Stop and remove a timer.</p>
<em>*Args:*</em>
<p>name: Timer identifier to remove</p>
<em>*Note:*</em>
<p>No error is raised if the timer doesn't exist.</p>
<hr>
<h3>`exit()`</h3>
<p>Cleanly shut down the game engine and exit the application.</p>
<em>*Note:*</em>
<p>This immediately closes the window and terminates the program.</p>
<hr>
<h3>`getMetrics()`</h3>
<p>Get current performance metrics.</p>
<em>*Returns:*</em>
<p>dict: Performance data with keys:</p>
<ul>
<li>frame_time: Last frame duration in seconds</li>
<li>avg_frame_time: Average frame time</li>
<li>fps: Frames per second</li>
<li>draw_calls: Number of draw calls</li>
<li>ui_elements: Total UI element count</li>
<li>visible_elements: Visible element count</li>
<li>current_frame: Frame counter</li>
<li>runtime: Total runtime in seconds</li>
</ul>
<hr>
<h3>`setScale(multiplier: float)`</h3>
<p>Scale the game window size.</p>
<em>*Args:*</em>
<p>multiplier: Scale factor (e.g., 2.0 for double size)</p>
<em>*Note:*</em>
<p>The internal resolution remains 1024x768, but the window is scaled.</p>
<p>This is deprecated - use Window.resolution instead.</p>
<hr>
<h3>`setTimer(name: str, handler: callable, interval: int)`</h3>
<p>Create or update a recurring timer.</p>
<em>*Args:*</em>
<p>name: Unique identifier for the timer</p>
<p>handler: Function called with (runtime: float) parameter</p>
<p>interval: Time between calls in milliseconds</p>
<em>*Note:*</em>
<p>If a timer with this name exists, it will be replaced.</p>
<p>The handler receives the total runtime in seconds as its argument.</p>
<hr>
<h2>Automation Module</h2>
<p>The <code>mcrfpy.automation</code> module provides testing and automation capabilities.</p>
<h3>`automation.click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position`</h3>
<p>click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position</p>
<hr>
<h3>`automation.doubleClick(x=None, y=None) - Double click at position`</h3>
<p>doubleClick(x=None, y=None) - Double click at position</p>
<hr>
<h3>`automation.dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position`</h3>
<p>dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position</p>
<hr>
<h3>`automation.dragTo(x, y, duration=0.0, button='left') - Drag mouse to position`</h3>
<p>dragTo(x, y, duration=0.0, button='left') - Drag mouse to position</p>
<hr>
<h3>`automation.hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))`</h3>
<p>hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))</p>
<hr>
<h3>`automation.keyDown(key) - Press and hold a key`</h3>
<p>keyDown(key) - Press and hold a key</p>
<hr>
<h3>`automation.keyUp(key) - Release a key`</h3>
<p>keyUp(key) - Release a key</p>
<hr>
<h3>`automation.middleClick(x=None, y=None) - Middle click at position`</h3>
<p>middleClick(x=None, y=None) - Middle click at position</p>
<hr>
<h3>`automation.mouseDown(x=None, y=None, button='left') - Press mouse button`</h3>
<p>mouseDown(x=None, y=None, button='left') - Press mouse button</p>
<hr>
<h3>`automation.mouseUp(x=None, y=None, button='left') - Release mouse button`</h3>
<p>mouseUp(x=None, y=None, button='left') - Release mouse button</p>
<hr>
<h3>`automation.moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position`</h3>
<p>moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position</p>
<hr>
<h3>`automation.moveTo(x, y, duration=0.0) - Move mouse to absolute position`</h3>
<p>moveTo(x, y, duration=0.0) - Move mouse to absolute position</p>
<hr>
<h3>`automation.onScreen(x, y) - Check if coordinates are within screen bounds`</h3>
<p>onScreen(x, y) - Check if coordinates are within screen bounds</p>
<hr>
<h3>`automation.position() - Get current mouse position as (x, y) tuple`</h3>
<p>position() - Get current mouse position as (x, y) tuple</p>
<hr>
<h3>`automation.rightClick(x=None, y=None) - Right click at position`</h3>
<p>rightClick(x=None, y=None) - Right click at position</p>
<hr>
<h3>`automation.screenshot(filename) - Save a screenshot to the specified file`</h3>
<p>screenshot(filename) - Save a screenshot to the specified file</p>
<hr>
<h3>`automation.scroll(clicks, x=None, y=None) - Scroll wheel at position`</h3>
<p>scroll(clicks, x=None, y=None) - Scroll wheel at position</p>
<hr>
<h3>`automation.size() - Get screen size as (width, height) tuple`</h3>
<p>size() - Get screen size as (width, height) tuple</p>
<hr>
<h3>`automation.tripleClick(x=None, y=None) - Triple click at position`</h3>
<p>tripleClick(x=None, y=None) - Triple click at position</p>
<hr>
<h3>`automation.typewrite(message, interval=0.0) - Type text with optional interval between keystrokes`</h3>
<p>typewrite(message, interval=0.0) - Type text with optional interval between keystrokes</p>
<hr>
</body></html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,13 +0,0 @@
"""McRogueFace - Animated Movement (basic)
Documentation: https://mcrogueface.github.io/cookbook/combat_animated_movement
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_animated_movement_basic.py
This code is extracted from the McRogueFace documentation and can be
run directly with: ./mcrogueface path/to/this/file.py
"""
if new_x != current_x:
anim = mcrfpy.Animation("x", float(new_x), duration, "easeInOut", callback=done)
else:
anim = mcrfpy.Animation("y", float(new_y), duration, "easeInOut", callback=done)

View file

@ -1,12 +0,0 @@
"""McRogueFace - Animated Movement (basic_2)
Documentation: https://mcrogueface.github.io/cookbook/combat_animated_movement
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_animated_movement_basic_2.py
This code is extracted from the McRogueFace documentation and can be
run directly with: ./mcrogueface path/to/this/file.py
"""
current_anim = mcrfpy.Animation("x", 100.0, 0.5, "linear")
current_anim.start(entity)
# Later: current_anim = None # Let it complete or create new one

View file

@ -1,45 +0,0 @@
"""McRogueFace - Basic Enemy AI (basic)
Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_basic.py
This code is extracted from the McRogueFace documentation and can be
run directly with: ./mcrogueface path/to/this/file.py
"""
import random
def wander(enemy, grid):
"""Move randomly to an adjacent walkable tile."""
ex, ey = int(enemy.x), int(enemy.y)
# Get valid adjacent tiles
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
random.shuffle(directions)
for dx, dy in directions:
new_x, new_y = ex + dx, ey + dy
if is_walkable(grid, new_x, new_y) and not is_occupied(new_x, new_y):
enemy.x = new_x
enemy.y = new_y
return
# No valid moves - stay in place
def is_walkable(grid, x, y):
"""Check if a tile can be walked on."""
grid_w, grid_h = grid.grid_size
if x < 0 or x >= grid_w or y < 0 or y >= grid_h:
return False
return grid.at(x, y).walkable
def is_occupied(x, y, entities=None):
"""Check if a tile is occupied by another entity."""
if entities is None:
return False
for entity in entities:
if int(entity.x) == x and int(entity.y) == y:
return True
return False

View file

@ -1,11 +0,0 @@
"""McRogueFace - Basic Enemy AI (multi)
Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_multi.py
This code is extracted from the McRogueFace documentation and can be
run directly with: ./mcrogueface path/to/this/file.py
"""
# Filter to cardinal directions only
path = [p for p in path if abs(p[0] - ex) + abs(p[1] - ey) == 1]

View file

@ -1,14 +0,0 @@
"""McRogueFace - Basic Enemy AI (multi_2)
Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_multi_2.py
This code is extracted from the McRogueFace documentation and can be
run directly with: ./mcrogueface path/to/this/file.py
"""
def alert_nearby(x, y, radius, enemies):
for enemy in enemies:
dist = abs(enemy.entity.x - x) + abs(enemy.entity.y - y)
if dist <= radius and hasattr(enemy.ai, 'alert'):
enemy.ai.alert = True

View file

@ -1,82 +0,0 @@
"""McRogueFace - Melee Combat System (basic)
Documentation: https://mcrogueface.github.io/cookbook/combat_melee
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_basic.py
This code is extracted from the McRogueFace documentation and can be
run directly with: ./mcrogueface path/to/this/file.py
"""
class CombatLog:
"""Scrolling combat message log."""
def __init__(self, x, y, width, height, max_messages=10):
self.x = x
self.y = y
self.width = width
self.height = height
self.max_messages = max_messages
self.messages = []
self.captions = []
ui = mcrfpy.sceneUI(mcrfpy.currentScene())
# Background
self.frame = mcrfpy.Frame(x, y, width, height)
self.frame.fill_color = mcrfpy.Color(0, 0, 0, 180)
ui.append(self.frame)
def add_message(self, text, color=None):
"""Add a message to the log."""
if color is None:
color = mcrfpy.Color(200, 200, 200)
self.messages.append((text, color))
# Keep only recent messages
if len(self.messages) > self.max_messages:
self.messages.pop(0)
self._refresh_display()
def _refresh_display(self):
"""Redraw all messages."""
ui = mcrfpy.sceneUI(mcrfpy.currentScene())
# Remove old captions
for caption in self.captions:
try:
ui.remove(caption)
except:
pass
self.captions.clear()
# Create new captions
line_height = 18
for i, (text, color) in enumerate(self.messages):
caption = mcrfpy.Caption(text, self.x + 5, self.y + 5 + i * line_height)
caption.fill_color = color
ui.append(caption)
self.captions.append(caption)
def log_attack(self, attacker_name, defender_name, damage, killed=False, critical=False):
"""Log an attack event."""
if critical:
text = f"{attacker_name} CRITS {defender_name} for {damage}!"
color = mcrfpy.Color(255, 255, 0)
else:
text = f"{attacker_name} hits {defender_name} for {damage}."
color = mcrfpy.Color(200, 200, 200)
self.add_message(text, color)
if killed:
self.add_message(f"{defender_name} is defeated!", mcrfpy.Color(255, 100, 100))
# Global combat log
combat_log = None
def init_combat_log():
global combat_log
combat_log = CombatLog(10, 500, 400, 200)

View file

@ -1,15 +0,0 @@
"""McRogueFace - Melee Combat System (complete)
Documentation: https://mcrogueface.github.io/cookbook/combat_melee
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_complete.py
This code is extracted from the McRogueFace documentation and can be
run directly with: ./mcrogueface path/to/this/file.py
"""
def die_with_animation(entity):
# Play death animation
anim = mcrfpy.Animation("opacity", 0.0, 0.5, "linear")
anim.start(entity)
# Remove after animation
mcrfpy.setTimer("remove", lambda dt: remove_entity(entity), 500)

View file

@ -1,14 +0,0 @@
"""McRogueFace - Melee Combat System (complete_2)
Documentation: https://mcrogueface.github.io/cookbook/combat_melee
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_complete_2.py
This code is extracted from the McRogueFace documentation and can be
run directly with: ./mcrogueface path/to/this/file.py
"""
@dataclass
class AdvancedFighter(Fighter):
fire_resist: float = 0.0
ice_resist: float = 0.0
physical_resist: float = 0.0

Some files were not shown because too many files have changed in this diff Show more