Skip to content
hikari
RenderingEditorAIToolchainDocumentation
GitHub
hikari

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsLicenseBack to top
Documentation / Systems
Browse docs
Overview
Tutorials16
OverviewFirst game projectFirst entityAuthored user_data and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesPlay, Edit, and scenesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides16
OverviewDevelopment guideGameplay APIChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / UnrealActor and component lifecycle
Systems28
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)UI and editorEditor asset hot reloadEditor Project Selector
Language reference2
OverviewAkari language referenceKawa language reference
Engine overview
Start exploring
  • No matching sections. Try fewer words or another topic.
NavigateEnter Openesc Close
Overview
Tutorials16
OverviewFirst game projectFirst entityAuthored user_data and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesPlay, Edit, and scenesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides16
OverviewDevelopment guideGameplay APIChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / UnrealActor and component lifecycle
Systems28
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)UI and editorEditor asset hot reloadEditor Project Selector
Language reference2
OverviewAkari language referenceKawa language reference
Engine overview
Systems20 min read

Asset formats (Shinra pipeline)

On this page
On this pageMetadata sidecars.shintexture sampler bits (header)Debug output and bundles.shincollision (SCC3) Back to top

The Shinra tool (src/shinra) produces binary engine assets and validates them before packaging. Runtime readers are brand-neutral under src/hikari/src/assets/formats/ and src/hikari/src/assets/bundle/; processors and validators stay in src/shinra/src/.

ExtensionSource inputsRuntime purpose
.shintexturePNG, JPEG, EXR, HDR, DDS (BC1–BC5 / ATI2), TGA; also extracted from glTF/GLB/FBX/OBJ/USDZTexture data (albedo / normal / cubemap) and platform-compressed or HDR mip chain
.shinmodelglTF, GLB, FBX, OBJ, USDZModel/mesh data (node TRS baked into vertices)
.shincollisionglTF/GLB/FBX/OBJ/USDZ when model.collision meta is setCooked mesh, convex hull, or decomposed compound (SCC3)
.model.jsonauto-emitted beside a source model (or hand-authored)Authoring placeable: mesh + materials[] + parts[] (editor / loose)
.shinmodeldoccooked from .model.jsonPackaged placeable parts list (SMD1; scenes still reference *.model.json)
.shinaudioWAV, OGGAudio payload and playback metadata
.material.jsonauthored JSON, or auto-emitted beside a source modelAuthoring material template (editor / loose)
.shinmaterialcooked from .material.jsonPackaged surface/volume material template (SMA2; scenes still reference *.material.json)
.shinsceneproject scenes/*.json (kind: com.hikari.scene)Schema-neutral binary value tree (HSC1; Shinra --scenes)
.shinprefabassets/prefabs/**/*.prefab.json (kind: com.hikari.prefab)Schema-neutral binary value tree (HSC1; Shinra --prefabs)
.shinparticle.particle.jsonCompiled GPU particle program (SPT2)
.shinanimgraph.animgraph.jsonSkeletal animation state machine (AGR1 v5 — layers, foot IK, root motion, 1D/2D blend spaces)
.shinspriteatlas.spriteatlas.jsonSorted sprite rect/pivot/nine-slice metadata referencing a normal texture (SAT1)
.shinfont.ttfValidated TrueType pass-through
.shinthumbsidecar of a cooked texture/audio/…Editor thumbnail bitmap (<cooked>.shinthumb)
.shinpeakssidecar of .shinaudioEditor waveform envelope (<cooked>.shinpeaks)

Model import (multi-output): one source model cooks to derived models/<stem>_img<N>.shintexture for unique used images (byte-identical maps intern onto one slot), companion materials, and models/<stem>.model.json. Sidecar files already referenced by the model are not cooked again as standalone textures. Generated JSON carries "generator": "shinra.model"; hand-authored files at the same path without that marker are left untouched. Editor model drop reads .model.json (no stem renaming). glTF/GLB, FBX, OBJ, and USDZ share every stage after import — the front-ends in shinra/src/import/ all produce the same ImportDocument.

OBJ specifics (shinra/src/import/obj/): Wavefront OBJ + MTL, no extra crate. Faces fan-triangulate and weld on (position, uv, normal) indices. UV V is flipped into top-left space. The file is already treated as right-handed Y-up metres (no FBX-style axis/unit conversion). Missing vn is left empty so the cook generates area-weighted normals. Classic Phong maps onto the glTF-shaped IR: Kd/map_Kd → base colour, Ns → GGX roughness √(2/(Ns+2)), metallic 0. illum 4/6 (or a black Kd with a non-white Tf) becomes transmission on the forward-lit path; d/Tr without that glass flag becomes alpha. Unset glass Ni (1.0) is raised to 1.5. Grayscale bump/map_bump is Sobel-converted to an OpenGL tangent-space normal (-bm scales the slope); already-blue maps pass through. Missing textures warn and drop to factors. .mtl and map_* files are cook-cache dependencies; sidecar images are model-owned and not cooked twice.

USDZ specifics (shinra/src/import/usd/, backed by the native-Rust openusd crate — no C++ OpenUSD): a .usdz is a 64-byte-aligned uncompressed ZIP whose first USD layer (USDA or USDC) is the stage root; textures stay inside the archive and are decoded with the same PNG/JPEG/DDS/TGA path as the other importers. UsdGeomMesh n-gons fan-triangulate (control cage only — Catmull-Clark is not subdivided). UV V is flipped into top-left space. metersPerUnit (UsdGeom fallback 0.01) and upAxis (fallback Y) land on a root helper node, never on vertex data, matching the FBX axis/unit fold. UsdPreviewSurface maps onto the glTF-shaped IR: diffuseColor → base colour, opacity / opacityThreshold → Blend / Mask, metallic + roughness pack into one metallic-roughness image when both are textures of equal size (G = roughness, B = metallic, R = occlusion when present). Separate maps of mismatched size drop to factors with a warning, like FBX. Missing textures warn and drop to factors. Material bindings walk material:binding:preview then material:binding (including MaterialBindingAPI when applied) and GeomSubset materialBind families. UsdSkel (import/usd/skel.rs): the first Skeleton with bound, weighted meshes becomes the model's one skin. Joints become nodes under the Skeleton prim's node (rest from restTransforms, else derived from bindTransforms), so the cook's palette closure and the units/up-axis helper wrap them like a glTF rig. Bound meshes (SkelBindingAPI applied, skel:jointIndices / skel:jointWeights, elementSize influences, skel:joints subsets, constant interpolation = rigid, geomBindTransform) get their strongest four influences per point, renormalised; their bind points and the inverse bind matrices both fold geomBindTransform × Skeleton local-to-world × helper so glTF skinning (v × IBM × G(t), mesh node ignored) reproduces UsdSkel skinning exactly. Every distinct skel:animationSource (inherited, or bound directly on the meshes as Sketchfab exports do) becomes a clip: joint-local T / R / S sampled at every authored time code, timeCodesPerSecond → seconds, animation joint order remapped onto the skeleton, unauthored paths left unkeyed. Joint scale keys are kept — the runtime sampler applies them, so FBX-derived ×100 / ×0.01 chains render correctly. Blend shapes and a second skeleton are not imported (extra skeletons' meshes import static, with a warning).

FBX specifics (shinra/src/import/fbx/, backed by the ufbx C library): axes and units convert to the engine convention at load (right-handed Y-up, metres) and land on node transforms, never on vertex data. FBX geometric transforms and non-standard inherit modes become helper nodes, since the IR carries only T/R/S. UV V is flipped into top-left space. Legacy Lambert/Phong TransparentColor × TransparencyFactor becomes ordinary alpha because FBX does not distinguish coverage from physical glass. Exact-name model.material_overrides.<name> sidecar authoring can either request transmission_from_alpha or explicitly replace PBR factors (base_color, metallic, roughness, emissive, emissive_strength, transmission, ior, thickness, attenuation_color, attenuation_distance, and double_sided). Invalid values and stale material names fail the cook, so this is durable source-format repair rather than a material-name heuristic. Real PBR shaders (Arnold / OpenPBR / 3ds Max Physical / glTF) preserve their explicit transmission, and map specular anisotropy plus clearcoat into the same companion slots as glTF KHR_materials_anisotropy / KHR_materials_clearcoat. Arnold and OSL store anisotropy rotation as a 0–1 factor over 180°; 3ds Max Physical stores degrees — both become radians at import. Greyscale anisotropy amount maps pack to the glTF layout (RG = +X, B = strength); RGB maps already in that layout pass through. DirectX clearcoat normals flip green with the base normal. Lambert/Phong has no coat or anisotropy lobe, so those slots stay empty. Animation is baked to 30 Hz linear T/R/S keys per anim stack, trimmed to start at t=0. Skin deformers with an identical joint palette and identical inverse binds are interned into one skin; anything else stays separate and meets the cook's one-skin-per-stem rule. Missing or undecodable textures warn and drop to material factors rather than failing the cook; separate metalness/roughness maps are dropped too, because the IR slot is a packed glTF metallic-roughness texture. Game-industry extras (filename and material-name tokens, never a scene-name branch): DDS BC1–BC5 (including ATI2 normals) decode to RGBA8; a Phong SpecularColor map named *_Specular provides packed roughness/metalness (G/B), while explicit *_ORM / *_ARM names additionally provide occlusion (R), all with metallic/roughness factors 1. DirectX normal maps flip green into the OpenGL IR; an otherwise opaque albedo with translucent texels becomes Mask (cutoff 0.5) so foliage cutouts depth-write; material-name tokens DoubleSided / TwoSided and Masked fill flags Phong does not carry; byte-identical images intern onto one cooked texture. Model-referenced sidecar files are not cooked a second time as standalone textures.

  • Single-part (one baked primitive): models/<stem>.shinmodel + models/<stem>.material.json; .model.json has mesh, materials[] (one slot), and a one-entry parts[].
  • Multi-part (more than one material slot after merge): primitives that share a material index are merged into one slot by default. Large static environments can enable model.spatial_clusters: Shinra first merges compatible geometry, then recursively splits expensive ranges along their longest spatial axis. target_triangles controls authoring/residency granularity, max_extent controls preferred bounds, and min_triangles prevents sparse geometry from becoming tiny parts merely because it spans a large area. Defaults are 32,768 target / 8,192 minimum triangles, aligned with rigid LOD generation so ordinary clusters retain an LOD chain. Every emitted part is then converted to SRM1 v5 meshlets; runtime camera and shadow submission culls those meshlets and merges compatible work into pipeline buckets. Emits models/<stem>_p<N>.shinmodel + models/<stem>_m<N>.material.json; no stem .shinmodel / .material.json. .model.json sets mesh to parts[0].mesh, lists all materials, and records parts[] (name, mesh, material).
  • Editable hierarchy placement: set model.placement to editable_hierarchy in import settings. Static primitives are no longer merged by material; the generated .model.json embeds a com.hikari.prefab-shaped placement template. Dropping the model in the editor expands that template as ordinary scene actors in one undo step, with source-node grouping and one independently selectable render actor per primitive. Geometry remains world-baked so previews and initial placement agree; the group actors therefore carry identity transforms rather than reconstructed DCC pivots. The expansion deliberately has no prefab link or runtime template dependency. Skeletal models warn and use merged placement.
  • Scene placeable: actors set "components": { "render": { "model": "asset://…/<stem>.model.json", … } } plus optional "materials": [...] slot overrides (com.hikari.scene document version 1). Runtime binds one RenderComponent with N primitives (one draw per slot). Transparent slot materials disable shadow casts for that section only.
  • Companion materials map imported PBR onto engine params. Opaque slots use _engine/gbuffer / DeferredGeometry; transparent / transmissive slots use _engine/forward_lit / ForwardLit with transmission, ior, thickness, attenuation_color, and attenuation_distance. Imported anisotropy (glTF KHR_materials_anisotropy; FBX Arnold / OSL / 3ds Max Physical / OpenPBR) maps to anisotropy_strength, anisotropy_rotation, and the anisotropy texture. Imported clearcoat (glTF KHR_materials_clearcoat; the same FBX PBR shaders plus glTF-in-FBX) maps its factor, roughness, and three texture slots without flattening either lobe into base roughness. flags.transparent selects the forward path; params.transmission > 0 further selects the refractive PSO (depth write, no blend). flags.alpha_mode is straight or premultiplied and defaults to straight. flags.preserve_authored_normals keeps custom foliage normals oriented as authored in raster and RT (default false; also available in exact-name model overrides). flags.double_sided disables back-face cull; flags.alpha_cutoff (>0) enables Mask discard in the G-buffer (glTF alphaMode: MASK).
  • Graph-authored material: still a version-1 com.hikari.material document and still cooks to SMA2. Its optional top-level graph object has kind: com.hikari.material-graph, graph schema version: 1, stable-id nodes[], and pin-ordinal links[]. Surface graphs point material.shader at the deterministic material_graph_<material-id> package (a reserved namespace — Shinra rejects a graph package outside it) and material_graph_vertex / material_graph_fragment; skinned meshes use the material_graph_vertex_skinned twin. Volume graphs have no surface shader package: Shinra folds canonical medium properties and emits a bounded extinction program directly into SMA2. A surface document without graph is a hand-authored Akari material and is never inferred into a graph — there is a hand-authored form for it to stay in. A volume document without graph has no such form: domain: volume with only a material.volume block is the constant medium, and opening it in the editor synthesizes the equivalent graph (one constant per Volume Output input) so it can be edited at all. Cooking either shape produces the same SMA2; the synthesized graph only reaches disk if the author saves.
    • Emissive textures are not cooked opaque, so their alpha survives and carries no lighting meaning — which makes it the natural place to bake a per-pixel coordinate for a graph to animate against. The example bakes distance along each circuit trace there, and fract(alpha − time × speed) marches one bright head down the path. Panning the UVs instead would slide the whole pattern sideways, which is a different effect and usually the wrong one.
    • Emissive is HDR, and its precision is relative. The G-buffer packs it into 16 bits over 0..16 split across two alpha channels (emissive_pack.akari): one 4-bit exponent, stepping by half a binade, shared by three 4-bit mantissas. Because every channel is quantised against that one shared scale, a neutral colour stays neutral at any brightness and a 0.1 emissive resolves as finely as a 10.0 one — worst case 5.7% of the colour's peak, ~2.6% typical, and every power-of-two multiple of the range is exact. This replaced a linear RGB565, which quantised the channels against different grids: red and blue died below 0.26 while green survived, so a dim grey glow came back pure green. Anything below ~0.003 still packs to black, and the editor warns when a folded emissive lands there.
    • Normals and Fresnel. A normal_texture node fills the material's normal slot, and the surface's Normal input (appended last, so older documents keep their pin ordinals) takes a tangent-space vector. The tangent frame is derived from screen-space derivatives of world position and UV — hikari_tangent_to_world_normal in brdf.akari, shared with the standard gbuffer and forward paths — so no mesh needs a tangent attribute and nothing interpolates one. A graph with no Normal wired emits no frame, no derivatives and no call at all. fresnel measures against the shaded normal, so a rim follows the mapped detail; wiring one into the Normal chain is refused by name rather than producing a shader that references a value before it exists.
    • A node's optional text is its single string payload: an asset path on a sampler, Akari source on a Custom Expression. A sampler with no text lowers to its neutral constant with no fetch and no branch — an unfinished node costs nothing until it is finished — while an assigned one keeps the MapMask guard, because assigned in the document is not the same as resolved at load.
    • material.textures / material.params are load-bearing, not decoration. The ray-traced path samples HikariGpuMaterial, never the lowered graph shader, so the editor writes each sampler node into its own bindless slot (albedo, metallic_roughness, occlusion, emissive, anisotropy, clearcoat, clearcoat_roughness, clearcoat_normal — one node per slot, a second is rejected) and constant-folds each surface input into params. The Surface Output exposes anisotropy direction/strength/rotation plus clearcoat factor/roughness/normal; older pin ordinals remain stable because these inputs are appended. A sampler standing in its own channel folds to the identity multiplier, so Albedo Texture × Color writes that colour and RT reproduces the graph exactly; a channel reaching Time or UV falls back to the same constant the generated shader uses. The node vocabulary itself is one table, src/hikari/src/editor/material_graph/nodes.schema.json, that the Zig editor renders at comptime and Shinra checks in a test — drift between the two is a build failure, not a material that silently stops producing a shader.
  • .shinmodel (SRM1 v5): 64-byte mesh descriptors with full f32×3 world AABB min/max, a cook-selected ray-tracing LOD bias, and per-LOD meshlet topology. Import bakes node TRS (glTF decomposed() / ufbx local_transform), generates missing normals, flips winding on negative-scale bakes, and preserves authored tangents when present. Version 5 is a hard cut: the on-disk index buffer is gone. Each LOD stores 64-byte meshlet descriptors (sphere@16 / cone@32), a global-vertex remap, and packed local micro-triangles (64 verts / 124 tris). The engine loader materializes a u32 index stream from that topology for BLAS and physics. Vertices are expanded into the packed 32-byte engine row (hikari/sdk/src/vertex_layout.zig: f32×3 position, oct snorm16×2 normal, unorm8×4 color, f32×2 uv, one reserved word; skinned rows add joints/weights for 40 B). Alpha-masked meshes that pass Shinra's card-topology classifier receive bias 2; ordinary cutouts emit zero and use Hikari's conservative masked fallback. After meshes/materials the optional skeleton/animation trailer is:
    • Every primitive is welded at bake. Byte-identical vertices (all present attributes equal bit for bit) collapse into one and the primitive is re-indexed, before LOD, cards, and meshlets. Lossless: hard edges and UV seams stay split. Unindexed glTF/OBJ content (the Khronos Fox ships three vertices per triangle) would otherwise fill every meshlet to the 64-vertex cap at 21 triangles.
    • Meshlets are the on-disk topology. Rigid triangles are Morton-sorted by centroid before meshlet construction so clusters start from a spatially coherent stream; skinned meshes run meshoptimizer's vertex-cache ordering instead (they draw as one submesh, so spatial order buys nothing). A linear buildMeshletsScan then packs that stream into 64-vert / 124-tri clusters — greedy adjacency/KD-tree clustering is ~14× slower on a 32k-triangle part and only ~10% denser after the same pre-pass. The cooker permutes the vertex buffer into meshlet first-use order: the engine's vertex shader is nothing but a remap pull, so remap locality is the dominant raster memory cost and the on-disk remap is therefore near-sequential. The cook log reports meshlet fill (average triangles/vertices, cone validity, and the count of meshlets under 32 triangles that their submesh does not explain — a small submesh or a submesh's last meshlet is never counted). Older cooks (v4 and below) are rejected.
    • The layout is hand-mirrored in three places with no shared schema — the Rust encoder, the Rust reader, and src/hikari/src/assets/formats/model.zig. src/shinra/fixtures/shinmodel/srm1_v5_meshlets.shinmodel is the committed file all three are tested against; regenerate it with HIKARI_WRITE_SHINMODEL_FIXTURE=1 cargo test -p shinra golden and update the Zig expectations in model_mesh.zig in the same change.
    • Skeleton (joint_count > 0): joint_count × (name_hash u32 + parent_index i16 + bind_matrix f32×16 IBM, column-major) = 70 B/joint; then mandatory rest-local TRS joint_count × (pos3 + rot4 xyzw + scale3) = 40 B/joint; then joint name strings in palette order (u16 len > 0 + UTF-8, max 256 B). name_hash must equal FNV-1a of the name. Runtime skin: skin[j] = global_pose[j] * ibm[j].
    • Animations (animation_count > 0): per clip a 16-byte header (name_hash, duration, ticks_per_second, channel_count, flags) + channel_count × 16-byte channel headers (joint_index, path T/R/S, interpolation, key_count, time_offset, value_offset relative to payload base after that clip’s channel headers) + tightly packed f32 times/values. After all clip track payloads: clip name strings in animations[] order (u16 len + UTF-8). Explicit key_count — no trailer length estimation. Flags: bit0 compressed (reserved — writers never set it; readers reject it), bit1 uniform_times.
  • .shinmaterial (SMA2): header byte 22 selects the surface or volume domain; bytes 23–26 carry the volume-program word count. FLAG_DOUBLE_SIDED (16), FLAG_ALPHA_MASK (32), and FLAG_PRESERVE_AUTHORED_NORMALS (256) apply to surfaces; alpha cutoff is an f32 at header bytes 18–21 when the mask flag is set. The fixed parameter block carries surface and canonical volume properties, followed by the string-offset table, an optional validated volume program (eight words per instruction, at most 24 instructions), then the string heap. Format version 4 carries the surface anisotropy/clearcoat parameters and texture slots alongside phase_second, phase_blend, and multi_scatter for volumes. Both readers reject older versions.

Output mirrors input folders and includes assets.shinmanifest.json. Unsupported types and dotfiles are skipped.

Metadata sidecars

Each <filename>.shinmeta.json may contain optional texture, audio, and/or model sections. Model metadata includes placement (merged, the default, or static-only editable_hierarchy), optional workload-aware spatial_clusters, exact-name material_overrides, and LOD, optimization, CPU-access, and collision controls.

texture section: type (albedo / normal / cubemap / metallic_roughness / occlusion / emissive), mipmaps, sRGB, premultiplied alpha, compression quality, cubemap face_size, stop/max/min-edge controls, sampler: address_u / address_v (repeat | clamp | mirror, default repeat), filter (linear | point, default linear).

Texture type drives preprocessing. Colour maps stay BC7 / ASTC 6×6; normals use a 2-channel-friendly format so XY is not sharing bits with an unused B and A.

TypeDefaultsProcessing
albedosRGB on, mips onsRGB-aware mips, BC7/ASTC
normallinear, mips onre-normalized mips; BC5 (D3D12/Vulkan) / ASTC 4×4 (Metal). Shaders reconstruct Z from XY.
metallic_roughnesslinear, mips onBC7/ASTC; glTF G=roughness, B=metallic
occlusionlinear, mips onBC7/ASTC; sample R
emissivesRGB on, mips onsRGB-aware mips, BC7/ASTC
cubemaplinear, mips onequirectangular → 6 faces; HDR sources (EXR/HDR) store RGBA16F; LDR uses platform compression; GGX-prefiltered specular mips + L2 irradiance SH trailer

Alpha-tested model textures preserve source alpha-test coverage across generated mip levels. Their colour is filtered with alpha weights, so transparent atlas padding cannot darken surviving foliage or railings as distance selects lower mips.

Cubemap layout: width=height=face, array_size=6, cube_map flag, faces packed +X,-X,+Y,-Y,+Z,-Z per mip. Mip 0 is the sharp sky; mips 1..N are GGX-convolved at roughness i/(N-1). Flag bit4 (ibl_split_sum) marks that path and a 108-byte trailer of 27×f32 L2 SH coeffs (RGB-interleaved) after the last mip payload.

.shintexture sampler bits (header)

64-byte SRT1 header field after array_size (formerly reserved u16):

BitsFieldEncoding
0–1address_u0 = repeat, 1 = clamp, 2 = mirror
2–3address_vsame
4filter0 = linear, 1 = point
5–15reservedmust be 0

Zero (missing shinmeta / old assets) = wrap/wrap/linear — bit-identical to historical content. Runtime Texture.sampler carries the decoded SamplerDesc. Each material draw uses one effective sampler (albedo if set, else first bound map). That desc indexes the geometry sampler catalog (gpu/sampler_catalog.zig): D3D12 static samplers in space 1, Metal argument-buffer sampler table. AF applies to linear catalog entries.

SectionImportant settings
audiovolume normalization, sample rate, compression quality, looping, streaming hint, output format
modelallow_cpu_access (default false), LOD generation, mesh optimization, compression quality, collision (mode: none | mesh | convex | convex_decomposition, optional node name filter)

Static render geometry is GPU-resident after upload and its decoded CPU payload is released by default. Set model.allow_cpu_access: true only for systems that need to inspect render vertices at runtime. Physics does not require it: collision uses the separately cooked .shincollision companion. The flag occupies SRM1 header bit 6; it does not change the v2 payload layout.

Audio formats are Pcm16Le, PcmF32Le, and OpusOgg (wire magic SHN1). Shinra's --target, not runtime asset code, chooses texture compression.

Debug output and bundles

--emit-raw texture,audio writes inspectable raw sidecars; --emit-json-assets writes base64 JSON mirrors with version, byte length, SHA-256, and data. JSON mirrors are omitted from release manifests.

Bundle wire shape (3.0): binary default → {name}.shinbundle with an embedded TOC: every entry payload starts on a 4 KiB boundary, then a fixed-record table (80 bytes per entry: FNV-1a 64 of the ASCII-lowercased key, offset, stored size, uncompressed size, key offset/length into a string blob, compression, asset type, SHA-256), then a 48-byte footer (SHNBNDL3). The runtime maps the file, validates the table in place and binary-searches it — no parse, no per-key allocation; boot reads only the footer and table of each packed pack to build its path → pack index. {name}.shinbundle.json is still written beside it for humans and shinra validate (which checks the two agree), but nothing at runtime reads it; {name}.shinbundle.statcache.json is Shinra's rebuild cache. Writer src/shinra/src/bundle/toc.rs, reader src/hikari/src/assets/bundle/toc.zig. --bundle-format json → self-contained {name}.shinbundle.data.json (entries sorted by relative path: asset type, base64 payload, stored/uncompressed size, per-entry compression, hash of uncompressed data). --compression zstd is the pack default; each TOC entry records its actual compression. .shintexture entries stay uncompressed so mip ranges are directly addressable (their image blocks are already GPU-compressed), while other entries use the requested default. A pre-3.0 bundle (no footer) is refused at open with TocMissing; re-seal.

Catalog maps: product seal writes product.shinbundle.map.json v3 (pack → delivery, deps, labels, loose root; paths only for loose packs — a packed pack's membership is its TOC). shinra pack and cook --bundle do not emit per-stem .shinbundle.map.json — membership is in each pack's TOC. Exception: cook --bundle-strategy first-level-folders (debug/specialist) still writes a stem map for partitioned art.

Art packs include cooked binaries only: .shintexture / .shinmodel / .shincollision / .shinmaterial / .shinmodeldoc / .shinaudio / .shinparticle / .shinanimgraph / .shinspriteatlas / .shinfont / .kawabc / shaders. Scenes (.shinscene) and prefabs (.shinprefab) are domain-staged, not art-pack members. Authoring .model.json / .material.json / .prefab.json / .particle.json / .animgraph.json / .spriteatlas.json stay out of the pack; runtime remaps those URIs to the binary companions. Other .json sidecars (for example .shinmeta.json) stay out of the pack. Editor-only .shinthumb / .shinpeaks are not product pack members.

.shinspriteatlas (SAT1): a 32-byte header, hash/name-sorted fixed 40-byte records, and a length-prefixed UTF-8 heap. Each record stores a source rect (u16), normalized pivot, and left/top/right/bottom borders. Runtime validates once and performs allocation-free binary search by FNV-1a 64-bit hash with name comparison for collisions. Pixel data is intentionally absent; the header names an ordinary texture asset so compression, mips, hot reload, and GPU residency are shared with non-UI consumers.

.shincollision (SCC3)

Offline physics geometry, companion of the cooked model (models/foo.shinmodel + models/foo.shincollision). Opt in via <model>.shinmeta.json:

json
{ "model": { "collision": { "mode": "mesh" } } }
modeOutput
none (default)No file
meshTriangle soup from baked scene meshes
convexClosed convex triangle surface plus exact unit-density mass moments
convex_decompositionConcave closed surface decomposed offline into a bounded convex compound

Optional node: only include baked pieces whose node/mesh name contains that substring (e.g. "Collision").

Convex input must be closed, manifold, non-degenerate and convex. Shinra normalizes whole-mesh winding, validates every face, and cooks volume, center of mass and the full inertia tensor. Invalid content fails during cooking rather than producing an approximate dynamic body.

Decomposition uses V-HACD at a fixed internal resolution, then validates and bounds the result. max_hulls defaults to 16 (range 1–64), concavity to 0.002 (lower is tighter and slower), and min_volume to 0.001. Tiny parts merge into the nearest part instead of leaving holes. These controls affect only the offline cook.

json
{
  "model": {
    "collision": {
      "mode": "convex_decomposition",
      "max_hulls": 16,
      "concavity": 0.002,
      "min_volume": 0.001
    }
  }
}

Binary: 48-byte SCC3 header + fixed 80-byte descriptors for each tightly packed part + flat f32×3 vertices and u32 triangle indices. Every hull descriptor stores unit-density volume, center of mass, and the full inertia tensor. Runtime caches the geometry once and expands a compound into ordinary Tenkai hull colliders, so decomposition adds no work to the simulation hot path.

json
"physics": { "collision": "asset://./models/foo" }

--validate checks processed formats and present bundles, including payload sizes, offsets, hashes, and map consistency. --validate no-build checks an existing output tree.

PreviousAsset residencyNext UI and editor

Documentation follows the current engine checkout.

Snapshot cc148c75Source docs/systems/asset-formats.md
On this pageMetadata sidecars.shintexture sampler bits (header)Debug output and bundles.shincollision (SCC3) Back to top