diff --git a/Proposal-Next-Gen-Grid-Entity-System.-.md b/Proposal-Next-Gen-Grid-Entity-System.-.md new file mode 100644 index 0000000..2441299 --- /dev/null +++ b/Proposal-Next-Gen-Grid-Entity-System.-.md @@ -0,0 +1,436 @@ +# Proposal: Next-Generation Grid & Entity System + +**Status:** Design Phase +**Complexity:** Major architectural overhaul +**Impact:** Grid System, Entity Management, Performance + +**Related Pages:** +- [[Grid-System]] - Current grid architecture +- [[Entity-Management]] - Current entity usage +- [[Grid-Rendering-Pipeline]] - Current rendering architecture + +**Source Documents:** +- `NEXT_GEN_GRIDS_ENTITIES_SHORTCOMINGS.md` - Analysis of current limitations +- `NEXT_GEN_GRIDS_ENTITIES_PROPOSAL.md` - Detailed technical proposal +- `NEXT_GEN_GRIDS_ENTITIES_IDEATION.md` - Use cases and ideation + +**Related Issues:** +- [#115](../../issues/115) - SpatialHash for 10,000+ entities +- [#116](../../issues/116) - Dirty flag system +- [#113](../../issues/113) - Batch operations +- [#117](../../issues/117) - Memory pool +- [#123](../../issues/123) - Subgrid system +- [#124](../../issues/124) - Grid Point Animation + +--- + +## Executive Summary + +The current UIEntity/UIGrid system has fundamental architectural limitations preventing implementation of modern roguelike features. This proposal outlines a comprehensive redesign supporting: + +- **Flexible entity content** - Entities containing any UIDrawable (Frame, Caption, Grid, Sprite) +- **Multi-tile entities** - 2x2, 3x3, or arbitrary-sized creatures and structures +- **Custom layer system** - Weather effects, particle layers, UI overlays +- **Spatial optimization** - O(1) entity queries via spatial hashing +- **Memory efficiency** - Optional gridstate, chunk-based loading + +**Key Insight:** Maintain entity as grid-specific *container* (no inheritance from UIDrawable), but allow flexible *content* (any UIDrawable). + +--- + +## Current Limitations + +### 1. Entity Type Rigidity + +**Problem:** +- UIEntity hardcoded to contain only UISprite +- Cannot place Frames, Captions, or Grids on grids +- Blocks speech bubbles, nested grids, complex UI + +**Current Code:** +```cpp +class UIEntity { + UISprite sprite; // Hardcoded! + // Should be: std::shared_ptr content; +} +``` + +### 2. Single-Tile Limitation + +**Problem:** +- Entity position is single point +- No concept of dimensions or occupied tiles +- Blocks large enemies (2x2 dragons), multi-tile structures (castle doors) + +**Missing:** +- `width`/`height` properties +- Spatial occupancy tracking +- Collision detection for multi-tile entities + +### 3. Fixed Layer System + +**Problem:** +- Grid has three hardcoded layers: tiles, entities, visibility +- No custom layers +- Blocks cloud layer, particle effects, weather overlays + +### 4. Performance Issues + +**Problem:** +- Linear O(n) iteration through all entities +- No spatial indexing +- Full grid re-render every frame + +**Current Code:** +```cpp +// O(n) iteration every frame +for (auto e : *entities) { + if (in_viewport(e)) render(e); +} +``` + +### 5. Memory Inefficiency + +**Problem:** +- Every entity maintains full gridstate vector (width × height) +- Decorative entities (clouds) waste memory on visibility data +- Cannot unload distant chunks + +--- + +## Proposed Architecture + +### Core Change 1: Flexible Entity Content + +```cpp +class UIEntity { // No inheritance - grid-specific container +private: + std::shared_ptr content; // Any drawable! + sf::Vector2f gridPosition; // Position in grid coords + sf::Vector2i dimensions; // Size in tiles (default 1x1) + std::set occupiedTiles; // Cached occupied positions + std::vector gridstate; // Optional perspective data + +public: + void setContent(std::shared_ptr drawable); + void renderAt(sf::RenderTarget& target, sf::Vector2f pixelPos); + bool occupies(int x, int y) const; +}; +``` + +**Python API:** +```python +# Entity with sprite (backward compatible) +enemy = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=5) + +# Entity with frame (NEW - speech bubble) +speech_frame = mcrfpy.Frame(size=(100, 50)) +speech_caption = mcrfpy.Caption(text="Hello!") +speech_frame.append(speech_caption) +speech_entity = mcrfpy.Entity(grid_pos=(player.x, player.y - 2)) +speech_entity.content = speech_frame + +# Entity with nested grid (NEW - mini-map) +minimap_grid = mcrfpy.Grid(grid_size=(20, 20), pos=(0, 0), size=(100, 100)) +minimap_entity = mcrfpy.Entity(grid_pos=(5, 5)) +minimap_entity.content = minimap_grid +``` + +### Core Change 2: Multi-Tile Entities + +```cpp +class GridOccupancyMap { +private: + std::unordered_map>> spatialHash; + int cellSize = 16; + +public: + void addEntity(std::shared_ptr entity); + void removeEntity(std::shared_ptr entity); + std::vector> getEntitiesAt(int x, int y); // O(1) + std::vector> getEntitiesInRect(sf::IntRect rect); +}; +``` + +**Python API:** +```python +# Large enemy (2x2 tiles) +dragon = mcrfpy.Entity( + grid_pos=(20, 20), + sprite_index=10, + dimensions=(2, 2) # NEW: multi-tile support +) + +# Check what tiles dragon occupies +occupied = dragon.occupied_tiles # [(20, 20), (21, 20), (20, 21), (21, 21)] + +# Collision detection accounts for size +if grid.can_move_to(dragon, new_x, new_y): + dragon.x = new_x + dragon.y = new_y +``` + +### Core Change 3: Flexible Layer System + +```cpp +class GridLayer { +public: + enum class Type { TILE, ENTITY, EFFECT, OVERLAY, CUSTOM }; + +private: + Type type; + std::string name; + int zOrder; + float opacity = 1.0f; + bool visible = true; + std::shared_ptr entities; // For ENTITY layers + +public: + virtual void render(UIGrid* grid, sf::RenderTarget& target); +}; + +class UIGrid : public UIDrawable { +private: + std::map> layers; + std::vector> sortedLayers; // By zOrder + +public: + std::shared_ptr addLayer(const std::string& name, + GridLayer::Type type, int zOrder); +}; +``` + +**Python API:** +```python +# Create custom layers +grid.add_layer("clouds", mcrfpy.LayerType.EFFECT, z_order=100) +grid.add_layer("particles", mcrfpy.LayerType.EFFECT, z_order=50) +grid.add_layer("units", mcrfpy.LayerType.ENTITY, z_order=10) + +# Add entities to specific layers +cloud = mcrfpy.Entity(grid_pos=(15, 15), sprite_index=20) +grid.get_layer("clouds").entities.append(cloud) + +# Layer control +grid.get_layer("clouds").opacity = 0.5 +grid.get_layer("clouds").visible = False +``` + +### Core Change 4: Spatial Optimization + +**SpatialHash Implementation:** +```cpp +int hashPosition(int x, int y) const { + return (x / cellSize) * 1000000 + (y / cellSize); +} + +std::vector> getEntitiesInRect(sf::IntRect rect) { + std::set> result; + + // Only check relevant hash cells + for (int y = rect.top; y < rect.top + rect.height; y += cellSize) { + for (int x = rect.left; x < rect.left + rect.width; x += cellSize) { + int hash = hashPosition(x, y); + if (spatialHash.count(hash)) { + result.insert(spatialHash[hash].begin(), spatialHash[hash].end()); + } + } + } + + return std::vector>(result.begin(), result.end()); +} +``` + +**Performance Impact:** +- Current: O(n) for entity queries +- Proposed: O(1) average case + +**Benchmark Targets:** +- 10,000 entities with 50 visible: <1ms per frame +- Entity collision detection: O(k) where k = entities in same hash cell + +--- + +## Migration Path + +### Phase 1: Performance Foundation (Issues #115, #116, #117) + +**Backward compatible improvements:** +1. Add SpatialHash to existing UIGrid +2. Implement dirty flag system +3. Add memory pool for entities + +**No breaking changes to Python API.** + +### Phase 2: Multi-Tile Support (Issue #123) + +**Add to existing UIEntity:** +```cpp +// Add to UIEntity class +sf::Vector2i dimensions = {1, 1}; // Default 1x1 (backward compatible) +std::set occupiedTiles; + +void updateOccupiedTiles(); +bool occupies(int x, int y) const; +``` + +**Python API additions:** +```python +# Backward compatible - existing code works unchanged +enemy = mcrfpy.Entity(grid_pos=(10, 10), sprite_index=5) + +# New code can specify dimensions +dragon = mcrfpy.Entity(grid_pos=(20, 20), sprite_index=10, dimensions=(2, 2)) +``` + +### Phase 3: Flexible Content (Issue #124) + +**Replace UIEntity::sprite with content:** +```cpp +// Deprecate: UISprite sprite; +// Add: std::shared_ptr content; + +// Backward compatibility shim: +PyObject* get_sprite() { + auto sprite = std::dynamic_pointer_cast(content); + if (!sprite) { + // Legacy: entity still has sprite member + return legacy_sprite_accessor(); + } + return RET_PY_INSTANCE(sprite); +} +``` + +**Migration period:** 1-2 releases with deprecation warnings. + +### Phase 4: Layer System + +**Add GridLayer system alongside existing architecture:** +```cpp +// Existing entities automatically added to default "entities" layer +// New layer API available for advanced use +``` + +**Backward compatible - existing code works unchanged.** + +--- + +## Use Cases Enabled + +### Speech Bubbles +```python +speech = mcrfpy.Frame(size=(100, 40)) +speech.append(mcrfpy.Caption(text="Hello adventurer!")) +bubble = mcrfpy.Entity(grid_pos=(npc.x, npc.y - 1)) +bubble.content = speech +grid.entities.append(bubble) +``` + +### Large Enemies +```python +dragon = mcrfpy.Entity(grid_pos=(25, 25), sprite_index=DRAGON, dimensions=(3, 3)) + +# Pathfinding accounts for size +if grid.can_large_entity_move_to(dragon, new_x, new_y): + dragon.move_to(new_x, new_y) +``` + +### Weather Effects +```python +grid.add_layer("rain", mcrfpy.LayerType.EFFECT, z_order=200) +rain_layer = grid.get_layer("rain") + +for i in range(100): + raindrop = mcrfpy.Entity( + grid_pos=(random.randint(0, 49), random.randint(0, 49)), + sprite_index=RAINDROP + ) + rain_layer.entities.append(raindrop) +``` + +### Nested Mini-Map +```python +minimap = mcrfpy.Grid(grid_size=(20, 20), pos=(0, 0), size=(100, 100)) +# ... populate minimap ... + +minimap_entity = mcrfpy.Entity(grid_pos=(0, 0)) +minimap_entity.content = minimap +hud_layer.entities.append(minimap_entity) +``` + +--- + +## Performance Expectations + +### Before (Current System) +- 1,000 entities: 60 FPS +- 10,000 entities: 15 FPS (unacceptable) +- Entity query: O(n) = slow + +### After (Proposed System) +- 1,000 entities: 60 FPS +- 10,000 entities: 60 FPS (with spatial hash + culling) +- Entity query: O(1) average case + +### Memory Impact +- Per-entity overhead: +24 bytes (dimensions, occupied tiles set) +- Spatial hash: ~8KB for 1000 entities (negligible) +- Optional gridstate: Save width × height × sizeof(UIGridPointState) per decorative entity + +--- + +## Open Questions + +1. **Backward Compatibility Timeline** + - How many releases should deprecation period last? + - Support for legacy `entity.sprite` accessor? + +2. **Layer API Design** + - Should layers have separate render textures? + - Layer blending modes (multiply, add, alpha)? + +3. **Multi-Tile Pathfinding** + - Should large entities use separate TCOD maps? + - How to handle partially-blocked paths? + +4. **Content Delegation** + - Should entity forward all UIDrawable methods to content? + - Or keep explicit `entity.content.method()` pattern? + +--- + +## Implementation Complexity + +**Estimated Effort:** +- Phase 1 (SpatialHash, dirty flags): 40-60 hours +- Phase 2 (Multi-tile): 20-30 hours +- Phase 3 (Flexible content): 30-40 hours +- Phase 4 (Layers): 40-50 hours + +**Total:** 130-180 hours (3-4 months part-time) + +**Risk Areas:** +- Backward compatibility testing +- Python binding complexity for flexible content +- Performance regression testing +- Documentation updates + +--- + +## Decision Needed + +**Should this proposal move forward?** +- ✅ Addresses major architectural limitations +- ✅ Enables modern roguelike features +- ✅ Clear migration path with backward compatibility +- ⚠️ Significant implementation effort +- ⚠️ Risk of introducing bugs in core systems + +**Alternative:** Focus on incremental improvements (SpatialHash, dirty flags) without architectural redesign. + +--- + +**Navigation:** +- [[Home]] - Documentation hub +- [[Grid-System]] - Current architecture +- [[Design-Proposals]] - All design proposals