# World And Chunk Semantics This document describes the **current** `WorldShaper` world/chunk behavior as of Arc 1. It is a compatibility and refactor aid, not an endorsement of the long-term model. ## Scope These notes summarize the behavior currently implemented in: - `src/worldChunking.ts` - `src/components/worldshaperShared.ts` - regression tests in: - `src/worldChunking.test.ts` - `src/components/worldshaperShared.test.ts` ## Coordinate Model ### Chunk Dimensions - chunk width and height are normalized with `normalizeChunkDimension` - values are floored to integers - invalid values fall back to `DEFAULT_WORLD_CHUNK_SIZE` - normalized dimensions are clamped to a minimum of `1` ### World To Chunk Coordinates - chunk coordinates use floor division - this applies on both positive and negative world coordinates - examples with chunk size `32`: - `0 -> chunk 0` - `31 -> chunk 0` - `32 -> chunk 1` - `-1 -> chunk -1` - `-33 -> chunk -2` This means negative coordinates behave as mathematical grid cells, not truncation toward zero. ### World To Local Coordinates - local coordinates are derived from the resolved chunk coordinate - formula: `world - (chunk * chunkSize)` - result stays in the half-open range `[0, chunkSize)` - examples with chunk size `32`: - `31 -> local 31` - `32 -> local 0` - `-1 -> local 31` - `-33 -> local 31` ### Local To World Coordinates - formula: `(chunkCoord * chunkSize) + localCoord` - examples with chunk size `32`: - `chunk -2, local 31 -> world -33` ### Address Resolution `resolveWorldChunkAddress` returns: - `chunkX` - `chunkY` - `localX` - `localY` - `chunkKey` in `x:y` form - `fileName` in `x_y.json` form ## Chunk Identity And Storage - chunk keys use `buildChunkKey(chunkX, chunkY)` - chunk filenames use `buildChunkFileName(chunkX, chunkY)` - filenames preserve negative signs, for example `-3_4.json` ## Empty Chunk Defaults `createEmptyChunk` currently creates: - schema version `1` - top-level `backgroundTileId` - `roomLayers[0]` filled with `.` characters - `roomLayers[1]` filled with spaces - empty `heightLayers` - empty `instances` This establishes the current meaning that: - `.` in layer `0` is the default empty background cell encoding - spaces in non-background layers represent empty overlay cells ## Background Tile Behavior `getMapBackgroundTileId` currently resolves background tiles in this order: 1. top-level `backgroundTileId` 2. legacy nested `tiles.backgroundTileId` 3. empty string fallback Arc 1 should treat the nested `tiles.backgroundTileId` shape as compatibility baggage. ## Room Layer Semantics `parseRoomLayers` currently does the following: - parses `record.roomLayers` if present - ignores malformed entries - requires a numeric `layer` - sorts output by ascending `layer` - normalizes row sizes to map bounds - uses `.` fill for layer `0` - uses space fill for non-zero layers - filters blank `instanceIds` If no explicit layer `0` exists: - a synthetic layer `0` is created from top-level `record.rows` If there are no usable layers at all: - a single synthetic layer `0` is returned from top-level `record.rows` ### Current `zIndex` Behavior - layer `0` always gets `zIndex: 0` - non-zero layers preserve provided `zIndex` if present - otherwise non-zero layers default to `0` - non-zero `zIndex` values are clamped into `[0, 5]` This is an important current behavior to preserve during Arc 1, but it looks at least partly accidental because non-zero layers do **not** derive `zIndex` from layer number. ## Height Patch Semantics `parseHeightLayers` currently treats height patches as sparse row-based overlays. ### Input Interpretation - `rows` are string arrays - `.` is interpreted as empty space - empty margins are trimmed away - patches are clipped to map bounds ### Normalization Rules - duplicate patch ids are dropped after the first occurrence - `z` is clamped to a minimum of `1` - `x` and `y` are floored to integers - rows completely outside bounds become empty - leading/trailing empty rows are removed - leading/trailing empty columns are removed by cropping to occupied content - trailing whitespace inside retained rows is stripped ### Resulting Meaning The current height patch encoding behaves more like a cropped sparse stamp than a fixed-size tile layer. That is useful to document now because any future redesign needs to decide whether this sparse behavior is intentional or just a side effect of the current editor implementation. ## Chunk Instance Semantics Arc 1 has not redesigned chunk instances yet, but the current shape is: - `id` - optional `templateId` - `layer` - `x` - `y` - `record` The semantics of `templateId + record` are still under-specified and should be treated as a known redesign target for later arcs. ## Likely Accidental Or Under-Specified Behavior These behaviors are currently preserved, but should not be treated as settled architecture: - non-background layers default to `zIndex: 0` instead of deriving depth from layer number - duplicate height patch ids are silently dropped after the first occurrence - top-level `rows` still act as a fallback source for synthesized background layers - nested `tiles.backgroundTileId` is still accepted - layer `0` empties use `.` while non-zero layer empties use spaces - chunk instance meaning is still implicit rather than explicitly modeled ## Arc 2+ Questions - Should negative-coordinate behavior remain floor-based, or should world addressing be modeled differently at a higher level? - Should layer depth be derived from `layer`, `zIndex`, or a clearer world-space model? - Should height data remain sparse text rows, or become a more explicit numeric structure? - Should background tiles stay top-level, or belong to a clearer terrain/base-layer contract? - What is the correct long-term meaning of chunk instances, templates, and per-instance overrides?