The glb-textured.1 artifact connects embedded PNG/JPEG images to imported opaque base color, metallic/roughness and emissive materials. It travels through the existing authored-world catalog, ECS mesh handles, bounded GPU replacement and device reconstruction paths. Default Darkwind content remains unchanged; this is asset support for richer authored scenes.
#Import and identity
npm run import:model -- games/engine-zoo/assets/textured-panel.glb /tmp/textured-panel.model.jsonTextured artifacts use format 2 and glb-textured.1; untextured artifacts retain format 1 / glb-static.1 and their existing hashes. This stage introduced isolated model jobs asset-job.2 / glb-compiler.2 (superseded by the surface profile) so old build results cannot satisfy the expanded compiler contract. Portable compiler bundles include both image codecs and their notices.
Images must be embedded buffer views with an explicit PNG or JPEG MIME type. Image views cannot have a stride or GPU target, or serve as accessor views. External/data URIs, UV sets other than TEXCOORD_0, texture transforms/extensions, alpha blending and unsupported material features fail explicitly. Textured primitives must provide UVs; generated flat normals preserve those coordinates.
Base color and emissive maps use texture-rgba8-opaque.1 with usage color-opaque: source alpha is ignored, all published alpha bytes become 255, and every mip averages RGB in linear light before sRGB encoding. Existing color artifacts retain their alpha-weighted filtering and hashes. The distinct profile prevents alpha-dependent mip results from being mistaken for opaque surface derivatives. Metallic/roughness uses the existing linear data profile.
These conventions follow the glTF 2.0 material and sampler specification: base color and emissive samples multiply their linear factors; roughness reads green and metalness reads blue. Red and alpha in the metallic/roughness map are unused. UV (0,0) addresses the upper-left image corner, with CPU rows uploaded without a vertical flip. Opaque base-color factor alpha is also ignored.
#Sampling and ownership
The renderer uploads all validated mip levels. Color images use rgba8unorm-srgb; parameter maps use rgba8unorm. A material has separate image/sampler bindings for each supported map. Every glTF nearest/linear/mipmap filter combination and clamp/repeat/mirrored-repeat mode is mapped explicitly. Non-mip minification filters clamp level selection to zero; omitted filters choose linear magnification and trilinear minification. Missing maps use a shared white texel within the candidate allocation.
Image bytes are deduplicated by artifact hash within a prepared model/catalog, independent of sampler identity. Reflected mesh variants and repeated placements share their image resources. Material bind groups and samplers are shared within a candidate. Untextured draws retain their original pipeline/layout and allocate no fallback image. At this stage the complete shader bundle had seven pipelines under forward-shaders.3, including single- and double-sided textured variants; the instance ABI remains 112 bytes.
The compiler admits at most 16 source images, 32 source textures/samplers and 32 compiled material variants. Per-image limits remain those of the texture compiler. Unique decoded mip bytes per model are capped at 8 MiB. Catalog admission combines conservative reflected/unreflected geometry bytes, unique image bytes and the four-byte fallback under 16 MiB; the shared 16 MiB portable JSON ceiling can impose a tighter bound.
GPU staging includes geometry, unique mip bytes and the white fallback in the existing 32 MiB accepted-plus-candidate logical byte cap. This is logical resource storage, not measured driver/RSS allocation. Short validation/out-of-memory scopes cover allocation, upload and binding creation. Every partial candidate is destroyed on failure. Publication waits for prior GPU work; the old resources remain owned until replacement succeeds. Texture counts appear in renderer totals and modelState; render-target bytes remain separate. Device loss disposes resource ownership, and replacement devices reinstall from validated CPU artifacts.
#Verification
textured-panel.glb is an original numeric fixture with four colored quadrants, deliberately transparent source texels, indexed geometry and a progressive JPEG parameter map. tools/cli/textured-model-fixture.ts regenerates the GLB and pinned model JSON using the independent development-only PNG encoder. It reuses the independently checked JPEG from the texture compiler fixture.
Headless coverage checks alpha-independent linear-light mips, isolated and packaged compiler publication, mutable-input ownership, rehashed corruption, usage/reference validation, sampler mapping, GPU allocation fault cleanup, deduplication and authored-world/checkpoint integration.
The real WebGPU probe checks quadrant orientation and opaque alpha; eight sRGB gray levels against independently calculated linear material factors; separate emissive and green/blue parameter controls; ignored red/alpha channels; final-mip sampling; repeat/clamp/mirror addressing; invalid-candidate frame preservation; repeated replacement counts; and exact pixel restoration after device destruction/reconstruction. CPU allocation mocks alone do not establish these rendered properties.
Fixture material-textures.2 checks source bytes 0, 10, 11, 32, 64, 128, 192 and 255, including both sides of the sRGB piecewise breakpoint. The factor comparison permits at most one RGB output byte of error across the entire image and requires exact alpha. This is a measured project acceptance budget for two distinct numeric paths, not a WebGPU guarantee of one-byte accuracy. The Arc B390 showed a maximum one-byte difference where the previous byte-identical comparison failed; SwiftShader matched exactly. The renderer's color formats and shader code are unchanged. Identical-path restoration, ignored-channel and replacement checks retain exact comparison.
A deliberately incorrect linear reference differs by 57 output levels at source byte 128. A browser fault that changes rgba8unorm-srgb allocations to rgba8unorm fails at source byte 10 with a 34-level error on both adapters, and releases worker/canvas ownership. Hardware Observatory acceptance records the native and software controls, retained original failure, full ten-scenario hardware suite and both negative tests.
Surface materials now add normal/tangent-space and occlusion maps. Masked materials add alpha cutoff in color/depth and shadow passes. Alpha blending, compressed textures, streamed residency/eviction, material authoring UI, production art replacement and character animation remain open requirements of the full engine plan. No complete roadmap milestone is closed here.
Source 99acd34 passes 226 headless tests and all 26 browser scenarios. The acceptance record distinguishes pre-commit verification and subsequent catalog accounting/Docker-check additions. The worker report records every rendered control and resource census.
The later surface stage expands the bundle to nine pipelines and includes tangent/uniform bytes in ownership accounting. Source CI for this stage passed core/browser jobs but exposed a missing notice HTTP route in Docker acceptance; the subsequent surface change fixes that route.
The masked profile expands the complete bundle to thirteen pipelines and uses alpha-preserving color processing for masked base-color maps. Its factor-only materials also allocate a white fallback without source images. The original opaque profile and fixture counts above remain unchanged.