1
0
Fork 0
Code Issues Pull requests Projects Releases Packages Wiki Activity Actions Pages
Worldshaper/docs/refactor/world-and-chunk-semantics.md
2026-06-27 10:48:00 -04:00

186 lines
5.8 KiB
Markdown

# 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?