From aea2c9d56be99764b747ba97806e534cc2440ecb Mon Sep 17 00:00:00 2001 From: super-jalawii Date: Sat, 27 Jun 2026 10:48:00 -0400 Subject: [PATCH] document current world and chunk semantics --- docs/refactor/world-and-chunk-semantics.md | 186 +++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 docs/refactor/world-and-chunk-semantics.md diff --git a/docs/refactor/world-and-chunk-semantics.md b/docs/refactor/world-and-chunk-semantics.md new file mode 100644 index 0000000..d1edf4e --- /dev/null +++ b/docs/refactor/world-and-chunk-semantics.md @@ -0,0 +1,186 @@ +# 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?