worldmaterial

package
v0.0.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 5, 2026 License: MIT Imports: 7 Imported by: 0

README

World material atlas v1

world/material-atlas@1 is the opt-in runtime capability, exported as worldmaterial.Feature, cartridge.FeatureWorldMaterialAtlasV1 and asset.CapabilityWorldMaterialAtlasV1. The game manifest includes the union of its levels' capabilities in its existing sorted, unique version-2 feature list, including world/sectors@1 for compiled worlds. Existing manifests and worlds without this capability retain their released encodings and legacy loading.

A world level carries three fixed logical level.EntryData payloads and, when Layout.Mips is present, one additional combined mip-tail payload:

Symbol Entry Content
LayoutEntry @world/material-layout Canonical UTF-8 JSON placements
AlbedoEntry @world/material-albedo Opaque RGBA QOI, sRGB colorspace (0)
DataEntry @world/material-data Straight RGBA QOI, linear colorspace (1)
MipTailEntry @world/material-mips Optional combined albedo/data QOI, raw linear storage (1)

Level metadata declares "kartyWorldMaterialAtlas":"karty.world-material-atlas@1" (MetadataKey and Schema). A host must reject missing/partial pairs, entries without this metadata, metadata without the entries, or an atlas without a compiled world. These cross-entry/world checks belong to the level consumer; Pair.Validate validates the layout, L0 pair and optional tail together. The tail and the complete eight mips records must be present together. Generic level.Encode/Decode enforce envelope bounds and entry name/kind rules, not this opt-in payload schema or metadata/world consistency.

JSON layout

EncodeLayout emits fields in this exact order, without whitespace:

{"schema":"karty.world-material-atlas@1","width":352,"height":352,"materials":[{"materialId":0,"x":48,"y":48,"width":256,"height":256,"gutter":16}]}
  • Top-level fields: schema (string), width, height (integer pixel dimensions), materials (array of placement objects), followed by optional mips.
  • Placement fields: materialId (uint32 level-local material ID), x, y (integer inner-tile origin), width, height (256), gutter (16 pixels), followed by optional strengths.
  • IDs are unique, in first-surface-use order: floor, ceiling, then walls per sector in compiled-world order. ID 0 denotes the opaque default material; it is included only if used. IDs need not be contiguous or numerically sorted.
  • A nonzero ID is the same level-local source texture ID used by the compiled world's surface and level.TextureEntryName(id), not a dense slot index or cartridge-wide asset ID. The builder requires that source texture to exist, decode correctly and be opaque. Default ID 0 needs no source texture.
  • There are 1–196 materials. Slots are row-major in a 14-column grid, with 288-pixel cells and a 32-pixel outer inset. For zero-based slot i, inner origin is (48 + (i % 14)*288, 48 + (i / 14)*288).
  • Dimensions are min(count,14)*288 + 64 by ceil(count/14)*288 + 64, up to 4096 by 4096. Each tile's surrounding gutter is part of its cell. Slots, dimensions and gutters must match exactly; arbitrary packing is not v1.
  • DecodeLayout accepts only the bytes EncodeLayout would emit, bounded to 32 KiB. Unknown, duplicate, missing, reordered fields, alternate numeric spelling, whitespace and trailing data are rejected. This is machine-generated JSON, not an author-editable source format.

NewLayout, Layout.Validate, EncodeLayout, and DecodeLayout define this layout contract. Pair{Layout, Albedo, Data, MipTail} groups in-memory encoded bytes; its Go JSON representation is not the public level wire format. Validation does not transfer or clone caller-owned slices. Invalid input returns ErrAtlas.

Optional mip tail and strengths

NewMipLayout(layout) clones the placements and controls, preserves every L0 rectangle, and adds all eight L1–L8 records. Each canonical record has fields level, size, gutter, y, columns in that order. For level n (1–8), size = 256 >> n, gutter = max(1,16 >> n), and cell = size + 2*gutter. For count materials, columns = min(2*count, floor(4096/cell)). The first band begins at y=0; each subsequent band begins after the previous band's ceil(2*count/columns)*cell padded height. Albedo/data cells are interleaved in L0 material order: cell index 2*slot is albedo, 2*slot+1 is raw data. The inner origin is (index%columns*cell+gutter, y+index/columns*cell+gutter). MipRect(level,slot,data) returns this rectangle. All records, including the final 1×1 level with its one-pixel gutter, are required; partial or alternate packing is invalid.

MipDimensions() returns the maximum band width and sum of band heights. At 196 materials the padded tail is 4086×2723; each dimension remains at most 4096 for every supported count. A single extra image contains both region kinds, fitting the engine's fourth shader sampler. Its QOI header is RGBA and linear (1) because it is raw mixed storage: albedo cells still contain sRGB encoded bytes, while data cells retain linear RG normal XY, B height and A AO. Albedo interiors/gutters are opaque; data AO may be zero. Unused tail padding is charged to the decoded allocation even though it is not sampled. An absent mips field and absent tail retain the exact L0-only layout encoding.

An optional placement strengths object contains all four fields in this order: normal, height, ao, rim. Each is a finite float64 in [0,4]. Omitting the object means 1 for every effect; explicit 0 disables that effect. Normal scales tangent XY before reconstructing/normalizing Z. Height scales bounded relief. AO applies clamp(1-strength*(1-AO),0,1) to ambient only. Rim scales the restrained direct-light rim; it does not imply specular gloss. Controls change layout metadata independently of generated image bytes, so artistic adjustments reuse the Materialize cache. Individual valid controls must still fit the shared 32 KiB encoded-layout bound; excessively verbose high-precision controls across a large atlas can exceed it and are rejected.

The engine retains strengths in metadata bytes: q=min(254,round(value*64)) below 3.984375; values at or above that endpoint midpoint use reserved byte 255. Bytes 0–254 decode as q/64, and byte 255 decodes as exactly 4. Defaults bypass repacking and retain previous metadata bytes. Thus most values use a 1/64 grid; the final representable interval is 1/32, with maximum nearest-value error 1/64. When any material has a nondefault rim strength, the normal buffer's B rim mask stores height*rim/4 in eight bits for all materials and the resolve scales it by four. This reduces rim-mask precision for default-strength materials in that same atlas; it changes neither normal XY nor geometric depth.

Build and load responsibilities

The native Go CLI packs/resamples albedo tiles and extrudes their gutters at build time, then invokes the managed Materialize executable once on the completed atlas. It packs OpenGL tangent normal X/Y into R/G, material height into B, and ambient occlusion into A. Normal byte 128 is zero; signed decode is clamp((byte-128)/127, -1, 1). Height/AO are normalized unsigned bytes. A is data, not opacity, so uploads must not premultiply RGB by AO. The CLI restores generated gutters and preserves the L0 QOI pair. It generates each material's mip interiors independently, then extrudes that level's own gutters; it never downsamples across atlas material boundaries. Albedo averages in linear RGB and encodes sRGB afterward. Normals decode to normalized 3D vectors, average and normalize before XY encoding, preserving exact neutral (128,128). Height/AO use linear averages; AO remains straight data even when zero. The complete 256-to-1 chain is identified by the build/cache generation revision.

Runtime loads these payloads/layout directly; it does not repack or regenerate material maps. Both images must have exactly the layout dimensions and RGBA headers with their respective colorspaces. Pair.Validate preflights every bounded QOI allocation, including the padded tail, before any full stream decoding and rejects nonopaque albedo pixels, including gutters, margins and unused slots; data alpha may take any byte value. QOI decoding checks operation boundaries and the exact pixel count without allocation before invoking the codec, so malformed runs cannot grow pixel buffers beyond their declared bounds. The maximum decoded L0 pair is 128 MiB; the maximum pair plus padded tail is 178,722,440 bytes. Consumers account for all three images alongside other textures under the shared 256 MiB decoded-texture budget. Level entry/envelope encoded-size limits remain unchanged and can reject otherwise-valid large pairs.

There are no filesystem paths, URLs or external texture references in this contract: entry names are fixed logical keys, never filenames. CLI source/cache path confinement and Materialize output validation remain CLI responsibilities. Consumers additionally check that layout IDs and order match all compiled world surfaces, with no missing or unused placements; the SDK atlas package cannot validate a world it is not given. Source textures used independently by sprites or UI remain separate level texture entries; the pair does not replace those references. Envelope validation rejects duplicate numeric texture IDs even when distinct hexadecimal entry names alias the same ID. Payload validation cannot prove Materialize execution, correct extrusion or channel-map provenance, nor distinguish already-premultiplied data from valid channel bytes; those remain build-pipeline and upload responsibilities.

These optional extensions belong to the unreleased SDK 0.0.8 candidate. Subsequent incompatible layout/channel changes require a new schema and capability. Version 1 does not modify released world, level-envelope, or cartridge wire versions.

Documentation

Overview

Package worldmaterial defines the build-time world material atlas contract.

Index

Constants

View Source
const (
	Feature        = "world/material-atlas@1"
	Schema         = "karty.world-material-atlas@1"
	MetadataKey    = "kartyWorldMaterialAtlas"
	LayoutEntry    = "@world/material-layout"
	AlbedoEntry    = "@world/material-albedo"
	DataEntry      = "@world/material-data"
	MipTailEntry   = "@world/material-mips"
	MipLevels      = 8
	TileSize       = 256
	Gutter         = 16
	CellSize       = TileSize + 2*Gutter
	GridSize       = 14
	Inset          = 32
	MaxMaterials   = GridSize * GridSize
	MaxLayoutBytes = 32 * 1024
)

Variables

View Source
var ErrAtlas = errors.New("world material atlas is invalid")

Functions

func EncodeLayout

func EncodeLayout(l Layout) ([]byte, error)

EncodeLayout emits the only JSON representation accepted by DecodeLayout.

Types

type Layout

type Layout struct {
	Schema    string     `json:"schema"`
	Width     int        `json:"width"`
	Height    int        `json:"height"`
	Materials []Rect     `json:"materials"`
	Mips      []MipLevel `json:"mips,omitempty"`
}

Layout maps level-local material IDs to a canonical row-major atlas. It has no paths: the paired payloads use the fixed logical level entry names above.

func DecodeLayout

func DecodeLayout(encoded []byte) (Layout, error)

DecodeLayout rejects unknown/duplicate fields, trailing bytes and noncanonical JSON (including whitespace, field reordering and alternative number syntax).

func NewLayout

func NewLayout(ids []uint32) (Layout, error)

NewLayout preserves the supplied unique IDs; ID zero is the opaque default material. Callers derive their first-use order from the compiled world: floor, ceiling, then walls per sector. This function has no world to check.

func NewMipLayout

func NewMipLayout(l Layout) (Layout, error)

NewMipLayout adds the complete L1-L8 chain, preserving the exact L0 geometry. It clones placements and strength values, retaining caller slice ownership.

func (Layout) MipDimensions

func (l Layout) MipDimensions() (width, height int)

MipDimensions returns the padded allocation dimensions, or zero when absent or malformed. It does not use untrusted record arithmetic.

func (Layout) MipRect

func (l Layout) MipRect(level, slot int, data bool) (Rect, error)

MipRect returns an interior region in the combined tail. Level is 1-8 and slot is the zero-based index in Materials; data selects the raw RGBA region.

func (Layout) Validate

func (l Layout) Validate() error

type MipLevel

type MipLevel struct {
	Level   int `json:"level"`
	Size    int `json:"size"`
	Gutter  int `json:"gutter"`
	Y       int `json:"y"`
	Columns int `json:"columns"`
}

MipLevel records one canonical shelf band. Cells contain an albedo tile then a raw data tile for each material in the L0 first-use order. Coordinates and dimensions are derived rather than repeating hundreds of per-material rects.

type Pair

type Pair struct {
	Layout  Layout
	Albedo  []byte
	Data    []byte
	MipTail []byte
}

Pair groups encoded QOI bytes with their shared layout; callers retain slice ownership. It is not a serialized level format: levels carry three entries and an optional fourth mip tail. Tail albedo cells hold sRGB bytes; raw data cells hold the linear channels below. The combined tail has a linear raw-storage QOI tag. Albedo is opaque sRGB RGBA. Data uses straight linear RGBA channels: RG OpenGL tangent normal XY (128 is zero, signed decode clamps (byte-128)/127 to [-1,1]), B height, A ambient occlusion, NOT opacity.

func (Pair) Validate

func (p Pair) Validate() error

Validate preflights every allocation before completely decoding any QOI.

type Rect

type Rect struct {
	MaterialID uint32     `json:"materialId"`
	X          int        `json:"x"`
	Y          int        `json:"y"`
	Width      int        `json:"width"`
	Height     int        `json:"height"`
	Gutter     int        `json:"gutter"`
	Strengths  *Strengths `json:"strengths,omitempty"`
}

Rect describes the inner tile, excluding its extruded gutter. MaterialID is the compiled world's level-local source texture ID, not a dense atlas slot or cartridge-wide asset ID. Zero denotes the opaque default material.

type Strengths

type Strengths struct {
	Normal float64 `json:"normal"`
	Height float64 `json:"height"`
	AO     float64 `json:"ao"`
	Rim    float64 `json:"rim"`
}

Strengths scales material effects independently. An omitted Strengths pointer means one for every channel; explicit zero disables that effect.

func (Strengths) Validate

func (s Strengths) Validate() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL