# Unreal / Blender handoff

The generated art is ready in `../art/generated/`: three GLBs with embedded PBR textures, a packed `singularity-art.blend`, and two Cycles preview renders. All art was built and rendered in Blender 4.5.9; no external asset downloads or AI images are used.

## Open the artwork

Open `../art/generated/singularity-art.blend` in Blender. Collections separate the six-part robot, deck/bridge, and cargo/finish/sweeper. The robot uses painted metal armour, machined fasteners, optical lenses, rubber joints, grippers, and treaded boots. Texture images are packed into the blend and embedded in each GLB. These are rigid meshes, not a skeletal character or animation montage.

Regenerate with:
```powershell
& '../.tools/blender-4.5.9-windows-x64/blender.exe' --background --python ../art/blender/generate_singularity_assets.py
& '../.tools/blender-4.5.9-windows-x64/blender.exe' --background --python ../art/blender/verify_assets.py
```
Commands above run from `unreal/`. Any Blender 4.5 installation may replace the portable executable path.

## Unreal setup (requires editor verification)

Build the supplied game/editor targets in Unreal 5.4, then create a level at `/Game/Singularity/Maps/Lobby`. That configured level is not yet supplied as a .umap. Import the three GLBs using scene import so course object transforms are retained. Keep body meshes separate, retain their local pivots, and enable material/texture import. Add simple collision for any meshes you wish to simulate.

Create a Blueprint subclass of `ASingularitySharedBody`. Assign `RoleMeshes` in order: EyesHead, LeftArm, RightArm, TorsoBack, LeftLeg, RightLeg. Meshes are local to their solver node; keep the component scale at one after the importer converts metres to centimetres. TeamMaterial overrides only named TeamCoral/TeamMint paint slots, preserving rubber, steel, and optics.

Server coordinates are metres: lateral X, up Y, forward Z. `ApplyAuthoritativeSnapshot` maps them to Unreal centimetres as `(Z, X, Y) * 100` and maps torso-first node order into role order. Ranked presentation is kinematic. Explicitly enabling Chaos on the parts is an experimental local preview; it is not the ranked simulation. Constraints have midpoint anchors and angular limits; local inputs expire after 500 ms and a hop requires a grounded press.

## Networking boundary

`USingularitySpacetimeBridge` is an event interface, not a connected SpacetimeDB client. A transport still needs authentication, subscriptions, JSON decoding, role leases, room/result handling, and reducer calls. The actual server join role is a numeric 0–5; input reducer payload is x/z/action. ClientTick and Role in the local packet are routing metadata, not additional input reducer arguments.

Bind the player's bridge OnInputReady to the transport's input reducer, and feed decoded team snapshots into OnSnapshotReady / ApplyAuthoritativeSnapshot. Do not bind local force inputs to the ranked body. The controller uses WASD/Space at up to 30 Hz through DefaultInput.ini. The GameMode creates its bridge before BeginPlay; remote native clients own a local controller bridge.

## Verification limits

The exported files pass binary GLB tests and a Blender import round trip. Preview renders were visually inspected. Unreal is not installed in this environment: C++ compilation, import orientation, collision, Blueprint wiring, and native multiplayer remain unverified. The web build does not validate Unreal code.
