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

5.8 KiB

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?