worldlightmap

package
v0.0.11 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 12 Imported by: 0

README

Build-generated world lightmap layout

world/lightmaps@1 is a capability available in SDK 0.0.8. The public CLI generates @world/lightmaps/layout as an EntryData entry in the existing KLD envelope. Metadata contains kartyWorldLightmaps: "karty.world-lightmaps@1". Cartridges require this feature together with world/sectors@1. Existing world, level-envelope and cartridge versions are unchanged; absent layouts preserve existing packaging. Older SDK manifests remain unchanged.

The canonical JSON Layout has schema karty.world-lightmaps@1, algorithm 1, a lower-case SHA-256 geometry digest, requested texels per world unit, padding, pages, charts and semantic bindings. This version permits exactly one square 512 or 1024 page, at most 1,048,576 allocated texels and 8,192 charts. Layout payloads are bounded to 8 MiB and remain subject to the envelope aggregate bound. Density is finite in (0,64]; padding is 1–16 pixels. Builders never silently lower density to fit. Oversized charts or a full page are explicit errors.

Charts contain orthonormal world-space normal, tangent and bitangent vectors, normalized affine U/V planes and integer receiver/padded rectangles. Rectangles are [minimumX, minimumY, maximumX, maximumY], with exclusive maximums. The receiver rectangle is exactly the padded rectangle inset by padding. Receiver bounds start at texel centres, and each axis spans at least two texels to give thin steps interior raster samples. No material UV is reused or changed.

Semantic binding kinds are sector-floor, sector-ceiling, sector-wall, solid-top, solid-bottom, solid-side. index addresses the sector or solid array; edge is the directed wall/footprint edge, or -1 for caps. chart is a zero-based chart index, or -1 only for a fully open wall with no opaque receiver. Every semantic face has exactly one binding, in sector floor/ceiling/wall order followed by solid top/bottom/side order. Every solid side has its own identity; the repeating Solid.SideUV field does not affect these bindings.

Algorithm 1 merges coplanar sector floors/ceilings only across coincident, reciprocal ordinary portals. Equal coordinates in disconnected spaces do not merge. Opaque bands around partial portals follow the shared pure world.WallProfile crossing/controller rules; the doorway receives no opaque geometry. Solid caps and sides remain separate charts. Deterministic rectangle packing sorts by descending height, descending width and stable chart identity. Charts are not rotated, and their reserved rectangles cannot overlap.

The geometry digest covers stable sector/solid identities, coordinates, elevation planes, directed portal indexes and reciprocal wall indexes. Actor state, materials, material UVs and lighting do not affect it. Geometry changes invalidate the layout. Light movement invalidates runtime lighting rather than the geometry layout. Consumers validate complete bounds, digest, bases, affine maps, receiver coverage and semantic binding identities before GPU allocation. Decoding also rejects unknown, duplicate, missing, null, trailing and noncanonical records through exact canonical JSON comparison.

Optional runtime_bake has two explicitly versioned encodings. A single visibility recipe retains its existing bytes and behavior:

{"encoding":"point-visibility@1","light_id":"court-light","shadow_size":512}

An ordered direct-light recipe selects one to eight unique authored IDs:

{"encoding":"direct-rnm3@1","light_ids":["brazier-north","brazier-south"],"shadow_size":512}

light_id and light_ids are mutually exclusive. Visibility requires the single nonempty ID and no list; direct RNM requires the nonempty list and no single field. IDs must exist in Document.Lighting.Lights; selection order is preserved, with no implicit first-light selection. Shadow size is 32–512. Omission packages reusable coordinates without requesting a runtime bake. Unknown encodings, duplicate selections, explicit empty/null fields and noncanonical fields fail complete validation.

Both runtime recipes require all sectors to belong to one ordinary reciprocal physical component, every selected emitter to be inside its floor/ceiling volume, and every solid to have a host receiver association. Association uses conservative XY bounding-box overlap including tangent boundaries; it does not impose a new exact polygon or height restriction on solid placement. Builders validate the complete selection before publication. Layout-only data still accepts disconnected/transformed spaces, outside lights and unassociated solids. point-visibility@1 stores static shadow visibility, leaving selected light colour/falloff and material normal response live at runtime.

The runtime direct-rnm3@1 recipe stores static direct diffuse lighting as three horizontal RGBM coefficient tiles per logical page, with matching chart coordinates in each tile. The logical page bounds above do not count those physical coefficient copies. A 1024 logical page therefore has a 3072×1024 RGBA8 image (12 MiB); additional scratch and cached recipes require separate runtime accounting. No RGBM range field is authored: range = max(1, 3 Σ max(R,G,B)) over the selected linear RGB light colours. Each coefficient encodes RGB = C/(range*M) and A = M, with quantized M rounded upward and at least 1/255 for covered texels, including black. Alpha zero means uncovered. Decode each filter tap as RGB*A*range before interpolation; normalize filter weights over covered taps using binary alpha coverage rather than weighting by the RGBM multiplier. Dilation preserves covered samples and cannot cross chart boundaries.

In chart coordinates (tangent, bitangent, normal), the RNM basis is:

  • B0 = (√(2/3), 0, 1/√3)
  • B1 = (-1/√6, 1/√2, 1/√3)
  • B2 = (-1/√6, -1/√2, 1/√3)

For receiver normal N and unit receiver-to-light direction L, use the existing squared Half Lambert function H(N,L) = (0.5 dot(N,L)+0.5)². For each selected light, wi = H(Bi,L) and accumulate Ci = Tint * attenuation² * visibility * 3*H(N,L)*wi/Σwi. Here attenuation = max(0, 1-distance²/radius²). Geometric backfaces have zero visibility. Runtime material normal n uses weights max(dot(n,Bi),0)², normalized by their sum, to combine the decoded coefficients. A neutral material normal reproduces the existing Half Lambert response before RGBM quantization. This is a Source-inspired directional approximation, not radiosity or an exact integration of incident lighting.

Only static environment direct diffuse is baked by the runtime recipe. Once the complete atlas is published, its selected environment lights are excluded from runtime diffuse but retain their front-facing live rim contribution. The combined directional atlas has no per-light occlusion for that rim. Selected lights still illuminate actors at runtime. Ambient cube and material texture AO remain runtime terms, applied once; the bake includes neither ambient nor material AO. Camera movement reuses the atlas; changes to selected colour, position or radius invalidate it while retaining the geometry layout. Partial atlases are never published. Neither runtime encoding supplies bounced lighting, moving actor shadows or transformed-portal light transport. Multipage layouts require a separate versioned decision. Completed direct or offline-bounced atlas images use the prebake extension below.

Compile, Surfaces, GeometryDigest, Validate, Encode and Decode are pure public Go helpers. They neither import private engine packages nor triangulate renderer meshes. The host associates retained triangle semantic identities with these bindings and evaluates the affine planes per vertex.

Optional completed prebake, version 1

SDK 0.0.8 also defines world/lightmaps-prebaked@1, requiring world/lightmaps@1, world/lighting@1 and world/sectors@1. This additive extension preserves all existing layout and runtime-recipe bytes. It packages one completed three-tile RNM atlas without rebaking it during startup. The existing algorithm 1 stores direct lighting only. Algorithm 2 adds the explicit offline diffuse-bounce producer described below, while retaining the same direct-rnm3@1 coefficient encoding and image layout. No released schema or algorithm-1 canonical spelling changes.

A level carries both fixed EntryData entries @world/lightmaps/prebake and @world/lightmaps/prebake-rnm3, plus metadata kartyWorldLightmapPrebake: "karty.world-lightmap-prebake@1". Both entries and the marker are mutually required, alongside the existing complete layout. The image is QOI RGBA tagged linear (1), with raw straight RGBM channels: never premultiply by its multiplier alpha. Width is three times the logical page width; height is the page height. The encoded image is bounded to 8 MiB and the existing level envelope remains bounded to 16 MiB. A valid 1024-page image costs 12 MiB decoded; QOI data with insufficient compression can exceed the encoded bound and must fail explicitly. This extension does not raise level or shared decoded-texture budgets.

The algorithm-1 canonical manifest schema karty.world-lightmap-prebake@1 has required fields in this order: schema, algorithm (1), encoding (direct-rnm3@1), layout_sha256, bake_sha256, image_sha256, image_bytes, width, height, rgbm_range. It is bounded to 16 KiB. All digests are lowercase SHA-256. layout_sha256 hashes the exact canonical layout JSON, including charts, bindings, coordinates and the ordered recipe. bake_sha256 hashes canonical JSON of {schema, algorithm, layout_sha256, lights} in that field order, with lights containing the selected complete world.PointLight records in recipe order. This binds IDs, position, linear colour and radius while excluding unselected lights, ambient, actors and material AO. rgbm_range is the exact derived float64 range described above, not an authoring control. image_sha256 and image_bytes identify the exact lossless QOI bytes.

NewPrebake derives validated algorithm-1 metadata from a completed direct image; EncodePrebake emits a canonical manifest and DecodePrebake accepts only that spelling. PrebakePair.Validate checks the complete layout, lighting identity, dimensions, channel/colourspace tags, every manifest field and the full bounded QOI stream before decoded image allocation. Unknown, duplicate, missing, null, reordered or noncanonical fields fail. Validation cannot prove lighting quality or honest producer provenance; signed/hashed level packaging protects the final artifact. Pairs retain caller ownership of image slices; consumers must clone them before mounted publication. The host rejects stale packaged inputs rather than silently rebuilding a different atlas.

Offline diffuse-bounce producer, algorithm 2

NewOfflinePrebake accepts a completed image and OfflineBakeInputs, producing the same manifest schema with algorithm: 2 and producer: "cpu-rnm3-pathtrace@1". Additional canonical fields follow rgbm_range in this order: producer, samples, bounces, seed, reflectance_sha256, surface_sha256. Samples are 1–256, bounces are 0–4 and seed is an unsigned 64-bit integer. Zero bounces and zero seed are valid and omitted from the canonical manifest; explicit zero fields are not canonical. Worker count does not affect identity because scheduling must not change pixels.

The public bake/worldlightmap package provides this deterministic CPU producer. It rasterizes actual receiver polygons, uses a triangle BVH for point-light visibility and traces bounded cosine-weighted diffuse paths. Primary direct coefficients retain the existing Half-Lambert contract. Bounced transfer uses Lambertian cosine response and actual nearest albedo texture pixels evaluated with compiled affine projections and triplanar weights, decoded from sRGB to linear RGB. Reflection channels are bounded to MaxDiffuseReflectance = 0.95. Normal/height/AO maps do not alter transport in this slice; material normals still combine the three directional coefficients at runtime. This is a Source-inspired RNM implementation with diffuse indirect light, not an exact reproduction of Valve's bake equations.

For b bounces, OfflineRGBMRange derives the storage range max(1, 3 Σ max(R,G,B) * Σ(k=0..b) 0.95^k) over selected lights. The manifest must contain that exact float64 value. This reserves bounded indirect energy; it does not guarantee a particular brightness. The RGBM channel, coverage, dimensions, chart-isolated dilation, QOI and memory bounds remain the same.

surface_sha256 is OfflineSurfaceDigest of the canonical compiled document with contents and lighting removed, committing geometry, topology, material identities and compiled UVs. reflectance_sha256 is supplied by the public baker's ReflectanceDigest, committing used decoded albedo pixels and their semantic material/UV assignments. The CLI rechecks reflectance against source before packaging; a host lacks original source images and can verify the surface identity and recorded digest, but cannot independently recompute those source pixels.

Algorithm-2 bake_sha256 hashes canonical JSON of {producer, direct_sha256, surface_sha256, reflectance_sha256, samples, bounces, seed} in that field order. direct_sha256 is the existing algorithm-1 bake identity for the same layout and selected light records. The identity therefore includes selected lighting and all offline transport controls without including ambient, actor state or material AO. Pair validation derives and checks the entire expected manifest and image hash; it cannot prove solver quality or honest producer provenance.

A valid algorithm-2 atlas replaces selected static environment diffuse with combined direct and indirect light. Live front-facing rim, actor lighting, ambient cube and material AO retain their existing runtime behavior. The combined atlas has no per-light rim visibility, moving shadows, spatial actor probes or transformed-portal transport. The first CPU producer has hard point shadows and finite-sample noise; it does not supply soft area-light sampling. The optional denoised producers below reduce indirect grain. If no completed bake is packaged, the original runtime direct recipe remains available. Malformed or stale packaged prebakes are errors, rather than permission to silently use an unrelated fallback atlas.

The public codec/qoi.Validate utility returns bounded metadata after header and complete stream validation without allocating decoded pixels. Inspect remains header/end-marker inspection; Decode reuses Validate before its pixel allocation. This permits preflight of a complete shared image budget without decoding images twice.

Versioned decision: indirect denoising producers v1

For the next Go module and host release after v0.0.8, algorithm 2 also accepts cpu-rnm3-pathtrace-atrous-low@1 and cpu-rnm3-pathtrace-atrous-medium@1. The producer string commits the complete filter recipe and preset; it participates in BakeSHA256. No JSON fields, RNM encoding, coefficient count or runtime composition semantics change. OfflineBakeInputs.Denoise selects the producer; empty/off preserves the original cpu-rnm3-pathtrace@1 manifest and pixel contracts. Unknown producers are still rejected. Older hosts reject these new producers and require an updated host; they must not silently reinterpret a denoised artifact as @1. See the baker package for the filtering and memory contract.

Documentation

Overview

Package worldlightmap defines the independently versioned, build-generated static receiver layout. It has no renderer or private engine dependencies.

Index

Constants

View Source
const (
	Schema                  = "karty.world-lightmaps@1"
	Algorithm               = uint32(1)
	EntryName               = "@world/lightmaps/layout"
	MetadataKey             = "kartyWorldLightmaps"
	Feature                 = cartridge.FeatureWorldLightmapsV1
	MaxEncodedSize          = 8 * 1024 * 1024
	MaxCharts               = 8192
	MaxTexels               = 1024 * 1024
	MaxBakeLights           = 8
	PointVisibilityEncoding = "point-visibility@1"
	DirectRNMEncoding       = "direct-rnm3@1"
)
View Source
const (
	OfflinePrebakeAlgorithm = 2
	OfflinePrebakeProducer  = "cpu-rnm3-pathtrace@1"
	MaxOfflineSamples       = 256
	MaxOfflineBounces       = 4
	MaxDiffuseReflectance   = .95
)
View Source
const (
	PrebakeSchema          = "karty.world-lightmap-prebake@1"
	PrebakeAlgorithm       = 1
	PrebakeEntryName       = "@world/lightmaps/prebake"
	PrebakeImageEntryName  = "@world/lightmaps/prebake-rnm3"
	PrebakeMetadataKey     = "kartyWorldLightmapPrebake"
	PrebakeFeature         = cartridge.FeatureWorldLightmapsPrebakedV1
	MaxPrebakeManifestSize = 16 * 1024
	MaxPrebakeImageSize    = level.MaxEntrySize
)

Variables

View Source
var ErrLayout = errors.New("world lightmap layout is invalid")

Functions

func Encode

func Encode(layout Layout, document *world.Document) ([]byte, error)

func EncodePrebake

func EncodePrebake(pair PrebakePair, layout Layout, document *world.Document) ([]byte, error)

EncodePrebake returns a canonical manifest only after complete pair validation.

func GeometryDigest

func GeometryDigest(document *world.Document) (string, error)

GeometryDigest includes stable semantic identity, boundary topology and elevation geometry. Actor state, lighting, materials and UVs do not affect it.

func OfflineDenoiseMode added in v0.0.9

func OfflineDenoiseMode(producer string) (string, error)

OfflineDenoiseMode rejects unknown producer versions before image allocation.

func OfflineDenoiseProducer added in v0.0.9

func OfflineDenoiseProducer(mode string) (string, error)

OfflineDenoiseProducer versions the complete filter, including its preset. Empty and off preserve the original producer and its exact manifest bytes.

func OfflineRGBMRange

func OfflineRGBMRange(layout Layout, document *world.Document, bounces int) (float64, error)

OfflineRGBMRange reserves a conservative finite range for direct lighting plus bounded diffuse transport. Each bounce attenuates maximum radiance by MaxDiffuseReflectance; three coefficient lobes need the same factor as the direct producer. This is a storage bound, not a promised illumination level.

func OfflineSurfaceDigest

func OfflineSurfaceDigest(document *world.Document) (string, error)

OfflineSurfaceDigest binds material identities and authored projection data in addition to geometry. Actor state and artistic ambient are not transport inputs. The original albedo pixels are checked separately by the public CLI.

func Validate

func Validate(layout *Layout, document *world.Document) error

Validate checks every record, rectangle, semantic binding and receiver before a caller can allocate GPU resources. The geometry digest prevents stale UVs.

Types

type Binding

type Binding struct {
	Kind  string `json:"kind"`
	Index int    `json:"index"`
	Edge  int    `json:"edge"`
	Chart int    `json:"chart"`
}

Binding associates a world surface with its receiver chart. Index addresses the world sector or solid array. Edge is -1 for caps, and addresses the directed wall or footprint edge otherwise. Chart -1 explicitly marks a wall with no opaque receiver. Array order is semantic discovery order.

type Chart

type Chart struct {
	ID           int        `json:"id"`
	Page         int        `json:"page"`
	Rect         [4]int     `json:"rect"`
	ReceiverRect [4]int     `json:"receiver_rect"`
	Normal       world.Vec3 `json:"normal"`
	Tangent      world.Vec3 `json:"tangent"`
	Bitangent    world.Vec3 `json:"bitangent"`
	UPlane       [4]float64 `json:"u_plane"`
	VPlane       [4]float64 `json:"v_plane"`
}

Chart describes a receiver's atlas placement and basis. Rectangles are [minimumX,minimumY,maximumX,maximumY] in page pixels, maximums exclusive. Plane values are normalized page coordinates.

type Layout

type Layout struct {
	Schema         string       `json:"schema"`
	Algorithm      uint32       `json:"algorithm"`
	GeometrySHA256 string       `json:"geometry_sha256"`
	TexelsPerUnit  float64      `json:"texels_per_unit"`
	Padding        int          `json:"padding"`
	Pages          []Page       `json:"pages"`
	Charts         []Chart      `json:"charts"`
	Bindings       []Binding    `json:"bindings"`
	RuntimeBake    *RuntimeBake `json:"runtime_bake,omitempty"`
}

func Compile

func Compile(document world.Document, options Options) (Layout, error)

Compile discovers and packs unique receiver coordinates without triangulating sectors or importing runtime code. Algorithm 1 merges only coplanar floors and ceilings across ordinary reciprocal portals, never across transformed or disconnected spaces. Walls and solid sides retain semantic chart identities.

func Decode

func Decode(encoded []byte, document *world.Document) (Layout, error)

type OfflineBakeInputs

type OfflineBakeInputs struct {
	Samples, Bounces  int
	Seed              uint64
	ReflectanceSHA256 string
	RGBMRange         float64
	Denoise           string // empty/off, low or medium; encoded in the producer identity
}

OfflineBakeInputs identifies the public deterministic CPU producer. Worker count is deliberately absent: changing parallelism must not change pixels.

type Options

type Options struct {
	TexelsPerUnit                          float64
	PageSize, Padding, MaxPages, MaxTexels int
	Light                                  string
	Lights                                 []string
	ShadowSize                             int
}

type Page

type Page struct {
	Width  int `json:"width"`
	Height int `json:"height"`
}

type PrebakeManifest

type PrebakeManifest struct {
	Schema            string  `json:"schema"`
	Algorithm         int     `json:"algorithm"`
	Encoding          string  `json:"encoding"`
	LayoutSHA256      string  `json:"layout_sha256"`
	BakeSHA256        string  `json:"bake_sha256"`
	ImageSHA256       string  `json:"image_sha256"`
	ImageBytes        int     `json:"image_bytes"`
	Width             int     `json:"width"`
	Height            int     `json:"height"`
	RGBMRange         float64 `json:"rgbm_range"`
	Producer          string  `json:"producer,omitempty"`
	Samples           int     `json:"samples,omitempty"`
	Bounces           int     `json:"bounces,omitempty"`
	Seed              uint64  `json:"seed,omitempty"`
	ReflectanceSHA256 string  `json:"reflectance_sha256,omitempty"`
	SurfaceSHA256     string  `json:"surface_sha256,omitempty"`
}

PrebakeManifest binds one completed direct RNM image to exact layout and selected lighting inputs. Its canonical JSON is the public manifest entry.

type PrebakePair

type PrebakePair struct {
	Manifest PrebakeManifest
	Image    []byte
}

PrebakePair retains caller ownership of Image; consumers clone it before publication. It is not the wire format: levels carry two fixed data entries.

func DecodePrebake

func DecodePrebake(encoded, image []byte, layout Layout, document *world.Document) (PrebakePair, error)

DecodePrebake rejects unknown, duplicate, missing, null, reordered and noncanonical fields before returning a validated pair. Image remains borrowed.

func NewOfflinePrebake

func NewOfflinePrebake(layout Layout, document *world.Document, image []byte, inputs OfflineBakeInputs) (PrebakePair, error)

NewOfflinePrebake binds a combined direct/indirect image to transport inputs. Format validation verifies identity and QOI integrity, not solver quality. ReflectanceSHA256 commits original decoded albedo; the CLI verifies it against source before packaging. Hosts can verify surface identity, but do not possess those original source images and cannot independently rehash their pixels.

func NewPrebake

func NewPrebake(layout Layout, document *world.Document, image []byte) (PrebakePair, error)

NewPrebake validates a completed raw linear RGBM QOI atlas and derives its identity. It neither decodes pixels nor imports runtime or filesystem code.

func (PrebakePair) Validate

func (pair PrebakePair) Validate(layout Layout, document *world.Document) error

Validate checks every identity and the complete bounded QOI stream without allocating decoded pixels. It cannot prove the lighting quality of a producer.

type RuntimeBake

type RuntimeBake struct {
	Encoding   string   `json:"encoding"`
	LightID    string   `json:"light_id,omitempty"`
	LightIDs   []string `json:"light_ids,omitempty"`
	ShadowSize int      `json:"shadow_size"`
}

type Surface

type Surface struct {
	Binding  Binding
	Normal   world.Vec3
	Polygons [][]world.Vec3
}

Surface describes actual receiver polygons before triangulation. Empty Polygons explicitly describes a fully open portal wall.

func Surfaces

func Surfaces(document *world.Document) ([]Surface, error)

Surfaces discovers semantic receivers in stable sector-floor/ceiling/wall, then solid-top/bottom/side order. It preserves separate solid edge identities.

Jump to

Keyboard shortcuts

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