Repository navigation
Converted assets
FaceModel reads ICTF version 6. HairStyleFile reads THRS version 1.
Both formats use little-endian numbers. These readers check external converted
input before they expose its arrays. A converter name or a file extension does
not make input trusted. This contract covers file construction, not later
changes to the public model storage.
ICTF has a 64-byte header. The complete layout declared by that header must fit in 64 MiB, including the header. Every count except the expression-byte count must be at most 1,048,576. The limits below also apply:
- Model vertices, drawn vertices, kept vertices and followers: 65,536 each
- Identity modes and expressions: 1,024 each
- Moved vertices in one expression: 65,536
- Expression bytes: 64 MiB minus 64 bytes
The reader checks all header limits before it multiplies counts or requests a body read. These bounds keep all offset arithmetic in range. The byte limit also bounds the sum of the sections. Decoded arrays use more memory than their packed input. The limit is a per-model input bound, not a process memory quota.
THRS has a 20-byte header. It permits 1 through 65,536 strands and 2 through 256 points per strand. Their product must be at most 1,048,576. The reader checks these counts before multiplication and reads only the declared body plus one byte. It refuses both truncation and trailing bytes.
The largest supported packed file is 6,684,692 bytes. The root and offset arrays contain at most 1,114,112 vectors in total.
Both readers refuse unknown versions before a body read. No malformed count can request an unbounded read or an unbounded array allocation.
ICTF requires finite positions, texture coordinates and follower weights. Both formats require finite quantization scales in the inclusive range from zero through the Float32 representation of 1e30. Zero permits a constant shape or strand. This bound keeps every decoded signed-byte or signed-short product finite. THRS root components are signed shorts divided by 32,767; they cannot encode NaN or infinity. Degenerate roots remain supported.
ICTF checks these index domains:
- Drawn-position indices and expression vertices name model vertices
- Drawn triangles name drawn vertices
- Skin triangles, skin edges, hole corners, coarse triangles and kept vertices name the first 11,248 model vertices, or all vertices in a smaller model
- Coarse edges and follower corners name entries in the kept-vertex array
Kept vertices must be unique. Every coarse-triangle and hole-corner vertex must occur in the kept array. This prevents a downstream coarse remap from producing a missing-index sentinel. Kept and follower counts cannot exceed the skin vertex count. Each hole needs at least three corners. Hole lengths must sum to the declared corner count.
The file provides two weights for every follower and three corners for it.
Expression records must occupy exactly their declared section. The reader checks each record's aligned byte spans, moved-vertex count, index domain and scale. Identity records have one scale and three signed bytes per model vertex, with four-byte padding. These checks do not certify manifold topology or the result of arbitrary later morph weights and geometry operations.
FaceModel(path, identities, expressions) retains its partial-read API.
A neutral model can omit all expressions and identity modes. All header
counts and the complete declared layout are still bounded. Every loaded
geometry section is checked. Expressions crossed to reach identity data are
checked even when the caller does not request expression arrays.
An unrequested tail is not read or certified. A prefix-only load can succeed when that tail is truncated or contains invalid values. ICTF ignores bytes after the complete declared layout. Use a full load to check all declared sections. Do not use a successful prefix load as proof of a valid whole file.
assets/converted-asset-manifest.json pins a recipe that reproduces all three
bundled production assets byte for byte. It records each upstream filename,
immutable commit, Git blob checksum, SHA-256 and byte count. It also records
the converter source hashes, format versions, transformation parameters and
license evidence. This proves an exact recipe. It does not establish the
original historical command log.
The verification record records output hashes, source counts and focused checks. It is a historical record of the run with the Python converters, which the Mojo converters replace. Use the Mojo commands below.
The production inputs are:
- ICT FaceKit Light at
da5f95a607f5e6b37755b38d3385d7f2853732e5: the neutral OBJ, 57 expression OBJs and identity OBJs 000 through 059 - Frostbitten Hair WebGPU at
4478dd129525ff7db92978178b55f760716cbd72:static/models/SintelHairOriginal-sintel_hair.16points.tfx - AMD TressFX 4.1 at
6957058e29dceb25a0c2a82849bb892f3d9fbce5:bin/Objects/HairAsset/Ratboy/Ratboy_mohawk.tfx
The manifest lists all 120 converter inputs individually. It also pins five upstream license or context files. Only the listed input files reach the converters. Extra files in a cache cannot add expressions to the face model.
The ICT Light inputs use MIT. Sintel's model uses CC-BY-3.0; the repository's
MIT software license does not replace the model license. Ratboy uses MIT.
See THIRD-PARTY-NOTICES.md for attribution and source links.
Run the checker from the repository root:
mojo run -I . tools/check_converted_asset_manifest.mojoThis checks local hashes and the complete provenance metadata. It also
regenerates three small original fixtures from assets/converted-fixtures/.
The check never accesses the network. It does not read or regenerate the
large production inputs. --require-source-pins remains an accepted option;
complete source pins are now required in the default check too.
Choose a cache directory outside the repository. The first command downloads
only missing pinned files with curl. The input data uses about 306 MB of
storage. The scratch copy needs at least that much additional space. The
conversion takes less than a minute. Ordinary tests do not perform this
download or run.
mojo run -I . tools/check_converted_asset_manifest.mojo --source-cache /path/to/converted-sources --fetch-sourcesTo reuse an existing cache without network access, omit --fetch-sources:
mojo run -I . tools/check_converted_asset_manifest.mojo --source-cache /path/to/converted-sourcesYou can also populate the cache from your own upstream checkouts. Use
CACHE/SOURCE_ID/COMMIT/UPSTREAM_PATH for each manifest entry. The source IDs
are ict, sintel and ratboy. Copy the input, license and context files
without changing their bytes. Git metadata is not required. The checker
verifies both checksums and the byte count, then stages the same checked
bytes in a private temporary directory.
Missing files fail in offline mode. Wrong revisions, changed sources and truncated inputs fail before conversion. The download option does not repair or overwrite altered cache files. Restore those files from the pinned source before retrying. A source or output mismatch is an error. The checker never updates accepted assets or their expected hashes.
The Mojo converters tools/hair_style.mojo and tools/ict_face_model.mojo
reproduce all three production outputs and the three synthetic outputs byte
for byte. This was verified from a pinned source cache on 2026-10-14. The
original reference run used the Python converters on CPython 3.12.14 on Linux
x86-64 on 2026-10-05. The output comparison is exact. Another runtime or
converter must pass the same hashes before its result is accepted.
The Mojo converters do the same floating-point operations in the same order as the Python converters did:
- Each product that feeds a sum is rounded on its own. A default Mojo build
fuses
a * b + cinto one rounding, so the converters callproduct, which the compiler cannot fuse. No special--fp-modeflag is necessary. -
atan2,sin,cosandx ** ycome from the C library, as in CPython. -
math.distis computed with CPython 3.12's scaled, compensated algorithm. - Where the Python face converter called
sum, the Mojo converter uses CPython 3.12's compensated addition. - Each decimal in an OBJ file is read to the nearest double, as
float()reads it.
THRS fitting adds floating-point values explicitly from left to right. This
keeps one binary64 rounding per addition. Python 3.12 changed built-in sum
to use more accurate floating-point addition. With the unmodified converter,
that change moved four Sintel offset components by one quantization step.
Explicit ordered addition reproduces the existing layered asset exactly. It does not patch or replace any asset bytes. The mohawk and the three synthetic output hashes also remain unchanged.
Full ICTF loading and the THRS readers validate the generated format at the boundary described above. Provenance and format validation do not establish anatomical or canonical-to-visual fidelity. That work remains separate in issue #297.
Tutorials
How-to
Reference
- Scene graph
- Rotations
- Cameras
- Geometry
- Geometry addons
- Lofts and clipping groups
- Sculptor
- Procedural generators
- Meshes and assets
- Lines
- Points and sprites
- Helpers
- Scene objects
- Animated objects
- Raycasting
- Curves and paths
- Animation
- Skinning
- Materials
- Node materials
- TSL functions
- Lights
- Lighting addons
- Fog
- Textures
- Renderer
- Renderer hooks and material flags
- Rasterization
- GPU backend
- Render target
- Post-processing
- GPU computation
- Compute nodes
- Progressive light map
- Gaussian splats
- Voxel global illumination
- SVG renderer
- Image files
- Image decode queue
- Model files
- More model files
- Exporters
- Scene JSON
- Windowing and controls
- Math
- Norm consumers
- Math addons
- Units
- Coverage tool
- Commands
- Examples
- Benchmarks
- Extensions
- Shared physics
- Physics query snapshots
- Continuous collision
- Tick force restoration
- Water
- Animals
- Buildings
- CARLA
- Femur
- Tibia
- Fibula
- Patella
- Knee
- Muscles
- Vessels
- Lymph
- Nerves
- Integument
- Leg
- Foot
- Pelvis
- Torso
- Arm
- Hand
- Head
- Genome
- Game humanoid
- Humanoid fidelity
- Audio-aligned game faces
- Converted assets
- Mesh quality
- Segment inertia
Explanation
- Software rasterizer
- Scene graph as an array
- Linear color
- Normals are not colors
- Perspective correction
- Fixed-point coverage
- Per-fragment shading
- Mipmaps
- Sorted transparency
- Types and checks
- Render hooks as a trait
- Shared CPU and GPU code
- Bands and threads
- PNG structure
- Extensions beside the port
- CUDA teardown hang
- Mojo compiler hang