Skip to content

Structures Implementation Plan

Purpose

This plan covers full implementation of map-space Structures.

Always update this plan when changing Structure data contracts, runtime ownership, occupancy behavior, rendering, sidecar load/save, commands, UI/debug tooling, pathing integration, or persistence behavior.

Structures are persistent or semi-persistent stateful map objects such as campfires, tents, caches, cairns, shelters, drying racks, and buildings. They can span multiple terrain pixels, have clear placement footprints, and may hide visual information below them. Structure-to-structure overlap is disallowed.

Structures are not sprite details and are not decals. They may reuse the same low-level atlas-backed quad rendering path as decals and agents, but their runtime owner, data model, placement validation, persistence, and gameplay contracts are separate.

Dependencies

  • Sprite rendering design context: docs/SPRITE_DETAILS_RENDERING_DESIGN.md
  • Existing map lifecycle and sidecar patterns: src/gameplay/mapSidecarLoader.js src/gameplay/mapDataSaveController.js
  • Existing command ownership rules: src/core/registerMainCommands.js src/gameplay/interactionCommands.js
  • Existing render pass architecture: src/render/renderPipelineRuntime.js src/render/frameSwarmRenderRuntime.js
  • Existing overlay/debug UI conventions: docs/RD_UI_ARCHITECTURE.md docs/UI_LAYOUT_GRID.md

Current Status

The first structure slice is implemented and manually smoke-tested.

Implemented:

  • optional assets/<mapName>/structures.json sidecar load/save
  • structureRuntime owner with validation, occupancy, placement, removal, state updates, render snapshots, and query APIs
  • runtime-generated map-sprite atlas from individual spriteSrc PNGs
  • shared map-sprite atlas generation treats structure sources as whole-image sprites unless a render item explicitly provides source-frame crop metadata; this preserves the 128x128 tent source while agents can still use 32x32 or 64x64 strip frames in the same renderer implementation
  • WebGL structure pass after terrain with terrain/point-light lighting
  • RD > Sprites > Structures controls for render visibility, type selection, place-at-player, repeated cursor place mode, nearest selection, removal, occupancy overlay, and selected-state readout
  • binary green/red footprint placement preview
  • local pathfinding and movement blocking for types with blocksMovement: true
  • focused and full JS test coverage for serializer, runtime, sidecar, commands, UI, rendering, movement, and pathfinding integration

Still intentionally future work:

  • player-facing structure placement through gameplay actions
  • route-planning obstacle projection
  • authored-default vs mutable-savegame separation
  • container/rest/heat/crafting/event/audio gameplay integrations
  • performance profiling with large structure counts

Phase 0: Scope Lock

  • Decide first proof structure type.
  • Recommended: cache if inventory/container integration is next.
  • Alternative: campfire if lighting/rest/event integration is next.
  • Chosen v1 proof: nomadic_tent in assets/map3/structures.json.
  • Decide first structure sprite source location.
  • Reusable structure sprites live under assets/sprites/structures/default/.
  • Scoped structure sets can live under assets/sprites/structures/<mapOrSetName>/.
  • Artists provide individual PNGs; the runtime can build the atlas.
  • Decide initial proof atlas slot size.
  • First proof uses one 128x128 source sprite rendered over 4x4 map pixels.
  • Structure slot size remains per-map/per-sidecar metadata, not hardcoded globally.
  • Decide first render order.
  • Recommended v1: terrain -> material detail -> sprite details -> ground decals -> structures -> agents -> UI.
  • Defer shared agent/structure y-sort until tall structures prove the need.
  • Decide whether v1 structures block pathfinding.
  • Occupancy grid exists in v1.
  • Local pathfinding and movement blocking are gated per structure type with blocksMovement.

Phase 1: Data Contract

  • Create docs/STRUCTURE_DATA_CONTRACT.md.
  • Define authored sidecar file name: structures.json.
  • Define versioned root shape.
  • Define structure type registry shape.
  • Define structure instance shape.
  • Define footprint mask shape.
  • Define visual bounds vs footprint bounds.
  • Define interaction radius or interaction points.
  • Define state payload rules.
  • Define backward-tolerant unknown-field behavior.
  • Define minimum v1 sidecar example.
  • Include one structure type.
  • Include one placed structure instance.
  • Include atlas metadata.
  • Define ID rules.
  • Stable unique instance IDs.
  • Stable structure type IDs.
  • Type IDs must not depend on atlas slot numbers.
  • Define coordinate rules.
  • Use pixelX and pixelY for current single-map compatibility.
  • Keep contract ready for future worldX and worldY.
  • Define anchor meaning as footprint origin for v1.
  • Define no-overlap rule.
  • Structure footprints cannot overlap occupied structure cells.
  • Visual quads may cover terrain/details/decals.
  • Visual quads may extend beyond footprint bounds.

Phase 2: Pure Structure Model

  • Add src/gameplay/structureDataSerializer.js.
  • Export default/empty structure data.
  • Normalize raw structures.json.
  • Validate version.
  • Normalize atlas metadata.
  • Normalize type definitions.
  • Normalize instances.
  • Clamp numeric fields.
  • Preserve unknown state fields where safe.
  • Reject duplicate type IDs.
  • Reject duplicate instance IDs.
  • Reject missing type references.
  • Reject invalid footprint masks.
  • Reject configured data caps.
  • Add unit tests for serializer.
  • Empty data normalizes.
  • Valid example normalizes.
  • Missing optional fields use defaults.
  • Duplicate IDs fail.
  • Invalid footprint masks fail.
  • Unknown future fields are tolerated where intended.
  • Add pure footprint helpers.
  • Convert footprint mask to occupied map cells.
  • Compute visual bounds.
  • Compute footprint bounds.
  • Test anchor/origin behavior.

Phase 3: Structure Runtime Owner

  • Add src/gameplay/structureRuntime.js.
  • Own structure type registry.
  • Own structure instance state.
  • Provide applyStructureData(rawData).
  • Provide serializeStructureData().
  • Provide getStructureSnapshot().
  • Provide getStructureRenderSnapshot().
  • Provide getStructureAtPixel(pixelX, pixelY).
  • Provide canPlaceStructure(typeId, pixelX, pixelY, options).
  • Provide placeStructure(typeId, pixelX, pixelY, state).
  • Provide removeStructure(instanceId).
  • Provide updateStructureState(instanceId, patch).
  • Keep runtime mutation explicit.
  • Do not mutate from renderer.
  • Do not mutate from event handlers.
  • Commands call structure runtime APIs.
  • Snapshot rules.
  • Snapshots must be clone-safe or immutable enough for UI/render reads.
  • Renderer snapshots include only render-relevant fields.
  • Gameplay snapshots include type/state/footprint information.
  • Add unit tests for runtime.
  • Apply and serialize round trip.
  • Snapshot isolation.
  • Placement succeeds on empty cells.
  • Placement rejects occupied footprint cells.
  • Removal frees occupancy.
  • State updates are explicit and preserve unknown state keys.

Phase 4: Occupancy Grid

  • Add occupancy grid ownership inside structureRuntime or a small helper module.
  • Use a typed array for occupancy.
  • 0 means empty.
  • Non-zero value maps to structure runtime index or compact handle.
  • Build occupancy from normalized structures.
  • Rebuild on apply/load.
  • Update on place/remove.
  • Detect overlaps during rebuild.
  • Add occupancy query APIs.
  • isStructureOccupied(pixelX, pixelY).
  • getStructureIdAt(pixelX, pixelY).
  • getOccupiedCells(instanceId).
  • Add tests.
  • Occupancy indexes match footprint masks.
  • Out-of-bounds footprints are rejected or clipped by explicit rule.
  • Removing a structure clears only its cells.
  • Rebuilding occupancy catches sidecar overlaps and skips invalid loaded instances while keeping valid earlier instances.
  • Defer pathfinding integration behind a clear follow-up task unless v1 proof requires blocking.

Phase 5: Render Backend

  • Decide shared renderer naming.
  • Recommended: src/render/mapSpriteRenderer.js for common atlas-backed quad drawing.
  • Structure-specific wrapper: src/render/structureRenderer.js only if needed.
  • Add shader sources for map-space quads.
  • Continuous map-space anchor.
  • Visual width/height in map pixels.
  • Atlas slot lookup.
  • Tint and opacity support.
  • Optional cardinal rotation support can be deferred.
  • Add renderer resource setup.
  • Program creation.
  • VAO/VBO or instance buffer.
  • Placeholder atlas texture upload.
  • Nearest filtering.
  • Correct source sprite vertical orientation through atlas UV packing.
  • Apply first-pass terrain lighting to structures.
    • Sample terrain normal texture at structure fragment map UV.
    • Sample terrain shadow texture for sun/moon attenuation.
    • Sample point-light texture.
    • Use terrain sun/moon/ambient frame lighting params.
  • Add render pass integration.
  • Register structure pass after main terrain.
  • Ensure alpha blending state is set and restored intentionally.
  • Draw only when terrain is shown.
  • Add render snapshot consumption.
  • Structure runtime produces compact render list.
  • Renderer does not know gameplay state.
  • Renderer does not allocate per structure every frame where avoidable.
  • Add focused render unit tests where practical.
  • Buffer packing contains expected instance attributes.
  • Empty render list skips draw.
  • Atlas slot math is deterministic.

Phase 6: Atlas And Assets

  • Add default structure sprite folder.
  • Create assets/sprites/structures/default/.
  • Keep the folder tracked with .gitkeep until real art is committed.
  • Add runtime atlas generation from individual source PNGs.
  • Use per-type spriteSrc paths.
  • Draw source images into fixed atlas slots.
  • Use nearest filtering by default.
  • Preserve whole-source image packing for structures that do not provide explicit source-frame crop metadata.
  • Add default structure metadata.
  • Define slot IDs.
  • Define type-to-sprite mapping.
  • Point map3 proof type at assets/sprites/structures/default/nomadic_tent_01.png.
  • Add graceful fallback behavior.
  • Missing atlas uses placeholder texture.
  • Missing structure sidecar means no structures.
  • Invalid sidecar surfaces a visible startup/map-load error if it should block.
  • Decide whether default atlas lives globally or map-locally.
  • V1 uses individual global/default sprite sources and a runtime-generated atlas.
  • Map-local/scoped source folders can be referenced by structures.json through spriteSrc.

Phase 7: Map Sidecar Load And Save

  • Extend mapSidecarLoader.
  • Load optional structures.json from URL maps.
  • Load optional structures.json from selected folder files.
  • Apply through structureRuntime.applyStructureData.
  • Missing sidecar applies empty structure data.
  • Invalid sidecar errors should be visible on title/map-load status if blocking.
  • Extend mapDataSaveController.
  • Include structures.json in Save All only if we decide authored defaults should save through map data.
  • Consider a separate save action if mutable run structures should not overwrite authored defaults.
  • Add tests.
  • Save All includes or intentionally excludes structures.json per decision.
  • Missing structures sidecar is tolerated.
  • File-folder loading applies structures.
  • Update AI_CONTEXT.md if sidecar behavior is implemented.

Phase 8: Commands

  • Register structure commands in command routing.
  • structure/place.
  • structure/remove.
  • structure/updateState.
  • structure/select if selection is part of v1.
  • Command rules.
  • Commands validate inputs.
  • Commands call structure runtime mutation APIs.
  • Commands return or surface failure reason where practical.
  • Events remain post-change refresh/invalidation only.
  • Add command tests.
  • Place command forwards valid placement and reports success.
  • Place command reports invalid placement without redraw.
  • Remove command routes through structure runtime and reports status.
  • Update command routes through structure runtime and reports status.

Phase 9: Interaction And Selection

  • Decide v1 interaction surface.
  • Debug-only placement through RD.
  • Terrain-click placement mode.
  • Local activity menu action.
  • Recommended v1.
  • RD debug placement/removal first.
  • Defer player-facing placement until structure gameplay is defined.
  • Add selection support if needed.
  • Use nearest-by-type query for first debug selection.
  • Store selected structure ID in UI owner, not structure runtime.
  • Draw selection gizmo through overlay canvas, not structure renderer.
  • Add placement preview if terrain-click placement is included.
  • Show footprint validity.
  • Green valid, red invalid.
  • Keep place mode and cursor preview active after successful placement for repeated authoring.
  • Do not resize fixed HUD/side panels for placement details.
  • V1 uses binary works/does-not-work feedback.
  • Future player-skill levels can expose richer per-cell reasons in the overlay/report.
  • Add tests for interaction helpers where practical.

Phase 10: RD Debug UI

  • Add RD panel controls in the appropriate tab.
  • Use RD > Sprites > Structures for first debug controls.
  • Keep panel content compact per docs/UI_LAYOUT_GRID.md.
  • Controls.
  • Toggle structure render visibility.
  • Select proof structure type.
  • Place at player.
  • Place mode with cursor footprint preview.
  • Select nearest by selected type.
  • Remove selected.
  • Show occupancy debug overlay.
  • Show selected structure state.
  • UI ownership.
  • UI dispatches commands.
  • UI reflects structure runtime snapshots.
  • UI does not mutate structure data directly.
  • Add UI tests if adding standalone runtime helpers.

Phase 11: Gameplay Integration Hooks

  • Define structure capability tags.
  • container.
  • rest.
  • heat.
  • crafting.
  • ritual.
  • blocksMovement.
  • Add minimal query APIs.
  • getStructuresNear(pixelX, pixelY, radiusPx).
  • getStructuresByCapability(capability).
  • getNearestStructureByType(typeId, pixelX, pixelY).
  • Defer deep integrations until needed.
  • Inventory/container runtime integration.
  • Rest/campfire effects.
  • Pathfinding blocking.
  • Event triggers.
  • Audio/lighting coupling.
  • Add tests for query APIs.

Phase 12: Pathfinding Integration

  • Decide whether v1 blocks movement.
  • V1 supports type-gated blocking through blocksMovement.
  • Add pathfinding cost/block integration.
  • If blocking is enabled.
  • Structure runtime exposes isMovementBlocked(pixelX, pixelY).
  • Structure runtime exposes bounded blocked-cell queries for local Dijkstra windows.
  • Pathfinding preview/runtime writes structure obstacles into the local Dijkstra field.
  • RD Pathing exposes structure and terrain diagonal corner-cutting toggles.
  • Route planning either ignores structures or receives a low-res obstacle projection.
  • Movement execution validates target cells against current occupancy.
  • Add tests.
  • Local pathfinding avoids blocking structures.
  • Movement fails or reroutes when target becomes blocked.
  • Non-blocking structures do not affect paths.

Phase 13: Persistence Strategy

  • Separate authored defaults from mutable run state.
  • structures.json as map/scenario defaults.
  • Future savegame state for player-built, damaged, moved, or removed structures.
  • Decide v1 persistence behavior.
  • Authored-only structures loaded from sidecar.
  • Save All writes current structures back as map data for now.
  • Or RD changes are debug/runtime-only until savegame architecture exists.
  • Document decision in AI_CONTEXT.md.
  • Add serialization tests matching the chosen behavior.

Phase 14: Performance And Limits

  • Define initial caps.
  • Maximum structure types per map.
  • Maximum structure instances per map.
  • Maximum footprint dimensions.
  • Maximum visual sprite dimensions in map pixels.
  • Recommended initial caps.
  • Structure types: 256.
  • Instances: 4096 or lower if UI/debug only.
  • Footprint dimensions: 64x64 max.
  • Visual dimensions: 128x128 map pixels max.
  • Add validation for caps.
  • Add performance smoke scenario with many structures.
  • Profile render pass timing if many structures are visible.

Phase 15: Documentation

  • Update docs/SPRITE_DETAILS_RENDERING_DESIGN.md if implementation choices differ from planning.
  • Add docs/STRUCTURE_DATA_CONTRACT.md.
  • Update AI_CONTEXT.md.
  • New structure owner module.
  • New sidecar behavior.
  • Render order.
  • Structure occupancy/pathing behavior.
  • Save/load behavior.
  • Update README.md only if run steps or asset requirements change.
  • Document required asset files if default maps use structures.

Phase 16: Validation

  • Run focused JS syntax checks.
  • node --check src\gameplay\structureDataSerializer.js
  • node --check src\gameplay\structureRuntime.js
  • node --check src\render\mapSpriteRenderer.js
  • node --check src\render\structureRenderer.js if created.
  • Run focused tests.
  • node --test tests\structureDataSerializer.test.js
  • node --test tests\structureRuntime.test.js
  • node --test tests\mapDataSaveController.test.js
  • node --test tests\mapSidecarLoader.test.js if added.
  • Run broader tests after integration.
  • node --test tests\*.test.js
  • Run docs lint.
  • npm run lint:md
  • Manual smoke test.
  • Load default map.
  • Confirm missing structures.json is harmless.
  • Confirm proof structure renders.
  • Confirm occupancy debug matches footprint.
  • Confirm placement rejects overlap.
  • Confirm save/load behavior matches the chosen persistence strategy.

Suggested Implementation Order

  • Phase 1: data contract.
  • Phase 2: serializer and pure tests.
  • Phase 3: runtime owner and tests.
  • Phase 4: occupancy grid and tests.
  • Phase 5: renderer proof with hardcoded render snapshot.
  • Phase 6: atlas/assets fallback.
  • Phase 7: sidecar load/save.
  • Phase 8: commands.
  • Phase 10: RD debug controls.
  • Phase 9: interaction/selection if needed.
  • Phase 11: gameplay query hooks.
  • Phase 12: pathfinding integration only when explicitly needed.
  • Phase 13: persistence strategy before player-built structures.
  • Phase 15: documentation updates.
  • Phase 16: validation.