diff --git a/Grid-Rendering-Pipeline.-.md b/Grid-Rendering-Pipeline.-.md index 59c4e71..5b31ddb 100644 --- a/Grid-Rendering-Pipeline.-.md +++ b/Grid-Rendering-Pipeline.-.md @@ -2,518 +2,373 @@ ## Overview -The Grid rendering pipeline is McRogueFace's most complex rendering subsystem. It handles viewport culling, zoom, entity rendering, and FOV overlays - all within a single `sf::RenderTexture` for optimal performance. +The Grid rendering pipeline handles multi-layer tilemap rendering with chunk-based caching for optimal performance. It supports arbitrary numbers of rendering layers, viewport culling, zoom, and entity rendering. **Parent Page:** [[Grid-System]] **Related Pages:** -- [[Performance-and-Profiling]] - ScopedTimer instrumentation points +- [[Performance-and-Profiling]] - Metrics and profiling tools - [[Entity-Management]] - Entity rendering within grids -- [[UI-Component-Hierarchy]] - Where Grid fits in render hierarchy +- [[Grid-TCOD-Integration]] - FOV and pathfinding (partially in transition) **Key Files:** -- `src/UIGrid.cpp::render()` - Main rendering entry point (lines 97-310) -- `src/GameEngine.cpp` - ProfilingMetrics struct for tracking render times -- `src/Profiler.h` - ScopedTimer RAII helper +- `src/UIGrid.cpp::render()` - Main rendering orchestration +- `src/GridLayers.cpp` - ColorLayer and TileLayer rendering +- `src/UIGrid.cpp::renderChunk()` - Per-chunk RenderTexture management **Related Issues:** -- [#116](../../issues/116) - Dirty flag system (avoid re-rendering static content) -- [#115](../../issues/115) - SpatialHash for entity culling optimization -- [#113](../../issues/113) - Batch operations API +- [#123](../../issues/123) - Chunk-based Grid Rendering (Closed - Implemented) +- [#148](../../issues/148) - Dirty Flag RenderTexture Caching (Closed - Implemented) +- [#147](../../issues/147) - Dynamic Layer System (Closed - Implemented) +- [#113](../../issues/113) - Batch Operations API (Open - includes FOV access discussion) +- [#115](../../issues/115) - SpatialHash for entity culling (Open) + +--- + +## Architecture Overview + +### Layer-Based Rendering + +Grids render content through **layers** rather than per-cell properties. Each layer is either a `ColorLayer` (solid colors) or `TileLayer` (texture sprites). + +**Render order is determined by z_index:** +- Layers with `z_index < 0` render **below** entities +- Entities render at the z=0 boundary +- Layers with `z_index >= 0` render **above** entities (overlays) + +Within each group, lower z_index values render first (behind higher values). + +``` +z_index: -3 -2 -1 0 +1 +2 + ↓ ↓ ↓ ↓ ↓ ↓ + [background] [tiles] [ENTITIES] [fog] [UI overlay] +``` + +**Implementation:** See `UIGrid::render()` which iterates layers in z_index order, inserting entity rendering at the 0 boundary. + +### Chunk-Based Caching + +Large grids are divided into **chunks** (regions of cells). Each chunk maintains its own `sf::RenderTexture` that caches the rendered result. + +**Key concepts:** +- Chunk size is implementation-defined (currently ~256 cells per dimension) +- Only chunks intersecting the viewport are considered for rendering +- Each chunk tracks whether its content is "dirty" (needs redraw) +- Static content renders once, then the cached texture is reused + +**Implementation:** See `UIGrid::renderChunk()` for chunk texture management. + +### Dirty Flag Propagation + +When layer content changes, only affected chunks are marked dirty: + +1. `layer.set(x, y, value)` marks the containing chunk as dirty +2. On next render, dirty chunks redraw to their RenderTexture +3. Clean chunks simply blit their cached texture + +This means a 1000x1000 grid with one changing cell redraws only ~1 chunk, not 1,000,000 cells. + +**Implementation:** Dirty flags propagate through `UIGrid::markDirty()` and are checked in `UIGrid::render()`. --- ## Render Pipeline Stages -The pipeline executes in **4 stages** during each frame: +### Stage 1: Viewport Calculation -``` -1. Viewport Calculation (culling bounds) - ↓ -2. Base Layer Rendering (tiles + background colors) - ↓ -3. Entity Layer Rendering (visible entities) - ↓ -4. FOV Overlay Rendering (discovered/visible states) -``` +Calculate which chunks and cells are visible based on camera position, zoom, and grid dimensions. -All rendering happens into an `sf::RenderTexture`, then the final texture is drawn to the window in a single operation. +**Key properties:** +- `center` / `center_x`, `center_y` - Camera position in pixel coordinates (within grid space) +- `zoom` - Scale factor (1.0 = normal, 2.0 = 2x magnification) +- `size` - Viewport dimensions in screen pixels + +**Implementation:** Viewport bounds calculated at start of `UIGrid::render()`. + +### Stage 2: Below-Entity Layers + +For each layer with `z_index < 0`, sorted by z_index: +1. Determine which chunks intersect viewport +2. For each visible chunk: + - If dirty: redraw layer content to chunk's RenderTexture + - Draw chunk's cached texture to output + +### Stage 3: Entity Rendering + +Entities render at the z=0 boundary: +1. Iterate entity collection +2. Cull entities outside viewport bounds +3. Draw visible entity sprites at interpolated positions + +**Note:** Entity culling is currently O(n). SpatialHash optimization planned in [#115](../../issues/115). + +### Stage 4: Above-Entity Layers + +For each layer with `z_index >= 0`, sorted by z_index: +- Same chunk-based rendering as Stage 2 +- These layers appear as overlays (fog, highlights, UI elements) + +### Stage 5: Final Compositing + +All rendered content exists in the grid's output RenderTexture, which is drawn to the window in a single operation. --- -## Stage 1: Viewport Calculation +## Performance Characteristics -### Purpose -Calculate which cells are visible based on camera position, zoom, and grid size. +**Static Grids:** +- Near-zero CPU cost after initial render +- Cached chunk textures reused frame-to-frame +- Only viewport calculation and texture blitting -### Implementation -```cpp -// Get cell dimensions from texture -int cell_width = ptex ? ptex->sprite_width : DEFAULT_CELL_WIDTH; -int cell_height = ptex ? ptex->sprite_height : DEFAULT_CELL_HEIGHT; +**Dynamic Grids:** +- Cost proportional to number of dirty chunks +- Single-cell changes affect only one chunk +- Bulk operations should batch changes before render -// Camera center in grid coordinates -float center_x_sq = center_x / cell_width; -float center_y_sq = center_y / cell_height; +**Large Grids:** +- 1000x1000+ grids render efficiently +- Only visible chunks processed +- Memory scales with grid size (chunk textures) -// Viewport size in cells -float width_sq = box.getSize().x / (cell_width * zoom); -float height_sq = box.getSize().y / (cell_height * zoom); +**Profiling:** Use `mcrfpy.getMetrics()` or the F3 overlay to see render times. See [[Performance-and-Profiling]]. -// Visible cell range -float left_edge = center_x_sq - (width_sq / 2.0); -float top_edge = center_y_sq - (height_sq / 2.0); -int x_limit = left_edge + width_sq + 2; // +2 for margin -int y_limit = top_edge + height_sq + 2; -``` +--- -**Key Insight:** The +2 margin ensures partially-visible cells at viewport edges are rendered completely. +## FOV and Perspective System (In Transition) -### Camera Properties +**Current Status:** The FOV computation (`compute_fov()`, `is_in_fov()`) works correctly for pathfinding and visibility queries. However, the automatic fog-of-war overlay system is **not currently connected** to the new layer architecture. -- `center_x`, `center_y` - Camera position in pixel coordinates -- `zoom` - Scale factor (1.0 = normal, 2.0 = 2x zoom) -- `box.getSize()` - Viewport dimensions in pixels +**What works:** +- `grid.compute_fov(x, y, radius)` - Computes which cells are visible +- `grid.is_in_fov(x, y)` - Queries visibility of a specific cell +- Pathfinding uses walkable/transparent properties correctly -### Python API +**What's in transition:** +- Per-entity perspective (`UIGridPointState`) not yet exposed to Python +- Automatic fog overlay rendering disconnected from layer system +- Batch FOV data access being designed ([#113 discussion](../../issues/113)) + +**Current workaround - Manual fog layer:** ```python -grid.center_x = player.x * cell_width # Follow player -grid.center_y = player.y * cell_height -grid.zoom = 2.0 # Zoom in +# Create a fog overlay layer (positive z_index = above entities) +fog_layer = grid.add_layer("color", z_index=1) + +# After computing FOV, update fog colors +grid.compute_fov(player.grid_x, player.grid_y, radius=10) + +for x in range(grid.grid_x): + for y in range(grid.grid_y): + if not grid.is_in_fov(x, y): + # Dim color for non-visible cells + fog_layer.set(x, y, mcrfpy.Color(0, 0, 0, 192)) + else: + # Transparent for visible cells + fog_layer.set(x, y, mcrfpy.Color(0, 0, 0, 0)) ``` +**Limitation:** This O(n²) iteration is inefficient for large grids. Batch operations ([#113](../../issues/113)) will address this with patterns like `cells_in_radius()` iterators. + --- -## Stage 2: Base Layer Rendering +## Layer Techniques -### Purpose -Render the "ground" layer: background colors and tile sprites. +### Multiple Overlay Layers -### Loop Structure -```cpp -// Profile this stage -ScopedTimer gridTimer(Resources::game->metrics.gridRenderTime); - -int cellsRendered = 0; -for (int x = left_edge - 1; x < x_limit; x++) { - for (int y = top_edge - 1; y < y_limit; y++) { - // Calculate pixel position within renderTexture - auto pixel_pos = sf::Vector2f( - (x * cell_width - left_spritepixels) * zoom, - (y * cell_height - top_spritepixels) * zoom - ); - - auto gridpoint = at(x, y); - - // 1. Draw background color - r.setPosition(pixel_pos); - r.setFillColor(gridpoint.color); - renderTexture.draw(r); - - // 2. Draw tile sprite (if texture exists and tilesprite set) - if (ptex && gridpoint.tilesprite != -1) { - sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, - sf::Vector2f(zoom, zoom)); - renderTexture.draw(sprite); - } - - cellsRendered++; - } -} - -// Record metrics for profiling overlay -Resources::game->metrics.gridCellsRendered += cellsRendered; -``` - -### Per-Cell Data - -Each `UIGridPoint` has: -- `color` - RGBA background color (drawn first) -- `tilesprite` - Sprite index from texture sheet (-1 = no sprite) -- `walkable` - Affects pathfinding (not used in rendering) - -### Performance Characteristics - -**Current Performance:** -- 100x100 grid, ~1000 visible cells -- Grid render time: 8-12ms (without dirty flags) -- Target: <2ms after dirty flag optimization (#116) - -**Bottlenecks:** -- Individual `renderTexture.draw()` calls per cell -- No caching of static content -- Python/C++ boundary crossings for `at(x, y)` calls - ---- - -## Stage 3: Entity Layer Rendering - -### Purpose -Render entities that exist on the grid at their interpolated positions. - -### Loop Structure -```cpp -{ - ScopedTimer entityTimer(Resources::game->metrics.entityRenderTime); - int entitiesRendered = 0; - int totalEntities = entities->size(); - - for (auto e : *entities) { - // Viewport culling: skip out-of-bounds 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; - } - - // Get entity's sprite - auto& drawent = e->sprite; - drawent.setScale(sf::Vector2f(zoom, zoom)); - - // Calculate pixel position (supports fractional positions) - auto pixel_pos = sf::Vector2f( - (e->position.x * cell_width - left_spritepixels) * zoom, - (e->position.y * cell_height - top_spritepixels) * zoom - ); - - // Render entity sprite - drawent.render(pixel_pos, renderTexture); - - entitiesRendered++; - } - - // Record metrics - Resources::game->metrics.entitiesRendered += entitiesRendered; - Resources::game->metrics.totalEntities += totalEntities; -} -``` - -### Entity Positions - -Entities have **floating-point positions** (`entity.position.x`, `entity.position.y`), allowing smooth sub-cell movement: +You can pre-create multiple overlay layers and toggle between them: ```python -# Entity at fractional position -entity.x = 10.5 # Halfway between cells 10 and 11 -entity.y = 20.75 +# Create several overlay options +highlight_layer = grid.add_layer("color", z_index=1) +danger_layer = grid.add_layer("color", z_index=2) +fog_layer = grid.add_layer("color", z_index=3) + +# Populate each with different data... +# highlight_layer shows selected cells +# danger_layer shows enemy threat zones +# fog_layer shows visibility + +# Toggle visibility to switch which overlay shows +def show_danger_zones(): + highlight_layer.visible = False + danger_layer.visible = True + fog_layer.visible = False + +def show_fog_of_war(): + highlight_layer.visible = False + danger_layer.visible = False + fog_layer.visible = True ``` -### Performance Considerations +### Z-Index Reordering -**Current Performance:** -- 50 entities: ~2-3ms render time -- 500 entities: ~20-30ms (all visible) -- With culling: only visible entities rendered - -**Optimization Opportunities:** -- **#115**: SpatialHash for O(1) visibility queries -- **#117**: Memory pool to reduce allocation overhead -- Sort by Z-order before rendering (not yet implemented) - ---- - -## Stage 4: FOV Overlay Rendering - -### Purpose -If `perspective_enabled` is true, overlay cells based on entity's discovered/visible state. - -### Three Visibility States - -1. **Visible** - Entity can currently see this cell → No overlay -2. **Discovered but not visible** - Entity has seen this cell before → Dark gray overlay (RGBA: 32, 32, 40, 192) -3. **Undiscovered** - Entity has never seen this cell → Black overlay (RGBA: 0, 0, 0, 255) - -### Loop Structure -```cpp -if (perspective_enabled) { - ScopedTimer fovTimer(Resources::game->metrics.fovOverlayTime); - auto entity = perspective_entity.lock(); // weak_ptr to entity - - sf::RectangleShape overlay; - overlay.setSize(sf::Vector2f(cell_width * zoom, cell_height * zoom)); - - if (entity) { - // Valid entity - use its gridstate - for (int x = left_edge - 1; x < x_limit; x++) { - for (int y = top_edge - 1; y < y_limit; y++) { - int idx = y * grid_x + x; - const auto& state = entity->gridstate[idx]; - - overlay.setPosition(pixel_pos); - - if (!state.discovered) { - // Black - never seen - overlay.setFillColor(sf::Color(0, 0, 0, 255)); - renderTexture.draw(overlay); - } else if (!state.visible) { - // Dark gray - seen before, not currently visible - overlay.setFillColor(sf::Color(32, 32, 40, 192)); - renderTexture.draw(overlay); - } - // else: visible and discovered - no overlay - } - } - } else { - // Entity destroyed/invalid - all cells black - // ... render full black overlays ... - } -} -// else: omniscient view (perspective_enabled = false) - no overlays -``` - -### Perspective System - -See [[Grid-TCOD-Integration]] for details on how `compute_fov()` updates entity gridstate. - -**Python API:** -```python -# Enable perspective view from entity's POV -grid.set_perspective(player_entity) - -# Disable perspective (omniscient view) -grid.clear_perspective() - -# Check if perspective enabled -if grid.perspective_enabled: - print("Using entity perspective") -``` - ---- - -## Final Output - -### RenderTexture to Window - -After all 4 stages complete: - -```cpp -renderTexture.display(); // Finalize texture - -// output sprite uses the renderTexture -output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y)); -output.setPosition(box.getPosition() + offset); - -// Single draw call to window -target.draw(output); -``` - -**Performance Benefit:** Entire grid (1000s of cells + entities + overlays) rendered to window in **one draw call**. - ---- - -## Profiling Integration - -The rendering pipeline is instrumented with `ScopedTimer` for F3 overlay: - -```cpp -void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) { - ScopedTimer gridTimer(Resources::game->metrics.gridRenderTime); - - // Stage 2: Base layer - // ... rendering ... - - // Stage 3: Entities - { - ScopedTimer entityTimer(Resources::game->metrics.entityRenderTime); - // ... entity rendering ... - } - - // Stage 4: FOV overlay - if (perspective_enabled) { - ScopedTimer fovTimer(Resources::game->metrics.fovOverlayTime); - // ... overlay rendering ... - } -} -``` - -Press **F3** in-game to see: -- Grid render time -- Entity render time -- FOV overlay time -- Cells/entities rendered - -See [[Performance-and-Profiling]] for details on using the profiler. - ---- - -## Performance Optimization Workflow - -### Current Bottlenecks (October 2025) - -1. **Grid re-renders every frame** even if static - - **Solution**: #116 (Dirty flag system) - - Expected improvement: 8-12ms → <2ms for static content - -2. **Entity culling is O(n)** - - **Solution**: #115 (SpatialHash) - - Expected improvement: 10,000 entities with 50 visible → 1-2ms - -3. **Individual draw calls per cell** - - **Solution**: Vertex arrays or instanced rendering - - Expected improvement: 50% reduction in draw call overhead - -### Profiling a Grid Performance Issue - -Example workflow using [[Performance-Optimization-Workflow]]: +Change layer order at runtime by modifying z_index: ```python -# 1. Create benchmark scene -import mcrfpy +# Bring a layer to front +important_layer.z_index = 100 -mcrfpy.createScene("test") -grid = mcrfpy.Grid(grid_size=(100, 100), pos=(0, 0), size=(1024, 768)) - -# Fill grid with varied content -for x in range(100): - for y in range(100): - cell = grid.at((x, y)) - cell.tilesprite = (x + y) % 10 - cell.color = (50 + x, 50 + y, 100, 255) - -ui = mcrfpy.sceneUI("test") -ui.append(grid) -mcrfpy.setScene("test") - -# 2. Press F3 to see profiler overlay -# 3. Check metrics: -# - Grid render time: X ms -# - Cells rendered: Y -# - FPS: Z +# Send a layer behind entities +background_layer.z_index = -10 ``` -**Interpreting Results:** -- Grid render time > 10ms → Consider dirty flags -- Entities rendered << total entities → Culling is working -- FOV overlay time > 5ms → Large visible area or complex state +**Note:** Changing z_index marks the layer dirty, triggering re-render of affected chunks. + +### Constructor `layers={}` Limitations + +The `layers={}` constructor argument is convenient but limited: + +```python +# This creates layers, but ALL get negative z_index (below entities) +grid = mcrfpy.Grid( + grid_size=(50, 50), + layers={"ground": "color", "terrain": "tile", "overlay": "color"} +) +# Result: ground=-3, terrain=-2, overlay=-1 (all below entities!) +``` + +**For overlays above entities, use `add_layer()` explicitly:** + +```python +grid = mcrfpy.Grid(grid_size=(50, 50), layers={}) +ground = grid.add_layer("color", z_index=-2) +terrain = grid.add_layer("tile", z_index=-1) +overlay = grid.add_layer("color", z_index=1) # Above entities! +``` + +--- + +## Migration from Legacy API + +Prior to the layer system, grids had built-in per-cell rendering via `UIGridPoint` properties. Here's how to recreate that behavior: + +### Legacy Pattern (Pre-November 2025) + +```python +# OLD API (no longer works): +grid = mcrfpy.Grid(50, 50, 16, 16) +cell = grid.at(x, y) +cell.tilesprite = 42 # Sprite index +cell.color = (255, 0, 0) # Background color +``` + +### Equivalent Modern Pattern + +```python +# NEW API - explicit layers: +grid = mcrfpy.Grid(grid_size=(50, 50), pos=(0, 0), size=(400, 400)) + +# Grid creates a default TileLayer; access it: +tile_layer = grid.layers[0] +tile_layer.set(x, y, 42) # Sprite index + +# For background colors, add a ColorLayer behind tiles: +color_layer = grid.add_layer("color", z_index=-2) # Behind default tile layer +color_layer.set(x, y, mcrfpy.Color(255, 0, 0)) +``` + +### Recreating Old Three-Layer Rendering + +The legacy system rendered: background color → tile sprite → FOV overlay. + +```python +# Create grid with no default layers +grid = mcrfpy.Grid( + grid_size=(50, 50), + pos=(100, 100), + size=(400, 400), + texture=tileset, + layers={} +) + +# Layer 1: Background colors (z=-2, behind everything) +background = grid.add_layer("color", z_index=-2) + +# Layer 2: Tile sprites (z=-1, above background, below entities) +tiles = grid.add_layer("tile", z_index=-1, texture=tileset) + +# Layer 3: FOV overlay (z=+1, above entities) +fog = grid.add_layer("color", z_index=1) + +# Now populate layers as needed +background.fill(mcrfpy.Color(20, 20, 30)) # Dark blue background + +for x in range(50): + for y in range(50): + tiles.set(x, y, calculate_tile_index(x, y)) + +# FOV overlay updated after compute_fov() calls +``` --- ## Common Issues -### Issue: Grid Renders Off-Center +### Issue: Layer Changes Don't Appear -**Cause:** `center_x`/`center_y` values incorrect relative to camera follow logic. +**Cause:** Layer content changed but chunk not marked dirty. -**Fix:** +**Fix:** Use layer methods (`set()`, `fill()`) which automatically mark dirty. Direct property manipulation may bypass dirty flagging. + +### Issue: Overlay Appears Behind Entities + +**Cause:** Layer z_index is negative. + +**Fix:** Use `z_index >= 0` for overlays: ```python -# Ensure center is in pixel coordinates, not cell coordinates -grid.center_x = player.x * cell_width -grid.center_y = player.y * cell_height +overlay = grid.add_layer("color", z_index=1) # Not z_index=-1 ``` -### Issue: Entities Flicker at Viewport Edge +### Issue: Performance Degrades with Many Changes -**Cause:** Culling margin too small (entities disappear before fully off-screen). - -**Fix:** The -1/+1 margins in culling logic should handle this, but if needed: -```cpp -// Increase margin from 1 to 2 -if (e->position.x < left_edge - 2 || e->position.x >= left_edge + width_sq + 2) -``` - -### Issue: Zoom Breaks Entity Positions - -**Cause:** `zoom` not applied consistently to entity pixel positions. - -**Fix:** Ensure both entity position calculation AND sprite scale use zoom: -```cpp -auto pixel_pos = sf::Vector2f( - (e->position.x * cell_width - left_spritepixels) * zoom, - (e->position.y * cell_height - top_spritepixels) * zoom -); -drawent.setScale(sf::Vector2f(zoom, zoom)); -``` - -### Issue: FOV Overlay Has Gaps - -**Cause:** `gridstate` not updated after `compute_fov()`. - -**Fix:** See [[Grid-TCOD-Integration]] for proper FOV computation sequence. - ---- - -## Future Improvements - -### Dirty Flag System (#116) - -Only re-render cells that changed: - -```cpp -// Proposed implementation -bool grid_dirty = true; // Set when cells change - -void UIGrid::render() { - if (!grid_dirty && cachedTexture.valid()) { - // Use cached texture - target.draw(cachedOutput); - return; - } - - // Full re-render - // ... existing pipeline ... - - grid_dirty = false; -} -``` - -**Benefits:** -- Static grids: 8-12ms → <1ms (just copy cached texture) -- Animated grids: Only re-render changed cells - -### Batch Cell Updates (#113) - -Expose NumPy-style batch operations: +**Cause:** Each `set()` call can mark a chunk dirty; many scattered changes = many chunk redraws. +**Fix:** Batch logically-related changes. For bulk updates, consider: ```python -# Current: O(n) Python/C++ crossings +# Less efficient: 10,000 individual calls for x in range(100): for y in range(100): - grid.at((x, y)).color = (255, 0, 0, 255) + layer.set(x, y, value) -# Proposed: O(1) crossings -grid.set_colors(colors_array) # NumPy array or Python list +# More efficient when available: bulk fill +layer.fill(mcrfpy.Color(0, 0, 0)) # Single operation ``` -### SpatialHash Entity Culling (#115) - -Replace linear entity scan with spatial queries: - -```cpp -// Current: O(n) -for (auto e : *entities) { - if (in_viewport(e)) render(e); -} - -// Proposed: O(visible) -auto visible_entities = spatial_hash.query(viewport_bounds); -for (auto e : visible_entities) { - render(e); -} -``` +Batch operations API ([#113](../../issues/113)) will provide more patterns. --- -## API Reference +## API Quick Reference -See [`docs/api_reference_dynamic.html`](../../src/branch/master/docs/api_reference_dynamic.html) for complete Grid API. +**Grid Properties:** +- `center`, `center_x`, `center_y` - Camera position (pixels in grid space) +- `zoom` - Scale factor +- `layers` - List of layer objects, sorted by z_index +- `fill_color` - Grid background (behind all layers) -**Key Properties:** -- `grid.center_x`, `grid.center_y` - Camera position (pixels) -- `grid.zoom` - Zoom level (float, default 1.0) -- `grid.perspective_enabled` - Read-only, check if FOV overlay active +**Grid Methods:** +- `add_layer(type, z_index, texture)` - Create new layer +- `remove_layer(layer)` - Remove a layer -**Key Methods:** -- `grid.set_perspective(entity)` - Enable FOV from entity's POV -- `grid.clear_perspective()` - Disable FOV overlay -- `grid.at((x, y))` - Get cell for modification +**Layer Properties:** +- `z_index` - Render order +- `visible` - Show/hide layer +- `grid_size` - Dimensions (read-only) + +**Layer Methods:** +- `set(x, y, value)` - Set cell (color or sprite index) +- `at(x, y)` - Get cell value +- `fill(value)` - Fill all cells --- **Navigation:** -- [[Grid-System]] - Parent page +- [[Grid-System]] - Parent page, layer concepts - [[Grid-TCOD-Integration]] - FOV computation details -- [[Grid-Entity-Lifecycle]] - How entities interact with rendering -- [[Performance-and-Profiling]] - Using ScopedTimer and F3 overlay -- [[Entity-Management]] - Entity rendering specifics +- [[Performance-and-Profiling]] - Metrics and optimization +- [[Entity-Management]] - Entity rendering + +--- + +*Last updated: 2025-11-29* \ No newline at end of file