diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 0dea84c..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,458 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Gitea-First Workflow - -**IMPORTANT**: This project uses Gitea for issue tracking, documentation, and project management. Always consult and update Gitea resources before and during development work. - -**Gitea Instance**: https://gamedev.ffwf.net/gitea/john/McRogueFace - -### Core Principles - -1. **Gitea is the Single Source of Truth** - - Issue tracker contains current tasks, bugs, and feature requests - - Wiki contains living documentation and architecture decisions - - Use Gitea MCP tools to query and update issues programmatically - -2. **Always Check Gitea First** - - Before starting work: Check open issues for related tasks or blockers - - When using `/roadmap` command: Query Gitea for up-to-date issue status - - When researching a feature: Search Gitea wiki and issues before grepping codebase - - When encountering a bug: Check if an issue already exists - -3. **Create Granular Issues** - - Break large features into separate, focused issues - - Each issue should address one specific problem or enhancement - - Tag issues appropriately: `[Bugfix]`, `[Major Feature]`, `[Minor Feature]`, etc. - - Link related issues using dependencies or blocking relationships - -4. **Document as You Go** - - When work on one issue interacts with another system: Add notes to related issues - - When discovering undocumented behavior: Create task to document it - - When documentation misleads you: Create task to correct or expand it - - When implementing a feature: Update the Gitea wiki if appropriate - -5. **Cross-Reference Everything** - - Commit messages should reference issue numbers (e.g., "Fixes #104", "Addresses #125") - - Issue comments should link to commits when work is done - - Wiki pages should reference relevant issues for implementation details - - Issues should link to each other when dependencies exist - -### Workflow Pattern - -``` -┌─────────────────────────────────────────────────────┐ -│ 1. Check Gitea Issues & Wiki │ -│ - Is there an existing issue for this? │ -│ - What's the current status? │ -│ - Are there related issues or blockers? │ -└─────────────────┬───────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────┐ -│ 2. Create Issues (if needed) │ -│ - Break work into granular tasks │ -│ - Tag appropriately │ -│ - Link dependencies │ -└─────────────────┬───────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────┐ -│ 3. Do the Work │ -│ - Implement/fix/document │ -│ - Write tests first (TDD) │ -│ - Add inline documentation │ -└─────────────────┬───────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────┐ -│ 4. Update Gitea │ -│ - Add notes to affected issues │ -│ - Create follow-up issues for discovered work │ -│ - Update wiki if architecture/APIs changed │ -│ - Add documentation correction tasks │ -└─────────────────┬───────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────┐ -│ 5. Commit & Reference │ -│ - Commit messages reference issue numbers │ -│ - Close issues or update status │ -│ - Add commit links to issue comments │ -└─────────────────────────────────────────────────────┘ -``` - -### Benefits of Gitea-First Approach - -- **Reduced Context Switching**: Check brief issue descriptions instead of re-reading entire codebase -- **Better Planning**: Issues provide roadmap; avoid duplicate or contradictory work -- **Living Documentation**: Wiki and issues stay current as work progresses -- **Historical Context**: Issue comments capture why decisions were made -- **Efficiency**: MCP tools allow programmatic access to project state - -### MCP Tools Available - -Claude Code has access to Gitea MCP tools for: -- `list_repo_issues` - Query current issues with filtering -- `get_issue` - Get detailed issue information -- `create_issue` - Create new issues programmatically -- `create_issue_comment` - Add comments to issues -- `edit_issue` - Update issue status, title, body -- `add_issue_labels` - Tag issues appropriately -- `add_issue_dependency` / `add_issue_blocking` - Link related issues -- Plus wiki, milestone, and label management tools - -Use these tools liberally to keep the project organized! - -## Build Commands - -```bash -# Build the project (compiles to ./build directory) -make - -# Or use the build script directly -./build.sh - -# Run the game -make run - -# Clean build artifacts -make clean - -# The executable and all assets are in ./build/ -cd build -./mcrogueface -``` - -## Project Architecture - -McRogueFace is a C++ game engine with Python scripting support, designed for creating roguelike games. The architecture consists of: - -### Core Engine (C++) -- **Entry Point**: `src/main.cpp` initializes the game engine -- **Scene System**: `Scene.h/cpp` manages game states -- **Entity System**: `UIEntity.h/cpp` provides game objects -- **Python Integration**: `McRFPy_API.h/cpp` exposes engine functionality to Python -- **UI Components**: `UIFrame`, `UICaption`, `UISprite`, `UIGrid` for rendering - -### Game Logic (Python) -- **Main Script**: `src/scripts/game.py` contains game initialization and scene setup -- **Entity System**: `src/scripts/cos_entities.py` implements game entities (Player, Enemy, Boulder, etc.) -- **Level Generation**: `src/scripts/cos_level.py` uses BSP for procedural dungeon generation -- **Tile System**: `src/scripts/cos_tiles.py` implements Wave Function Collapse for tile placement - -### Key Python API (`mcrfpy` module) -The C++ engine exposes these primary functions to Python: -- Scene Management: `createScene()`, `setScene()`, `sceneUI()` -- Entity Creation: `Entity()` with position and sprite properties -- Grid Management: `Grid()` for tilemap rendering -- Input Handling: `keypressScene()` for keyboard events -- Audio: `createSoundBuffer()`, `playSound()`, `setVolume()` -- Timers: `setTimer()`, `delTimer()` for event scheduling - -## Development Workflow - -### Running the Game -After building, the executable expects: -- `assets/` directory with sprites, fonts, and audio -- `scripts/` directory with Python game files -- Python 3.12 shared libraries in `./lib/` - -### Modifying Game Logic -- Game scripts are in `src/scripts/` -- Main game entry is `game.py` -- Entity behavior in `cos_entities.py` -- Level generation in `cos_level.py` - -### Adding New Features -1. C++ API additions go in `src/McRFPy_API.cpp` -2. Expose to Python using the existing binding pattern -3. Update Python scripts to use new functionality - -## Testing Game Changes - -Currently no automated test suite. Manual testing workflow: -1. Build with `make` -2. Run `make run` or `cd build && ./mcrogueface` -3. Test specific features through gameplay -4. Check console output for Python errors - -### Quick Testing Commands -```bash -# Test basic functionality -make test - -# Run in Python interactive mode -make python - -# Test headless mode -cd build -./mcrogueface --headless -c "import mcrfpy; print('Headless test')" -``` - -## Common Development Tasks - -### Compiling McRogueFace -```bash -# Standard build (to ./build directory) -make - -# Full rebuild -make clean && make - -# Manual CMake build -mkdir build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -make -j$(nproc) - -# The library path issue: if linking fails, check that libraries are in __lib/ -# CMakeLists.txt expects: link_directories(${CMAKE_SOURCE_DIR}/__lib) -``` - -### Running and Capturing Output -```bash -# Run with timeout and capture output -cd build -timeout 5 ./mcrogueface 2>&1 | tee output.log - -# Run in background and kill after delay -./mcrogueface > output.txt 2>&1 & PID=$!; sleep 3; kill $PID 2>/dev/null - -# Just capture first N lines (useful for crashes) -./mcrogueface 2>&1 | head -50 -``` - -### Debugging with GDB -```bash -# Interactive debugging -gdb ./mcrogueface -(gdb) run -(gdb) bt # backtrace after crash - -# Batch mode debugging (non-interactive) -gdb -batch -ex run -ex where -ex quit ./mcrogueface 2>&1 - -# Get just the backtrace after a crash -gdb -batch -ex "run" -ex "bt" ./mcrogueface 2>&1 | head -50 - -# Debug with specific commands -echo -e "run\nbt 5\nquit\ny" | gdb ./mcrogueface 2>&1 -``` - -### Testing Different Python Scripts -```bash -# The game automatically runs build/scripts/game.py on startup -# To test different behavior: - -# Option 1: Replace game.py temporarily -cd build -cp scripts/my_test_script.py scripts/game.py -./mcrogueface - -# Option 2: Backup original and test -mv scripts/game.py scripts/game.py.bak -cp my_test.py scripts/game.py -./mcrogueface -mv scripts/game.py.bak scripts/game.py - -# Option 3: For quick tests, create minimal game.py -echo 'import mcrfpy; print("Test"); mcrfpy.createScene("test")' > scripts/game.py -``` - -### Understanding Key Macros and Patterns - -#### RET_PY_INSTANCE Macro (UIDrawable.h) -This macro handles converting C++ UI objects to their Python equivalents: -```cpp -RET_PY_INSTANCE(target); -// Expands to a switch on target->derived_type() that: -// 1. Allocates the correct Python object type (Frame, Caption, Sprite, Grid) -// 2. Sets the shared_ptr data member -// 3. Returns the PyObject* -``` - -#### Collection Patterns -- `UICollection` wraps `std::vector>` -- `UIEntityCollection` wraps `std::list>` -- Different containers require different iteration code (vector vs list) - -#### Python Object Creation Patterns -```cpp -// Pattern 1: Using tp_alloc (most common) -auto o = (PyUIFrameObject*)type->tp_alloc(type, 0); -o->data = std::make_shared(); - -// Pattern 2: Getting type from module -auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"); -auto o = (PyUIEntityObject*)type->tp_alloc(type, 0); - -// Pattern 3: Direct shared_ptr assignment -iterObj->data = self->data; // Shares the C++ object -``` - -### Working Directory Structure -``` -build/ -├── mcrogueface # The executable -├── scripts/ -│ └── game.py # Auto-loaded Python script -├── assets/ # Copied from source during build -└── lib/ # Python libraries (copied from __lib/) -``` - -### Quick Iteration Tips -- Keep a test script ready for quick experiments -- Use `timeout` to auto-kill hanging processes -- The game expects a window manager; use Xvfb for headless testing -- Python errors go to stderr, game output to stdout -- Segfaults usually mean Python type initialization issues - -## Important Notes - -- The project uses SFML for graphics/audio and libtcod for roguelike utilities -- Python scripts are loaded at runtime from the `scripts/` directory -- Asset loading expects specific paths relative to the executable -- The game was created for 7DRL 2025 as "Crypt of Sokoban" -- Iterator implementations require careful handling of C++/Python boundaries - -## Testing Guidelines - -### Test-Driven Development -- **Always write tests first**: Create automation tests in `./tests/` for all bugs and new features -- **Practice TDD**: Write tests that fail to demonstrate the issue, then pass after the fix is applied -- **Close the loop**: Reproduce issue → change code → recompile → verify behavior change - -### Two Types of Tests - -#### 1. Direct Execution Tests (No Game Loop) -For tests that only need class initialization or direct code execution: -```python -# These tests can treat McRogueFace like a Python interpreter -import mcrfpy - -# Test code here -result = mcrfpy.some_function() -assert result == expected_value -print("PASS" if condition else "FAIL") -``` - -#### 2. Game Loop Tests (Timer-Based) -For tests requiring rendering, game state, or elapsed time: -```python -import mcrfpy -from mcrfpy import automation -import sys - -def run_test(runtime): - """Timer callback - runs after game loop starts""" - # Now rendering is active, screenshots will work - automation.screenshot("test_result.png") - - # Run your tests here - automation.click(100, 100) - - # Always exit at the end - print("PASS" if success else "FAIL") - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -# ... add UI elements ... - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) # 0.1 seconds -``` - -### Key Testing Principles -- **Timer callbacks are essential**: Screenshots and UI interactions only work after the render loop starts -- **Use automation API**: Always create and examine screenshots when visual feedback is required -- **Exit properly**: Call `sys.exit()` at the end of timer-based tests to prevent hanging -- **Headless mode**: Use `--exec` flag for automated testing: `./mcrogueface --headless --exec tests/my_test.py` - -### Example Test Pattern -```bash -# Run a test that requires game loop -./build/mcrogueface --headless --exec tests/issue_78_middle_click_test.py - -# The test will: -# 1. Set up the scene during script execution -# 2. Register a timer callback -# 3. Game loop starts -# 4. Timer fires after 100ms -# 5. Test runs with full rendering available -# 6. Test takes screenshots and validates behavior -# 7. Test calls sys.exit() to terminate -``` - -## Development Best Practices - -### Testing and Deployment -- **Keep tests in ./tests, not ./build/tests** - ./build gets shipped, and tests shouldn't be included - -## Documentation Guidelines - -### Inline C++ Documentation Format - -When adding new methods or modifying existing ones in C++ source files, use this documentation format in PyMethodDef arrays: - -```cpp -{"method_name", (PyCFunction)Class::method, METH_VARARGS | METH_KEYWORDS, - "method_name(arg1: type, arg2: type = default) -> return_type\n\n" - "Brief description of what the method does.\n\n" - "Args:\n" - " arg1: Description of first argument\n" - " arg2: Description of second argument (default: value)\n\n" - "Returns:\n" - " Description of return value\n\n" - "Example:\n" - " result = obj.method_name(value1, value2)\n\n" - "Note:\n" - " Any important notes or caveats"}, -``` - -For properties in PyGetSetDef arrays: -```cpp -{"property_name", (getter)getter_func, (setter)setter_func, - "Brief description of the property. " - "Additional details about valid values, side effects, etc.", NULL}, -``` - -### Regenerating Documentation - -After modifying C++ inline documentation: - -1. **Rebuild the project**: `make -j$(nproc)` - -2. **Generate stub files** (for IDE support): - ```bash - ./build/mcrogueface --exec generate_stubs.py - ``` - -3. **Generate dynamic documentation** (recommended): - ```bash - ./build/mcrogueface --exec generate_dynamic_docs.py - ``` - This creates: - - `docs/api_reference_dynamic.html` - - `docs/API_REFERENCE_DYNAMIC.md` - -4. **Update hardcoded documentation** (if still using old system): - - `generate_complete_api_docs.py` - Update method dictionaries - - `generate_complete_markdown_docs.py` - Update method dictionaries - -### Important Notes - -- **McRogueFace as Python interpreter**: Documentation scripts MUST be run using McRogueFace itself, not system Python -- **Use --exec flag**: `./build/mcrogueface --exec script.py` or `--headless --exec` for CI/automation -- **Dynamic is better**: The new `generate_dynamic_docs.py` extracts documentation directly from compiled module -- **Keep docstrings consistent**: Follow the format above for automatic parsing - -### Documentation Pipeline Architecture - -1. **C++ Source** → PyMethodDef/PyGetSetDef arrays with docstrings -2. **Compilation** → Docstrings embedded in compiled module -3. **Introspection** → Scripts use `dir()`, `getattr()`, `__doc__` to extract -4. **Generation** → HTML/Markdown/Stub files created - -The documentation is only as good as the C++ inline docstrings! \ No newline at end of file diff --git a/README.md b/README.md index 9d74e5a..6210792 100644 --- a/README.md +++ b/README.md @@ -57,28 +57,18 @@ mcrfpy.setScene("intro") ## Documentation -### 📚 Developer Documentation +### 📚 Full Documentation Site -For comprehensive documentation about systems, architecture, and development workflows: +For comprehensive documentation, tutorials, and API reference, visit: +**[https://mcrogueface.github.io](https://mcrogueface.github.io)** -**[Project Wiki](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki)** +The documentation site includes: -Key wiki pages: - -- **[Home](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Home)** - Documentation hub with multiple entry points -- **[Grid System](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Grid-System)** - Three-layer grid architecture -- **[Python Binding System](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Python-Binding-System)** - C++/Python integration -- **[Performance and Profiling](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Performance-and-Profiling)** - Optimization tools -- **[Adding Python Bindings](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Adding-Python-Bindings)** - Step-by-step binding guide -- **[Issue Roadmap](https://gamedev.ffwf.net/gitea/john/McRogueFace/wiki/Issue-Roadmap)** - All 46 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 -- **[roguelike_tutorial/](roguelike_tutorial/)** - Complete roguelike tutorial implementations +- **[Quickstart Guide](https://mcrogueface.github.io/quickstart)** - Get running in 5 minutes +- **[McRogueFace Does The Entire Roguelike Tutorial](https://mcrogueface.github.io/tutorials)** - Step-by-step game building +- **[Complete API Reference](https://mcrogueface.github.io/api)** - Every function documented +- **[Cookbook](https://mcrogueface.github.io/cookbook)** - Ready-to-use code recipes +- **[C++ Extension Guide](https://mcrogueface.github.io/extending-cpp)** - For C++ developers: Add engine features ## Build Requirements @@ -124,15 +114,7 @@ If you are writing a game in Python using McRogueFace, you only need to rename a 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 [Gitea Issues](https://gamedev.ffwf.net/gitea/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://gamedev.ffwf.net/gitea/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 diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 8d02b12..0000000 --- a/ROADMAP.md +++ /dev/null @@ -1,222 +0,0 @@ -# McRogueFace - Development Roadmap - -## Project Status - -**Current State**: Active development - C++ game engine with Python scripting -**Latest Release**: Alpha 0.1 -**Issue Tracking**: See [Gitea Issues](https://gamedev.ffwf.net/gitea/john/McRogueFace/issues) for current tasks and bugs - ---- - -## 🎯 Strategic Vision - -### Engine Philosophy - -- **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 - ---- - -## 🏗️ Architecture Decisions - -### Three-Layer Grid Architecture -Following successful roguelike patterns (Caves of Qud, Cogmind, DCSS): - -1. **Visual Layer** (UIGridPoint) - Sprites, colors, animations -2. **World State Layer** (TCODMap) - Walkability, transparency, physics -3. **Entity Perspective Layer** (UIGridPointState) - Per-entity FOV, knowledge - -### Performance Architecture -Critical for large maps (1000x1000): - -- **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 - ---- - -## 🚀 Development Phases - -For detailed task tracking and current priorities, see the [Gitea issue tracker](https://gamedev.ffwf.net/gitea/john/McRogueFace/issues). - -### Phase 1: Foundation Stabilization ✅ -**Status**: Complete -**Key Issues**: #7 (Safe Constructors), #71 (Base Class), #87 (Visibility), #88 (Opacity) - -### Phase 2: Constructor & API Polish ✅ -**Status**: Complete -**Key Features**: Pythonic API, tuple support, standardized defaults - -### Phase 3: Entity Lifecycle Management ✅ -**Status**: Complete -**Key Issues**: #30 (Entity.die()), #93 (Vector methods), #94 (Color helpers), #103 (Timer objects) - -### Phase 4: Visibility & Performance ✅ -**Status**: Complete -**Key Features**: AABB culling, name system, profiling tools - -### Phase 5: Window/Scene Architecture ✅ -**Status**: Complete -**Key Issues**: #34 (Window object), #61 (Scene object), #1 (Resize events), #105 (Scene transitions) - -### Phase 6: Rendering Revolution ✅ -**Status**: Complete -**Key Issues**: #50 (Grid backgrounds), #6 (RenderTexture), #8 (Viewport rendering) - -### Phase 7: Documentation & Distribution -**Status**: In Progress -**Key Issues**: #85 (Docstrings), #86 (Parameter docs), #108 (Type stubs), #97 (API docs) - -See [current open issues](https://gamedev.ffwf.net/gitea/john/McRogueFace/issues?state=open) for active work. - ---- - -## 🔮 Future Vision: Pure Python Extension Architecture - -### Concept: McRogueFace as a Traditional Python Package -**Status**: 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 - -### 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. - ---- - -## 📋 Major Feature Areas - -For current status and detailed tasks, see the corresponding Gitea issue labels: - -### Core Systems -- **UI/Rendering System**: Issues tagged `[Major Feature]` related to rendering -- **Grid/Entity System**: Pathfinding, FOV, entity management -- **Animation System**: Property animation, easing functions, callbacks -- **Scene/Window Management**: Scene lifecycle, transitions, viewport - -### Performance Optimization -- **#115**: SpatialHash for 10,000+ entities -- **#116**: Dirty flag system -- **#113**: Batch operations for NumPy-style access -- **#117**: Memory pool for entities - -### Advanced Features -- **#118**: Scene as Drawable (scenes can be drawn/animated) -- **#122**: Parent-Child UI System -- **#123**: Grid Subgrid System (256x256 chunks) -- **#124**: Grid Point Animation -- **#106**: Shader support -- **#107**: Particle system - -### Documentation -- **#92**: Inline C++ documentation system -- **#91**: Python type stub files (.pyi) -- **#97**: Automated API documentation extraction -- **#126**: Generate perfectly consistent Python interface - ---- - -## 📚 Resources - -- **Issue Tracker**: [Gitea Issues](https://gamedev.ffwf.net/gitea/john/McRogueFace/issues) -- **Source Code**: [Gitea Repository](https://gamedev.ffwf.net/gitea/john/McRogueFace) -- **Documentation**: See `CLAUDE.md` for build instructions and development guide -- **Tutorial**: See `roguelike_tutorial/` for implementation examples -- **Workflow**: See "Gitea-First Workflow" section in `CLAUDE.md` for issue management best practices - ---- - -## 🔄 Development Workflow - -**Gitea is the Single Source of Truth** for this project. Before starting any work: - -1. **Check Gitea Issues** for existing tasks, bugs, or related work -2. **Create granular issues** for new features or problems -3. **Update issues** when work affects other systems -4. **Document discoveries** - if something is undocumented or misleading, create a task to fix it -5. **Cross-reference commits** with issue numbers (e.g., "Fixes #104") - -See the "Gitea-First Workflow" section in `CLAUDE.md` for detailed guidelines on efficient development practices using the Gitea MCP tools. - ---- - -*For current priorities, task tracking, and bug reports, please use the [Gitea issue tracker](https://gamedev.ffwf.net/gitea/john/McRogueFace/issues).* diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index e4e9035..43b9c03 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -42,11 +42,8 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg) updateViewport(); scene = "uitest"; scenes["uitest"] = new UITestScene(this); - + McRFPy_API::game = this; - - // Initialize profiler overlay - profilerOverlay = new ProfilerOverlay(Resources::font); // Only load game.py if no custom script/command/module/exec is specified bool should_load_game = config.script_path.empty() && @@ -88,7 +85,6 @@ GameEngine::~GameEngine() for (auto& [name, scene] : scenes) { delete scene; } - delete profilerOverlay; } void GameEngine::cleanup() @@ -203,14 +199,10 @@ void GameEngine::run() testTimers(); // Update Python scenes - { - ScopedTimer pyTimer(metrics.pythonScriptTime); - McRFPy_API::updatePythonScenes(frameTime); - } + McRFPy_API::updatePythonScenes(frameTime); // Update animations (only if frameTime is valid) if (frameTime > 0.0f && frameTime < 1.0f) { - ScopedTimer animTimer(metrics.animationTime); AnimationManager::getInstance().update(frameTime); } @@ -248,12 +240,6 @@ void GameEngine::run() currentScene()->render(); } - // Update and render profiler overlay (if enabled) - if (profilerOverlay && !headless) { - profilerOverlay->update(metrics); - profilerOverlay->render(*render_target); - } - // Display the frame if (headless) { headless_renderer->display(); @@ -344,14 +330,6 @@ void GameEngine::processEvent(const sf::Event& event) int actionCode = 0; if (event.type == sf::Event::Closed) { running = false; return; } - - // Handle F3 for profiler overlay toggle - if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::F3) { - if (profilerOverlay) { - profilerOverlay->toggle(); - } - return; - } // Handle window resize events else if (event.type == sf::Event::Resized) { // Update the viewport to handle the new window size diff --git a/src/GameEngine.h b/src/GameEngine.h index 4721bb8..30ed619 100644 --- a/src/GameEngine.h +++ b/src/GameEngine.h @@ -9,16 +9,11 @@ #include "McRogueFaceConfig.h" #include "HeadlessRenderer.h" #include "SceneTransition.h" -#include "Profiler.h" #include -#include class GameEngine { public: - // Forward declare nested class so private section can use it - class ProfilerOverlay; - // Viewport modes (moved here so private section can use it) enum class ViewportMode { Center, // 1:1 pixels, viewport centered in window @@ -56,12 +51,7 @@ private: sf::Vector2u gameResolution{1024, 768}; // Fixed game resolution sf::View gameView; // View for the game content ViewportMode viewportMode = ViewportMode::Fit; - - // Profiling overlay - bool showProfilerOverlay = false; // F3 key toggles this - int overlayUpdateCounter = 0; // Only update overlay every N frames - ProfilerOverlay* profilerOverlay = nullptr; // The actual overlay renderer - + void updateViewport(); void testTimers(); @@ -79,29 +69,17 @@ public: int drawCalls = 0; // Draw calls per frame int uiElements = 0; // Number of UI elements rendered int visibleElements = 0; // Number of visible elements - - // Detailed timing breakdowns (added for profiling system) - float gridRenderTime = 0.0f; // Time spent rendering grids (ms) - float entityRenderTime = 0.0f; // Time spent rendering entities (ms) - float fovOverlayTime = 0.0f; // Time spent rendering FOV overlays (ms) - float pythonScriptTime = 0.0f; // Time spent in Python callbacks (ms) - float animationTime = 0.0f; // Time spent updating animations (ms) - - // Grid-specific metrics - int gridCellsRendered = 0; // Number of grid cells drawn this frame - int entitiesRendered = 0; // Number of entities drawn this frame - int totalEntities = 0; // Total entities in scene - + // Frame time history for averaging static constexpr int HISTORY_SIZE = 60; float frameTimeHistory[HISTORY_SIZE] = {0}; int historyIndex = 0; - + void updateFrameTime(float deltaMs) { frameTime = deltaMs; frameTimeHistory[historyIndex] = deltaMs; historyIndex = (historyIndex + 1) % HISTORY_SIZE; - + // Calculate average float sum = 0.0f; for (int i = 0; i < HISTORY_SIZE; ++i) { @@ -110,26 +88,13 @@ public: avgFrameTime = sum / HISTORY_SIZE; fps = avgFrameTime > 0 ? static_cast(1000.0f / avgFrameTime) : 0; } - + void resetPerFrame() { drawCalls = 0; uiElements = 0; visibleElements = 0; - - // Reset per-frame timing metrics - gridRenderTime = 0.0f; - entityRenderTime = 0.0f; - fovOverlayTime = 0.0f; - pythonScriptTime = 0.0f; - animationTime = 0.0f; - - // Reset per-frame counters - gridCellsRendered = 0; - entitiesRendered = 0; - totalEntities = 0; } } metrics; - GameEngine(); GameEngine(const McRogueFaceConfig& cfg); ~GameEngine(); @@ -179,30 +144,5 @@ public: sf::Music music; sf::Sound sfx; std::shared_ptr>> scene_ui(std::string scene); - -}; - -/** - * @brief Visual overlay that displays real-time profiling metrics - */ -class GameEngine::ProfilerOverlay { -private: - sf::Font& font; - sf::Text text; - sf::RectangleShape background; - bool visible; - int updateInterval; - int frameCounter; - - sf::Color getPerformanceColor(float frameTimeMs); - std::string formatFloat(float value, int precision = 1); - std::string formatPercentage(float part, float total); - -public: - ProfilerOverlay(sf::Font& fontRef); - void toggle(); - void setVisible(bool vis); - bool isVisible() const; - void update(const ProfilingMetrics& metrics); - void render(sf::RenderTarget& target); + }; diff --git a/src/Profiler.cpp b/src/Profiler.cpp deleted file mode 100644 index e19e886..0000000 --- a/src/Profiler.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "Profiler.h" -#include - -ProfilingLogger::ProfilingLogger() - : headers_written(false) -{ -} - -ProfilingLogger::~ProfilingLogger() { - close(); -} - -bool ProfilingLogger::open(const std::string& filename, const std::vector& columns) { - column_names = columns; - file.open(filename); - - if (!file.is_open()) { - std::cerr << "Failed to open profiling log file: " << filename << std::endl; - return false; - } - - // Write CSV header - for (size_t i = 0; i < columns.size(); ++i) { - file << columns[i]; - if (i < columns.size() - 1) { - file << ","; - } - } - file << "\n"; - file.flush(); - - headers_written = true; - return true; -} - -void ProfilingLogger::writeRow(const std::vector& values) { - if (!file.is_open()) { - return; - } - - if (values.size() != column_names.size()) { - std::cerr << "ProfilingLogger: value count (" << values.size() - << ") doesn't match column count (" << column_names.size() << ")" << std::endl; - return; - } - - for (size_t i = 0; i < values.size(); ++i) { - file << values[i]; - if (i < values.size() - 1) { - file << ","; - } - } - file << "\n"; -} - -void ProfilingLogger::close() { - if (file.is_open()) { - file.flush(); - file.close(); - } -} diff --git a/src/Profiler.h b/src/Profiler.h deleted file mode 100644 index fefcc08..0000000 --- a/src/Profiler.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -/** - * @brief Simple RAII-based profiling timer for measuring code execution time - * - * Usage: - * float timing = 0.0f; - * { - * ScopedTimer timer(timing); - * // ... code to profile ... - * } // timing now contains elapsed milliseconds - */ -class ScopedTimer { -private: - std::chrono::high_resolution_clock::time_point start; - float& target_ms; - -public: - /** - * @brief Construct a new Scoped Timer and start timing - * @param target Reference to float that will receive elapsed time in milliseconds - */ - explicit ScopedTimer(float& target) - : target_ms(target) - { - start = std::chrono::high_resolution_clock::now(); - } - - /** - * @brief Destructor automatically records elapsed time - */ - ~ScopedTimer() { - auto end = std::chrono::high_resolution_clock::now(); - target_ms = std::chrono::duration(end - start).count(); - } - - // Prevent copying - ScopedTimer(const ScopedTimer&) = delete; - ScopedTimer& operator=(const ScopedTimer&) = delete; -}; - -/** - * @brief Accumulating timer that adds elapsed time to existing value - * - * Useful for measuring total time across multiple calls in a single frame - */ -class AccumulatingTimer { -private: - std::chrono::high_resolution_clock::time_point start; - float& target_ms; - -public: - explicit AccumulatingTimer(float& target) - : target_ms(target) - { - start = std::chrono::high_resolution_clock::now(); - } - - ~AccumulatingTimer() { - auto end = std::chrono::high_resolution_clock::now(); - target_ms += std::chrono::duration(end - start).count(); - } - - AccumulatingTimer(const AccumulatingTimer&) = delete; - AccumulatingTimer& operator=(const AccumulatingTimer&) = delete; -}; - -/** - * @brief CSV profiling data logger for batch analysis - * - * Writes profiling data to CSV file for later analysis with Python/pandas/Excel - */ -class ProfilingLogger { -private: - std::ofstream file; - bool headers_written; - std::vector column_names; - -public: - ProfilingLogger(); - ~ProfilingLogger(); - - /** - * @brief Open a CSV file for writing profiling data - * @param filename Path to CSV file - * @param columns Column names for the CSV header - * @return true if file opened successfully - */ - bool open(const std::string& filename, const std::vector& columns); - - /** - * @brief Write a row of profiling data - * @param values Data values (must match column count) - */ - void writeRow(const std::vector& values); - - /** - * @brief Close the file and flush data - */ - void close(); - - /** - * @brief Check if logger is ready to write - */ - bool isOpen() const { return file.is_open(); } -}; diff --git a/src/ProfilerOverlay.cpp b/src/ProfilerOverlay.cpp deleted file mode 100644 index 7860b49..0000000 --- a/src/ProfilerOverlay.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include "GameEngine.h" -#include -#include - -GameEngine::ProfilerOverlay::ProfilerOverlay(sf::Font& fontRef) - : font(fontRef), visible(false), updateInterval(10), frameCounter(0) -{ - text.setFont(font); - text.setCharacterSize(14); - text.setFillColor(sf::Color::White); - text.setPosition(10.0f, 10.0f); - - // Semi-transparent dark background - background.setFillColor(sf::Color(0, 0, 0, 180)); - background.setPosition(5.0f, 5.0f); -} - -void GameEngine::ProfilerOverlay::toggle() { - visible = !visible; -} - -void GameEngine::ProfilerOverlay::setVisible(bool vis) { - visible = vis; -} - -bool GameEngine::ProfilerOverlay::isVisible() const { - return visible; -} - -sf::Color GameEngine::ProfilerOverlay::getPerformanceColor(float frameTimeMs) { - if (frameTimeMs < 16.6f) { - return sf::Color::Green; // 60+ FPS - } else if (frameTimeMs < 33.3f) { - return sf::Color::Yellow; // 30-60 FPS - } else { - return sf::Color::Red; // <30 FPS - } -} - -std::string GameEngine::ProfilerOverlay::formatFloat(float value, int precision) { - std::stringstream ss; - ss << std::fixed << std::setprecision(precision) << value; - return ss.str(); -} - -std::string GameEngine::ProfilerOverlay::formatPercentage(float part, float total) { - if (total <= 0.0f) return "0%"; - float pct = (part / total) * 100.0f; - return formatFloat(pct, 0) + "%"; -} - -void GameEngine::ProfilerOverlay::update(const ProfilingMetrics& metrics) { - if (!visible) return; - - // Only update text every N frames to reduce overhead - frameCounter++; - if (frameCounter < updateInterval) { - return; - } - frameCounter = 0; - - std::stringstream ss; - ss << "McRogueFace Performance Monitor\n"; - ss << "================================\n"; - - // Frame time and FPS - float frameMs = metrics.avgFrameTime; - ss << "FPS: " << metrics.fps << " (" << formatFloat(frameMs, 1) << "ms/frame)\n"; - - // Performance warning - if (frameMs > 33.3f) { - ss << "WARNING: Frame time exceeds 30 FPS target!\n"; - } - - ss << "\n"; - - // Timing breakdown - ss << "Frame Time Breakdown:\n"; - ss << " Grid Render: " << formatFloat(metrics.gridRenderTime, 1) << "ms (" - << formatPercentage(metrics.gridRenderTime, frameMs) << ")\n"; - ss << " Cells: " << metrics.gridCellsRendered << " rendered\n"; - ss << " Entities: " << metrics.entitiesRendered << " / " << metrics.totalEntities << " drawn\n"; - - if (metrics.fovOverlayTime > 0.01f) { - ss << " FOV Overlay: " << formatFloat(metrics.fovOverlayTime, 1) << "ms\n"; - } - - if (metrics.entityRenderTime > 0.01f) { - ss << " Entity Render: " << formatFloat(metrics.entityRenderTime, 1) << "ms (" - << formatPercentage(metrics.entityRenderTime, frameMs) << ")\n"; - } - - if (metrics.pythonScriptTime > 0.01f) { - ss << " Python: " << formatFloat(metrics.pythonScriptTime, 1) << "ms (" - << formatPercentage(metrics.pythonScriptTime, frameMs) << ")\n"; - } - - if (metrics.animationTime > 0.01f) { - ss << " Animations: " << formatFloat(metrics.animationTime, 1) << "ms (" - << formatPercentage(metrics.animationTime, frameMs) << ")\n"; - } - - ss << "\n"; - - // Other metrics - ss << "Draw Calls: " << metrics.drawCalls << "\n"; - ss << "UI Elements: " << metrics.uiElements << " (" << metrics.visibleElements << " visible)\n"; - - // Calculate unaccounted time - float accountedTime = metrics.gridRenderTime + metrics.entityRenderTime + - metrics.pythonScriptTime + metrics.animationTime; - float unaccountedTime = frameMs - accountedTime; - - if (unaccountedTime > 1.0f) { - ss << "\n"; - ss << "Other: " << formatFloat(unaccountedTime, 1) << "ms (" - << formatPercentage(unaccountedTime, frameMs) << ")\n"; - } - - ss << "\n"; - ss << "Press F3 to hide this overlay"; - - text.setString(ss.str()); - - // Update background size to fit text - sf::FloatRect textBounds = text.getLocalBounds(); - background.setSize(sf::Vector2f(textBounds.width + 20.0f, textBounds.height + 20.0f)); -} - -void GameEngine::ProfilerOverlay::render(sf::RenderTarget& target) { - if (!visible) return; - - target.draw(background); - target.draw(text); -} diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index b07e596..dafc6f8 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -3,7 +3,6 @@ #include "McRFPy_API.h" #include "PythonObjectCache.h" #include "UIEntity.h" -#include "Profiler.h" #include // UIDrawable methods now in UIBase.h @@ -96,14 +95,11 @@ void UIGrid::update() {} void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) { - // Profile total grid rendering time - ScopedTimer gridTimer(Resources::game->metrics.gridRenderTime); - // Check visibility if (!visible) return; - + // TODO: Apply opacity to output sprite - + output.setPosition(box.getPosition() + offset); // output sprite can move; update position when drawing // output size can change; update size when drawing output.setTextureRect( @@ -139,12 +135,11 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) if (y_limit > grid_y) y_limit = grid_y; // base layer - bottom color, tile sprite ("ground") - int cellsRendered = 0; for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0); - x < x_limit; //x < view_width; + x < x_limit; //x < view_width; x+=1) { - //for (float y = (top_edge >= 0 ? top_edge : 0); + //for (float y = (top_edge >= 0 ? top_edge : 0); for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0); y < y_limit; //y < view_height; y+=1) @@ -168,53 +163,35 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);; renderTexture.draw(sprite); } - - cellsRendered++; } } - // Record how many cells were rendered - Resources::game->metrics.gridCellsRendered += cellsRendered; - // middle layer - entities // disabling entity rendering until I can render their UISprite inside the rendertexture (not directly to window) - { - ScopedTimer entityTimer(Resources::game->metrics.entityRenderTime); - int entitiesRendered = 0; - int totalEntities = entities->size(); - - for (auto e : *entities) { - // Skip out-of-bounds entities for performance - // Check if entity is within visible bounds (with 1 cell margin for partially visible entities) - if (e->position.x < left_edge - 1 || e->position.x >= left_edge + width_sq + 1 || - e->position.y < top_edge - 1 || e->position.y >= top_edge + height_sq + 1) { - continue; // Skip this entity as it's not visible - } - - //auto drawent = e->cGrid->indexsprite.drawable(); - auto& drawent = e->sprite; - //drawent.setScale(zoom, zoom); - drawent.setScale(sf::Vector2f(zoom, zoom)); - auto pixel_pos = sf::Vector2f( - (e->position.x*cell_width - left_spritepixels) * zoom, - (e->position.y*cell_height - top_spritepixels) * zoom ); - //drawent.setPosition(pixel_pos); - //renderTexture.draw(drawent); - drawent.render(pixel_pos, renderTexture); - - entitiesRendered++; + for (auto e : *entities) { + // Skip out-of-bounds entities for performance + // Check if entity is within visible bounds (with 1 cell margin for partially visible entities) + if (e->position.x < left_edge - 1 || e->position.x >= left_edge + width_sq + 1 || + e->position.y < top_edge - 1 || e->position.y >= top_edge + height_sq + 1) { + continue; // Skip this entity as it's not visible } - - // Record entity rendering stats - Resources::game->metrics.entitiesRendered += entitiesRendered; - Resources::game->metrics.totalEntities += totalEntities; + + //auto drawent = e->cGrid->indexsprite.drawable(); + auto& drawent = e->sprite; + //drawent.setScale(zoom, zoom); + drawent.setScale(sf::Vector2f(zoom, zoom)); + auto pixel_pos = sf::Vector2f( + (e->position.x*cell_width - left_spritepixels) * zoom, + (e->position.y*cell_height - top_spritepixels) * zoom ); + //drawent.setPosition(pixel_pos); + //renderTexture.draw(drawent); + drawent.render(pixel_pos, renderTexture); } // top layer - opacity for discovered / visible status based on perspective // Only render visibility overlay if perspective is enabled if (perspective_enabled) { - ScopedTimer fovTimer(Resources::game->metrics.fovOverlayTime); auto entity = perspective_entity.lock(); // Create rectangle for overlays diff --git a/tests/benchmark_moving_entities.py b/tests/benchmark_moving_entities.py deleted file mode 100644 index 6c0fb76..0000000 --- a/tests/benchmark_moving_entities.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -Benchmark: Moving Entities Performance Test - -This benchmark measures McRogueFace's performance with 50 randomly moving -entities on a 100x100 grid. - -Expected results: -- Should maintain 60 FPS -- Entity render time should be <3ms -- Grid render time will be higher due to constant updates (no dirty flag benefit) - -Usage: - ./build/mcrogueface --exec tests/benchmark_moving_entities.py - -Press F3 to toggle performance overlay -Press ESC to exit -""" - -import mcrfpy -import sys -import random - -# Create the benchmark scene -mcrfpy.createScene("benchmark") -mcrfpy.setScene("benchmark") - -# Get scene UI -ui = mcrfpy.sceneUI("benchmark") - -# Create a 100x100 grid -grid = mcrfpy.Grid( - grid_size=(100, 100), - pos=(0, 0), - size=(1024, 768) -) - -# Simple floor pattern -for x in range(100): - for y in range(100): - cell = grid.at((x, y)) - cell.tilesprite = 0 - cell.color = (40, 40, 40, 255) - -# Create 50 entities with random positions and velocities -entities = [] -ENTITY_COUNT = 50 - -for i in range(ENTITY_COUNT): - entity = mcrfpy.Entity( - grid_pos=(random.randint(0, 99), random.randint(0, 99)), - sprite_index=random.randint(10, 20) # Use varied sprites - ) - - # Give each entity a random velocity - entity.velocity_x = random.uniform(-0.5, 0.5) - entity.velocity_y = random.uniform(-0.5, 0.5) - - grid.entities.append(entity) - entities.append(entity) - -ui.append(grid) - -# Instructions caption -instructions = mcrfpy.Caption( - text=f"Moving Entities Benchmark ({ENTITY_COUNT} entities)\n" - "Press F3 for performance overlay\n" - "Press ESC to exit\n" - "Goal: 60 FPS with entities moving", - pos=(10, 10), - fill_color=(255, 255, 0, 255) -) -ui.append(instructions) - -# Benchmark info -print("=" * 60) -print("MOVING ENTITIES BENCHMARK") -print("=" * 60) -print(f"Entity count: {ENTITY_COUNT}") -print("Grid size: 100x100 cells") -print("Expected FPS: 60") -print("") -print("Entities move randomly and bounce off walls.") -print("This tests entity rendering performance and position updates.") -print("") -print("Press F3 in-game to see real-time performance metrics.") -print("=" * 60) - -# Exit handler -def handle_key(key, state): - if key == "Escape" and state: - print("\nBenchmark ended by user") - sys.exit(0) - -mcrfpy.keypressScene(handle_key) - -# Update entity positions -def update_entities(ms): - dt = ms / 1000.0 # Convert to seconds - - for entity in entities: - # Update position - new_x = entity.x + entity.velocity_x - new_y = entity.y + entity.velocity_y - - # Bounce off walls - if new_x < 0 or new_x >= 100: - entity.velocity_x = -entity.velocity_x - new_x = max(0, min(99, new_x)) - - if new_y < 0 or new_y >= 100: - entity.velocity_y = -entity.velocity_y - new_y = max(0, min(99, new_y)) - - # Update entity position - entity.x = new_x - entity.y = new_y - -# Run movement update every frame (16ms) -mcrfpy.setTimer("movement", update_entities, 16) - -# Benchmark statistics -frame_count = 0 -start_time = None - -def benchmark_timer(ms): - global frame_count, start_time - - if start_time is None: - import time - start_time = time.time() - - frame_count += 1 - - # After 10 seconds, print summary - import time - elapsed = time.time() - start_time - - if elapsed >= 10.0: - print("\n" + "=" * 60) - print("BENCHMARK COMPLETE") - print("=" * 60) - print(f"Frames rendered: {frame_count}") - print(f"Time elapsed: {elapsed:.2f}s") - print(f"Average FPS: {frame_count / elapsed:.1f}") - print(f"Entities: {ENTITY_COUNT}") - print("") - print("Check profiler overlay (F3) for detailed timing breakdown.") - print("Entity render time and total frame time are key metrics.") - print("=" * 60) - # Don't exit - let user review - -mcrfpy.setTimer("benchmark", benchmark_timer, 100) diff --git a/tests/benchmark_static_grid.py b/tests/benchmark_static_grid.py deleted file mode 100644 index 5307232..0000000 --- a/tests/benchmark_static_grid.py +++ /dev/null @@ -1,122 +0,0 @@ -""" -Benchmark: Static Grid Performance Test - -This benchmark measures McRogueFace's grid rendering performance with a static -100x100 grid. The goal is 60 FPS with minimal CPU usage. - -Expected results: -- 60 FPS (16.6ms per frame) -- Grid render time should be <2ms after dirty flag optimization -- Currently will be higher (likely 8-12ms) - this establishes baseline - -Usage: - ./build/mcrogueface --exec tests/benchmark_static_grid.py - -Press F3 to toggle performance overlay -Press ESC to exit -""" - -import mcrfpy -import sys - -# Create the benchmark scene -mcrfpy.createScene("benchmark") -mcrfpy.setScene("benchmark") - -# Get scene UI -ui = mcrfpy.sceneUI("benchmark") - -# Create a 100x100 grid with default texture -grid = mcrfpy.Grid( - grid_size=(100, 100), - pos=(0, 0), - size=(1024, 768) -) - -# Fill grid with varied tile patterns to ensure realistic rendering -for x in range(100): - for y in range(100): - cell = grid.at((x, y)) - # Checkerboard pattern with different sprites - if (x + y) % 2 == 0: - cell.tilesprite = 0 - cell.color = (50, 50, 50, 255) - else: - cell.tilesprite = 1 - cell.color = (70, 70, 70, 255) - - # Add some variation - if x % 10 == 0 or y % 10 == 0: - cell.tilesprite = 2 - cell.color = (100, 100, 100, 255) - -# Add grid to scene -ui.append(grid) - -# Instructions caption -instructions = mcrfpy.Caption( - text="Static Grid Benchmark (100x100)\n" - "Press F3 for performance overlay\n" - "Press ESC to exit\n" - "Goal: 60 FPS with low grid render time", - pos=(10, 10), - fill_color=(255, 255, 0, 255) -) -ui.append(instructions) - -# Benchmark info -print("=" * 60) -print("STATIC GRID BENCHMARK") -print("=" * 60) -print("Grid size: 100x100 cells") -print("Expected FPS: 60") -print("Tiles rendered: ~1024 visible cells per frame") -print("") -print("This benchmark establishes baseline grid rendering performance.") -print("After dirty flag optimization, grid render time should drop") -print("significantly for static content.") -print("") -print("Press F3 in-game to see real-time performance metrics.") -print("=" * 60) - -# Exit handler -def handle_key(key, state): - if key == "Escape" and state: - print("\nBenchmark ended by user") - sys.exit(0) - -mcrfpy.keypressScene(handle_key) - -# Run for 10 seconds then provide summary -frame_count = 0 -start_time = None - -def benchmark_timer(ms): - global frame_count, start_time - - if start_time is None: - import time - start_time = time.time() - - frame_count += 1 - - # After 10 seconds, print summary and exit - import time - elapsed = time.time() - start_time - - if elapsed >= 10.0: - print("\n" + "=" * 60) - print("BENCHMARK COMPLETE") - print("=" * 60) - print(f"Frames rendered: {frame_count}") - print(f"Time elapsed: {elapsed:.2f}s") - print(f"Average FPS: {frame_count / elapsed:.1f}") - print("") - print("Check profiler overlay (F3) for detailed timing breakdown.") - print("Grid render time is the key metric for optimization.") - print("=" * 60) - # Don't exit automatically - let user review with F3 - # sys.exit(0) - -# Update every 100ms -mcrfpy.setTimer("benchmark", benchmark_timer, 100)