Interchange formats
Studio converts game data to common formats for outside tools and brings edits back. The codecs are written for Studio. Exported files are working copies. Only imported differences enter a project.
Inspectors
An inspector decodes one kind of asset for the Properties page. The binary view is always available.
| format | shows | editable |
|---|---|---|
| binary | strings and possible tag references | bounded byte replacement |
| CUI resource | imports, nodes, bindings, typed property operations | text hashes, raw flags, raw operation values |
| CUI program | the node hierarchy and root resource | raw only |
| behavior | a typed graph, source paths, linked tags | target tags, target hashes, raw comparison modes |
| investment item | stack size, bucket, sockets, stats, sandbox perks | existing scalar fields |
| investment tables | item index, display string hashes, collectible rows | display string hashes |
| material | shader stages, texture slots, inline vectors | texture assignments, raw flags, vector values |
Behavior expressions and CUI bindings are not run. Unknown behavior nodes stay visible.
File formats
| format | export | import |
|---|---|---|
| OBJ | triangles, normals, UVs | polygon meshes, negative indices, split attributes |
| glTF and GLB | triangles, normals, UVs | triangle primitives, scene transforms, interleaved buffers |
| glTF animation | a sampled joint hierarchy | not for clips; see the control importer |
| DDS | native formats, mips, cubes, volumes | DX10, DXT1, DXT3, DXT5, BC4, BC5, supported legacy RGBA masks |
| PNG | RGBA8, BGRA8, R8, BC1 to BC5 decoded | non-interlaced 8-bit grayscale, RGB, indexed, RGBA |
| property JSON | editable values and field identities | matching asset and field identities only |
glTF mesh import refuses skins, morph targets, sparse accessors and required extensions. It accepts embedded buffers and buffer files inside the asset folder. The original asset importer can skip skins and morphs.
PNG cannot decode BC6H or BC7. DDS keeps those blocks. Texture replacement rules are in Import and edit textures.
Coordinates
The game uses Z-up. OBJ keeps package coordinates. glTF export adds an exact Z-up to Y-up transform, and glTF import converts back. The glTF UV origin is converted too.
A single-model export keeps positions in model space, ready for reimport. A whole-scene export bakes placement transforms. Reimporting a scene export into a model would apply the placement as a geometry edit.
Native mesh reimport
Position reimport keeps topology, UVs, normals, skin weights and every other stream byte. It accepts one OBJ or glTF mesh for one selected part.
| mesh | position layouts |
|---|---|
| static | signed normalized shorts, strides 8 to 32 |
| entity | the same, plus float positions at stride 48 |
| terrain | not supported |
Vertex order and triangle indices must match the export. Positions must fit the original value range. Shared vertices must get the same position.
Animation export
Export animation… writes an animated node hierarchy from the native rig, without a skinned mesh. Relative clips need a base layer and are refused. The export does not evaluate gameplay inputs or extract root motion. Editable control export is a separate path. See Edit animation controls.
Asset bundles
An asset bundle is a working folder for Blender: asset.sunrise-bundle.json and its files. Studio
checks the source and project fingerprints first. Then it checks every edit before it publishes one
project transaction.
| surface | export | import |
|---|---|---|
| geometry | glTF with native vertex identity, normals, UVs, skin, LOD metadata | fixed-topology positions and supported UV layouts |
| normals | authored static-123 normals and tangents | the verified static-123 profile only |
| skin and rig | joints, bind matrices, weights, linear or dual-quaternion mode | must stay unchanged |
| materials | shader-derived previews, native slots, scalar nodes | slots and scalar constants |
| textures | every DDS subresource; PNG mips and slices | matching DDS or PNG edits; BC1 to BC5 and BC7 re-encode changed blocks |
| animation | sampled rig actions and native control actions | dense control edits in the existing layout |
| particles | effect metadata, emitter objects, constants | decoded constant channels |
| shaders | DXBC, disassembly, Blender text | raw working payloads only |
| other dependencies | typed graph and possible references | raw bytes; unsafe clone remaps fail |
| collision | collision/collision.json: shapes, placements and identity | instance moves as object edits; shape edits as deltas |
LODs come from the original packages. The Blender importer shows one level per model and configuration group. The others stay available for editing.
Exports must stay outside the installation and every project. Bundle and glTF paths cannot leave the bundle or follow file system links. Reference remapping uses installed native readers, never editable manifest claims or matching words.
Blend modes in the preview
A part’s blend comes from its render stage, as the game resolves it. A forced stage blend wins, then the material’s override, then the stage’s own pass blend.
| stage | blend without an override | override applies |
|---|---|---|
0 generate_gbuffer, 10 skin_prepass | opaque | yes |
1 decals, 2 investment_decals | alpha | yes |
6 decals_additive, 7 transparents | additive | yes |
8 distortion | additive, forced | no |
12 depth_prepass | no color, forced | no |
| any other stage | opaque | yes |
One material can draw at several stages. The bundle stores the blend per stage. In Blender, each
other blend gets a copy of the material named <material> [<blend>]. A part that writes no color
gets no preview and Blender hides it.
A stage 1 decals part shows when its shader reads only its own mesh and textures. Most enemy
decals are such shells over the body. A decal that reads frame textures, such as scene depth,
imports hidden: Blender cannot composite it as the game does. Native decal volumes in the
Sunrise sidebar shows it. A bundle whose materials lack the ReadsFrameTextures flag keeps every
stage 1 part hidden. A guardian
export shows its stage 1 parts: they are face markings skinned to the face. A world scene shows
only parts with no stage or at stages 0, 1, 2, 6 and 7.
Material previews
Blender previews follow the dataflow of the game’s pixel shaders. They approximate dye, detail, lighting and effects. They are not translated game shaders.
- Color textures use their sRGB format. Packed channels and normal maps keep data values.
- Native smoothness becomes Blender roughness. Normal XY rebuilds a positive Z.
- An alpha test becomes material alpha with the Dithered method. Texture alpha alone does not make a material transparent.
- Matching wrap or clamp axes map to Blender’s repeat or extend. Point filters use closest sampling. Mixed-axis addressing and other filters are approximations.
- Blender picks its own mip levels from the preview image.
- Metalness and roughness are clamped to 0 to 1.
- A loop with a constant trip count unrolls. A loop whose exit depends on the pixel, such as parallax or ray marching, stops at that exit. Its offset is left out, and the material notes say so.
- A
switchbecomes a selection between its cases. - A texture slot whose material row is empty reads zero, as in the viewport.
- Supported vertex-wave shaders export their motion as geometry nodes that follow the timeline.
- Any other entity part whose vertex shader moves it over time exports that offset as a program, read with every bone at rest. The add-on runs it as a Shader motion geometry-nodes modifier before the armature. A vertex texture read is sampled from the bundle’s image. A shader whose vertex constants Studio cannot evaluate, or that reads a volume texture there, keeps its rest shape.
- Animated material values, such as scrolling, rotating or pulsing layers, follow the timeline at the scene frame rate. Layers that read the view direction use Blender’s camera, normal and UV tangent. The bitangent takes its sign from the UV map, as the game’s does.
- An entity part’s vertex shader is run on sample vertices at the rest pose. That names each shading input: world normal, tangent, bitangent or position, a UV map, COLOR0 or raw POSITION0. A position the shader offsets, such as cloth sway, reads the Blender mesh’s own position.
- World scenes do not carry raw POSITION0. A shader that reads it sees zero there.
- A volume texture is written once as its top-mip slices side by side,
textures/<tag>_volume.png. The preview reads the eight nearest texels and blends them, so Hive and Taken noise moves as in the game. Without that file it reads its mean color. Cube textures read as their mean color. - An effect range with an open bound, such as a gate with no minimum, is written as the largest float of that sign. JSON has no infinity.
- Inputs the game fills at run time stay unknown. When a preview cannot build its color, its notes list the inputs and operations it could not resolve.
- Water is an opaque, smooth surface. It keeps the game’s ripple normal and in-scatter color. Refraction, shoreline fades and screen reflections are left out.
Clip names
A clip’s name is Studio’s name for its tag, else the package name, else a name recovered from an
audio-event path inside the clip, else Clip N. Pose actions are named <name> [Pose_<tag>] and
control animations <name> [<tag>]. Reimport reads the tag from glTF extras, not from the name.
New asset mode
Add new asset clones the root and its verified dependencies into one new package group. Installed and derived sources stay delta-backed. Whole payloads are allowed only when the source was already an added asset. An entity with unresolved internal references cannot be cloned. The import fails before the project changes.
World scenes
world.sunrise-scene.json is a read-only scene, format sunrise-world-scene version 1. It holds the
authored scene with no camera culling and no live transforms. Geometry arrays never enter the JSON.
They live in binary shards of at most 128 MiB, little-endian, each with its byte length and SHA-256.
A failed or cancelled export leaves no partial folder.
| part | content |
|---|---|
| geometry | float32 positions, normals and UVs, uint32 indices; shared when the content is identical |
| vertex inputs | COLOR0 as four uint8 lanes and the UV stream’s z and w as float32, when a part has them |
| materials | per material tag, render stage, terrain dye binding and gear inputs; native slots, a preview, culling |
| textures | used 2D textures, once each, as top-mip PNGs; composed plate atlases as their first mip |
| prototypes | ordered parts, material references, local transforms, stages, LODs, visibility |
| placements | fixed 128-byte rows |
| placement inputs | eight float32 per placement, when any has them: a ground plant’s record and occlusion |
| markers | transforms and source identities, as metadata |
Coordinates are native Z-up with unchanged units. Matrices are 16 float32 values in row-major,
row-vector order. Translation is in elements 12 to 14. A part’s local matrix multiplies its placement
matrix on the left. Blender transposes both and evaluates placement * local.
| offset | placement row field |
|---|---|
| 0 | uint32 prototype index |
| 4 | uint32 name index |
| 8 | 16 float32 matrix values |
| 72 | uint32 bubble hash |
| 76 | uint32 instance source tag |
| 80 | int32 instance source index; a ground plant adds its record number |
| 84 | uint32 placement source tag |
| 88 | int32 placement source offset |
| 92 | uint32 object class tag |
| 96 | uint64 placement identity |
| 104 | uint32 model source configuration tag |
| 108 | uint32 activity tag |
| 112 | int32 state ordinal |
| 116 | int32 scene layer |
| 120 | uint32 state-name index |
| 124 | reserved zero |
The Blender importer creates each mesh and material once. It uses collection instances when the material allows, and linked mesh objects with a shared geometry-node group when a material needs per-object world-basis attributes. A triangle that repeats a vertex index is left out before Blender computes normals. The count is stored on each mesh. A material whose game draw culls back faces turns on Backface Culling; EEVEE and the viewport follow it, Cycles does not.
A SpeedTree part’s LOD is the first level that draws it. Its far billboard is the last level, so the importer hides it with the other lower levels.
A shared export of a large city map is about a sixth the size of a plain GLB of the same scene, and imports into Blender many times faster.
World effects
world.effects.json sits beside the manifest, format sunrise-effects version 1. The manifest does
not list it. A scene with no effect markers writes none. Studio runs its own particle simulation,
the one the viewport draws, and bakes each emitter into looped sprite samples that Blender plays
back.
| part | content |
|---|---|
effects/particles.bin | 20-byte samples: float32 X, Y, Z; float16 size, alpha and spin; uint16 frame |
effects/sprites/<tag>.png | each emitter material’s sprite, at the largest mip no wider than 256 |
| emitters | name, source identity, system and emitter tags, material, sample range, origin |
| materials | the sprite and the blend state; without a usable sprite, an additive soft dot |
| skipped | lens flares and failed systems, which stay hidden markers |
Sprite size and fade are Studio’s choice. Emitters that gameplay starts are baked and hidden. Emitters the game runs on the GPU use Studio’s CPU steps, so they are approximate. One export holds at most 8,000,000 samples; later emitters stay markers.
World lights
world.lights.json sits beside the manifest, format sunrise-world-lights version 1. The manifest
does not list it. It holds one row per placed light, the sun, the sky and the light volumes. Values
are the game’s lighting units before exposure. These are irradiance units, the same as Blender’s.
Sky and Volumes are optional. An add-on that does not know them ignores them.
| field | content |
|---|---|
Kind | Point, Spot or Line |
Position, Direction | world position; the spot axis or the line axis |
Color | linear RGB, largest channel 1 |
Intensity | radiant intensity along Direction; a line gives it per metre |
Omni | intensity a spot adds in every direction |
Back, BackAngle, BackBlend | intensity a negative spot takes away behind the light, along -Direction; never more than Omni |
Range | Blender’s custom distance, where the light ends |
Radius | Blender’s soft falloff radius |
SpotAngle, SpotBlend | full cone angle in degrees, and Blender’s blend |
Length, Points, Aim | line length centred on Position, its number of Blender points, and the axis they shine along as spots, or null |
Copies | identical light rows at this place; the intensities are their sum |
Shadows, Enabled, Animated | shadow flag, the light’s enable program, and whether it changes with time |
TableTag, Row, MaterialTag, PixelShaderTag, BubbleHash, Layer, Fit | source identity and fit notes |
A light of intensity I lights a surface facing it at d metres by
I / (d^2 + Radius^2) * (1 - (d / Range)^4)^2, times the spot’s cone. That is Blender 5.2’s soft
falloff and custom distance, in EEVEE and Cycles.
Sun holds Direction toward the sun, Color and Irradiance. Direction is the time-of-day sky
direction, which the game’s sun pass shades with. ShadowDirection is the light direction the
game’s sun shadows use; it can differ by about 10 degrees.
Sky names two Radiance .hdr images beside the file. Both are equirectangular. Row 0 is straight
up. Column u looks along the azimuth atan2(y, x) = (0.5 - u) * 2 pi, the same layout as Blender’s
Environment Texture.
| field | content |
|---|---|
Path | world.sky.hdr, 512 by 256: the sky the camera sees |
LightPath | world.sky-light.hdr, 128 by 64: the sky light surfaces receive |
TimeOfDay, Keyframe | the time, and the sky lookup slice it picks |
PlacementListTag, LookupTag, BubbleHash | the placed atmosphere, its 3D sky lookup, its bubble |
Volumes lists the map’s local cubemap boxes. Transform maps the box to the world, as a
placement matrix does. The box spans -Extents to Extents.
Studio measures each light by running its game pixel shader on the CPU. The light’s TFX program
runs as the deferred light pass runs it, at full fade. The shader lights a white surface facing the
light, at points along 512 directions and inside the light’s volume. Rings around the brightest
direction give the cone. Radius and Range are fitted once to the summed rings, each sample
weighted by its shell’s area over its own value, so the dim edge counts by its relative error. The
cone is an omni part, a spot, and when it helps a negative spot facing back. The light that reaches
a surface edge-on is added, so a surface at 60 degrees gets the game’s light. A box volume longer
along one axis holds a line light; its extra length is the line. An animated light uses its mean
over 13 seconds.
The add-on sets point and spot power to 4 * pi * Intensity watts, the back spot to
-4 * pi * Back, and sun strength to Irradiance. A row of points along a line shares
Intensity * Length. A file without Points gets one point per half range. Cycles ignores custom
distance, so each light’s shader applies the same (1 - (d / Range)^4)^2 window.
Studio builds the sky from the placed atmosphere, as the game’s sky lighting does:
- The sky colour of each direction is the atmosphere’s 3D lookup at the time of day, plus a glow around the sun. This is a CPU port of technique 418; it matches Studio’s GPU result within 1% on average.
- The game folds the sky into a mirror-ball map, blacks out directions more than 35 degrees below the horizon, and halves it down to 2 by 2 texels. It reads surface light from that map between its last two levels.
world.sky-light.hdris built so that Blender’s diffuse light from it equals that read for every surface direction. The fit uses order-2 spherical harmonics. On the two maps checked it is within 5% of Studio’s sky light per pixel.
The game then multiplies the sky light by baked occlusion. The export leaves that out: Blender shades occlusion itself.