packages/assets/texture.ts compiles PNG/JPEG source into a content-addressed RGBA8 artifact with a complete mip chain. The same implementation runs in Node and the browser contract worker. This is the image-processing stage of milestone 7. Material texture integration now connects GLB references and GPU sampling; production game art remains subsequent work.
#CLI and artifact
npm run import:texture -- games/engine-zoo/assets/texture-grid.png /tmp/grid-color.texture.json color
npm run import:texture -- games/engine-zoo/assets/texture-grid.jpg /tmp/grid-data.texture.json dataThe output must be a new path in an existing directory. Usage is mandatory: color, color-opaque, data or normal. The CLI detects PNG/JPEG from file bytes and sends the owned source and processing settings to a separate process. A successful result is independently validated, written and synced to a temporary file, then published through an exclusive hard link. Failed imports leave no final output and existing files remain unchanged. Directory fsync/power-loss durability is not claimed.
npm run build:author packages import-texture.js, texture-worker.js, both codecs and their notices. node author-dist/import-texture.js <source> <new-output> <usage> runs outside the checkout without development dependencies. The existing Docker build includes this bundle.
The texture-rgba8.1 artifact contains source SHA-256, MIME type, usage, dimensions, exact decoded byte census and canonical base64 RGBA bytes for every level. Its SHA-256 covers the unsigned artifact, including the compiler profile and usage. Source bytes, not filenames, establish identity. Rows are top-to-bottom; channels are unpremultiplied RGBA. Source alpha is preserved except in explicit color-opaque artifacts. No EXIF rotation or embedded color-profile conversion is performed; material usage selects sample interpretation.
readTextureArtifact owns the JSON before asynchronous hashing. It verifies shape, dimensions, canonical binary encoding, every level size, complete chain, total byte count and checksum. It also recomputes each derived mip, so rehashing invalid mip pixels does not bypass validation. Integrity checks do not authenticate a publisher or prove correspondence to unavailable source bytes.
#Decoding and filtering
The PNG decoder handles grayscale, RGB, indexed palette, grayscale-alpha and RGBA, valid depths from 1 through 16 bits, five row filters, Adam7 interlace and palette/transparent-sample alpha. Sixteen-bit samples are rounded to RGBA8. Chunk CRCs, image ordering, dimensions, scanline length and zlib Adler checksum are checked. Unknown critical chunks and animated PNG are rejected. Ancillary text/profile chunks are skipped without decompression. The implementation follows the PNG specification.
JPEG uses pinned jpeg-js with strict decoding, typed-array output, a pixel ceiling and an explicit decoder memory limit. Header dimensions are checked first. The supported source profile is 8-bit grayscale or three-component baseline/extended/progressive JPEG; CMYK and lossless encodings are rejected. Upstream decoder options describe its allocation accounting, which is not an operating-system RSS limit.
All usage modes include the final row/column of odd-sized images through area-weighted filtering:
color: decode sRGB to linear light, filter with alpha weighting, then return unpremultiplied sRGB RGB and averaged alpha. This avoids dark mip transitions and color bleeding from fully transparent texels.color-opaque: ignore source alpha and filter sRGB RGB in linear light; all alpha bytes are 255. Uses the distincttexture-rgba8-opaque.1profile for opaque base color and emissive maps.data: average channel values directly; suitable for linear material parameter maps.normal: average decoded XYZ vectors, normalize the result and encode it again; cancellation falls back to positive Z. Alpha is averaged separately.
Mip dimensions halve with floor rounding, clamped to one, until 1×1. The glTF material convention distinguishes sRGB color maps from linear parameter maps; the material texture integration attaches those semantics to imported GLB materials.
#Bounds and dependencies
Input is at most 8 MiB, dimensions at most 2,048 per axis, and at most 1,048,576 pixels. PNG permits at most 4,096 chunks. The decoder allocates a fixed header-derived DEFLATE destination plus one overflow-detection byte before unfiltering; an oversized stream cannot grow that destination. PNG scanline storage admits up to 8 MiB plus bounded filter-byte overhead. The final decoded mip chain is limited to 8 MiB. JSON work/reply/artifact envelopes use the shared 16 MiB ceiling.
JPEG codec allocation accounting is limited to 64 MiB. CLI work uses the existing 30-second child-process deadline and 256 MiB JavaScript heap. Process termination is the CPU/time boundary for malformed compressed data; these controls are not an OS sandbox or a total-process-memory guarantee. Direct synchronous decoding should run in a worker, not on an interactive UI thread.
Runtime dependencies are pinned fflate 0.8.3 (MIT, fixed-output DEFLATE) and jpeg-js 0.4.4 (BSD-3-Clause package with Apache-2.0 decoder notices). fast-png 8.0.0 is a development-only independent fixture encoder; its decoder is not shipped or used by this pipeline. The lockfile pins transitive dependencies and retains existing platform/libc metadata. Browser output and portable compiler bundles include texture-codec-notices.txt. Upstream sources: fflate, jpeg-js, fast-png.
#Verification and open integration
tests/texture.test.ts checks independent PNG encodings, packed/interlaced/16-bit samples, transparency, corrupt framing/checksums, decompression overflow, filtering, owned input, rehashed mip rejection, process identity and portable CLI publication. The original 9×7 grid fixture is encoded with ImageMagick; PNG output matches every source channel and progressive JPEG output is compared against an independent ImageMagick decode within two channel values. tools/cli/texture-fixture.mjs regenerates the image/reference files; changes must deliberately update the pinned fixture hashes too.
The Engine Observatory contract worker processes PNG and JPEG under all three usage modes. Browser acceptance compares all six artifact identities, dimensions, mip layouts and byte counts with fresh Node execution and the pinned fixture. This proves portable CPU artifact generation, not GPU texture sampling or physical-device performance.
At the original compiler acceptance, GLB references and GPU material sampling were still open. The subsequent material integration supplies those paths for opaque base color, metallic/roughness and emissive maps. The surface-material stage adds normal/occlusion maps and an original furnished material study. Compressed GPU formats and production-world art integration remain required work. No engine milestone is closed by this compiler stage.
Source 099a999 passes 218 headless checks and all ten browser probe scenarios. The record distinguishes pre-commit execution and later formatting/notice additions; it does not claim a full player/multiplayer browser rerun for this image-processing stage. The worker report preserves all six artifact identities and complete mip layouts.
The subsequent material texture integration connects base color, metallic/roughness and emissive maps to GLB artifacts and actual GPU sampling.
Masked materials now use alpha-preserving color artifacts for base color, with sRGB RGB and linear alpha GPU sampling. Existing mip averaging is retained; threshold-coverage preservation is not implemented.