height * width * channels texels, row-major.
Float32Array for an HDR or otherwise float-encoded texture — note the
decoder always widens geolog_perchannel_u16 back to float32, so the
quantized-HDR case arrives here as floats too. Uint8Array/Uint16Array
are kept native, exactly as per-vertex colours are, since the GPU
normalizes them to [0, 1] for free.
A decoded ImageBitmap, always 4-channel 8-bit regardless of what the
source codec stored — which is why the byte budget charges w * h * 4
for this arm no matter the declared texture_channels.
A decoded texture, in whichever form its encoding produced.
A discriminated union rather than one struct with optional fields, because the three arms upload through genuinely different THREE.js classes (
DataTexture,Textureover anImageBitmap, orCompressedTexture) and an exhaustive branch onkindprevents a future arm from becoming a silently untextured mesh.width/height/channelsare the verified dimensions, not the declared ones: the loader compares what it decoded against MeshMetadata.texture_width and friends and rejects a mismatch, so by the time a payload exists these two agree. Carrying them here rather than re-reading the attrs keeps the upload from having to trust the attrs again.