# Fusion Protocol (v1)

How Fusion and a guest game talk. The exact byte layout is defined in
`schema/fusion.fidl`; this page explains the ideas.

## The pieces

| Piece | What it is | Who writes |
|---|---|---|
| **Discovery mapping** `Local\Fusion_Discovery` | A list of the games Fusion is waiting for, each with the name of its channel | Fusion adds entries; a guest *claims* its entry |
| **Channel** (one per guest) | One shared-memory block holding everything below | both |
| Header | magic number, protocol version, **layout hash**, process ids, heartbeats, states, the GPU to use | both (different fields) |
| Slots: `camera`, `input` | latest camera and controls | Fusion → guest |
| Slots: `layers`, `pose`, `view` | the guest's picture layers, its character's pose, where that character looks from | guest → Fusion |
| `layer_buffers` | who owns each picture buffer right now | both, with compare-and-swap |
| Rings: `to_guest`, `to_host` | events and commands, in order | one producer each |
| **Bulk stream** (optional, its own mapping) | big messages: textures, meshes | guest → Fusion |

### Why discovery?
Games are often started by Steam or their own launcher, so Fusion can't hand them a
command-line argument. Instead, the kit inside the game opens the discovery mapping,
finds the entry for its game id (e.g. `minecraft`), claims it, and opens the channel
named there.

### Slots ("latest value")
For data where only the newest value matters (camera, pose). The writer never waits
and the reader never sees half an update: a **seqlock** (sequence number that is odd
while writing) lets the reader detect an update in progress and simply retry.

### Rings (events and commands)
For things that must all arrive, in order (damage, "spawn this", collision data).
Each ring has one producer and one consumer. A message uses one 256-byte slot, or
several for big data (collision chunks up to ~240 KB). The producer never waits: if
the ring is full the message is dropped, counted in `dropped`, and the sender is told.

### Collision
`CollisionChunk` messages carry Fusion's Collision Hub in both directions:
- **guest → Fusion:** a game whose block gives `collision` publishes its collision;
- **Fusion → guest:** a game whose block needs `collision` gets the hub's collision around
  its pose (everything except its own).

Fusion also tells a guest once about each **empty** chunk around it (a chunk message with no
triangles), so the guest can tell "nothing here" from "not sent yet": Minecraft holds its player
until the ground below is known.

Each message is one **16 m chunk** of Fusion's grid (chunk (0, 0, 0) covers 0..16 m on
every axis) and **replaces** what its sender had in that chunk; no triangles means "empty
here". Positions are in **Fusion's space** (meters, Y-up, right-handed, around Fusion's
origin), like every position in the protocol: the kit converts, because only it knows
where Fusion's origin sits in its game. Triangles are cut to the chunk's cube and wound so
`(b - a) × (c - a)` points out of the solid. Sending an unchanged chunk again is cheap:
Fusion compares contents and ignores it. `tools/fake-guest` (`--ramp`) is the reference.

### Entities
A game whose block gives `entities` sends `EntityList` messages (guest → Fusion) into
Fusion's **entity registry**: its NPCs, villagers, vehicles or crates. Each list is the
**whole** list and replaces the one before, so an entity missing from it is gone (an empty
list means none left). Send it a few times a second while things move.

Each `EntityInfo` has:
- `entity`: the game's own id for it, the same in every list while it exists. Fusion gives it
  a Fusion id of its own, which stays the same as long as the game keeps listing it.
- `transform`: where it is (for a creature, its feet) and how it's turned, in Fusion's space.
- `bounds_min` / `bounds_max`: its box in meters, relative to its position and turned with it
  (a standing person is about (-0.3, 0, -0.3) .. (0.3, 1.8, 0.3)).
- `health` / `max_health` (0 = it has no health), `team`, `kind` ("zombie") and `name`
  ("Lydia", or "" to show the kind).

A long list goes in **parts** with the same `list_id` (`part` 0 .. `part_count` - 1, at most 64
entities per part is a good size). Fusion uses a list once all its parts arrived; a list with a
part lost to a full ring is skipped, and the next one replaces it. `tools/fake-guest`
(`--npcs`) is the reference.

When a game's process ends, Fusion removes its entities and its collision, and stops showing
its picture. A game that's only frozen or asleep keeps them.

### Spawning
Fusion asks a game to make something with a `Spawn` message (Fusion → guest): a `kind` such as
"zombie", standing at `transform` (its feet, in Fusion's space, facing the transform's −Z). The
game's router decides which kinds it can make. The guest answers with an `ActionResult` carrying
the same `request_id` (`ok` 1 or 0); the new thing then shows up in its next `EntityList`. In
Godot: `FusionGuestView.request_spawn(kind, transform)` and `get_request_result(id)`. The
Unreal kit is the reference (`kits/unreal`, `Router.spawn`).

### Picture layers: GPU or CPU
A guest describes up to 4 layers in the `layers` slot. Each has `LAYER_BUFFERS` (3) buffers, and
`layer_buffers` says who owns each one; ownership only changes by compare-and-swap (FREE →
WRITING → READY → HOST → FREE), so neither side waits.
- **GPU layers** (the default): shared Direct3D 12 textures (color + linear depth) and a fence,
  composited at their real depth. fake-guest and the Unreal kit use them.
- **CPU layers** (`LayerFlags::CPU`): plain pixels (RGBA8) in a mapping the guest creates, named
  `name_base`. No fence. For games whose graphics can't share textures with Direct3D 12.
  - **Color only** (`depth_format` 0): buffer *i* is at `i * width * height * 4`. Fusion shows it
    flat on top, like the game's own interface (`LayerKind::GUI` above `HAND`); Minecraft (OpenGL)
    sends its hand + HUD this way. `LayerFlags::BOTTOM_UP` says the first row is the bottom
    (OpenGL read-backs).
  - **Color + depth** (`depth_format` 41, R32F; step 5.9): buffer *i* is at
    `i * width * height * 8`: the RGBA8 pixels, then one little-endian `f32` per pixel, the
    distance in front of the camera in the game's length units. Fusion draws it in 3D with the
    same shader as GPU layers, so props and other games sort against it. The guest SDK's
    `picture(w, h, true)` makes one ([`guest-sdk.md`](https://steonmod.com/docs/guest-sdk)).
- A new size means new buffers: the guest bumps `generation`, resets the layer's buffers, and
  writes the layer again.

### Bulk streams
Ring messages top out at about 240 KB; a texture atlas or a mesh can be much bigger. A guest that
sends big data makes its own mapping (a **bulk stream**: `BulkHeader`, then a byte ring) and
announces it with a `BulkStream` message after every (re)connect. Each message in it is a
`{kind, len}` header plus exactly the bytes it would have in a ring, so Fusion handles bulk and
ring messages the same way. The writer never waits for long: a message that doesn't fit is
dropped and counted. Rust: `fusion_protocol::bulk`; Java: `fusion.kit.BulkWriter`.

### Mesh layers
Instead of pictures, a guest can send **geometry its own renderer built** (models, tints, baked
shading, animation), and Godot draws it, lit and depth-tested with everything else:
- `MeshTexture`: a texture (RGBA8, sRGB, top row first) by id, or a region of one (an animated
  sprite). Godot can only upload whole textures, so region updates are gathered and uploaded at
  most once a second (v0).
- `MeshPart`: one part of a mesh (one texture, opaque or translucent), a triangle list, front faces
  counter-clockwise (glTF). A mesh's parts share an `update_id`; Fusion shows the mesh when all
  `part_count` parts arrived, replacing its previous update; `part_count` 0 removes it. `attach`
  says whether `origin` is in Fusion's world or follows the guest's pose (a body). Only the newest
  update per mesh is built each Fusion frame.
- `MeshVertex`: position, uv, color (tint), a packed normal (0 = use the triangle's) plus a glow
  byte, and a cutout flag (alpha-tested leaves).
- `MeshLights` (point lights belonging to a mesh) and `MeshClear` (remove every mesh).

Minecraft's world, its player's body and its entities come this way (`kits/fabric`).

### Playing as a game's character ("possess", BUILD.md steps 1.3b and 1.6)
When a scenario plays as one of a game's Character blocks, Fusion:
- puts the character at the World's spawn point with a `Teleport` (answered by an
  `ActionResult` once the character stands there);
- does the mouse look itself and sends the result in `InputState` (`yaw`, `pitch`, flags
  `POSSESSED | LOOK`), so its camera never waits for the game;
- sends keys and mouse buttons as `RawInput` messages (keyboard scancodes as USB HID usages,
  the same numbers SDL uses), so the game's own controls, menus, chat and inventory work;
- also fills `InputState`'s generic movement from the keys held down (1.6): W/S as `move_y`,
  D/A as `move_x`, Space/Left Ctrl/Left Shift as the `JUMP`/`CROUCH`/`SPRINT` buttons, the left
  and right mouse buttons as `ATTACK`/`USE`. A kit that moves its character itself uses these
  (fake-guest's cube does), one that replays keys (Minecraft's) ignores them;
- moves its camera to the character's `View` (its eyes, or a third-person spot; the game's
  field of view) once the guest writes one with `ViewFlags::IN_WORLD`. While the game shows a
  screen of its own (`SCREEN_OPEN`), the mouse cursor is free and its position is sent instead.
  A guest that writes a `Pose` but never a `View` (for 1 s) gets Fusion's own third-person
  camera, 4 m from a point 1 m above the pose, turned by the mouse (1.6).

### Actions
`Action` (Fusion → guest) asks the game to do something to one of its entities, by the game's own
id (as in its `EntityList`): "remove", and later "stun", "play_animation"... The game's router
decides which actions it knows, and answers with an `ActionResult`. Godot:
`FusionGuestView.request_action(entity, action, args)`. Minecraft's kit knows "remove".

**Steering** uses two standard actions, for blocks that `need`
`steering`: Fusion finds a path on its navmesh and walks the NPC along it one waypoint at a time.
- `move_to`: `args[0..3]` = the next waypoint (x, y, z) in **Fusion's space** (meters, Y up),
  like `Spawn`; the kit converts it. Walk there, then wait for the next one.
- `stop`: stop where you are.

fake-guest's walkers know both.

### Questions from Fusion's tools
`Query` (Fusion → guest) carries text: a command and what follows it. The kit answers with a
`QueryReply` carrying the same `request_id`: `ok` = 1 and JSON, or `ok` = 0 and what went wrong
in plain words (an unknown command says which ones the kit knows). Both have a text tail
(`TextByte`s, UTF-8) and stay under 64 KB: a long list is cut and says `"cut": true`. Router
Studio (F4) and `fusion-cli game` ask them; Godot: `FusionGuestView.query(text)` and
`get_query_reply(id)`. Nothing is changed by a question except `reload`.

| Command | Answer (JSON) |
|---|---|
| `info` | `kit`, `game`, `router`, `map` (where the game is), `player` (its player's class), `player_path`, `commands` |
| `classes [text]` | `classes`: live objects grouped by class, most first: `class`, `path` (the class's own path, for spawning), `count`, `role` (`character`, `pawn` or `actor`), `parents` (nearest first), `player` (the player is one); `total`, `cut`. `text` keeps classes whose name contains it |
| `objects CLASS [text]` | `objects` of that class, nearest to the player first: `name`, `path`, `class`, `at` (game units), `distance` (m), `player`; `total`, `cut` |
| `inspect OBJECT` | an object (by path, or a live actor's name): `name`, `path`, `class`, `parents`, `role`, `at`, `fields`: `name`, `type` (`Float`, `Object`, `Struct`...), `owner` (the class that declares it), `value` when it's plain (numbers, text, booleans; an object field's value is that object's path, to inspect next) |
| `find TEXT` | `classes` as above, plus `objects` whose own name has the text |
| `test ROUTER.TOML` | the kit tries this router's data on the running game without changing anything: `checks` (`ok`, `what`, `detail`): the player's class, the map, each entity rule (how many live objects match, what its health fields read), each spawn asset |
| `reload ROUTER.TOML` | the running game uses this router's data from now on (Router Studio's "Apply live", `fusion-cli router reload`); `{"reloaded": true}`. A line `--- router.lua ---` after the TOML is followed by the router's Lua, which the kit runs instead of the installed file (hot reload); a script that fails is refused and the running router stays |

The Unreal kit answers all of them from UE4SS's reflection (`router_api.lua`, `M.answer`);
fake-guest answers the first five about its cube, plank and walkers.

### Sleep and stage mode
Fusion's scheduler sends these to a game that isn't the World's or the played Character's, or
when free RAM runs low:
- `Sleep`: release input and mute; about 150 ms later Fusion suspends the process. A kit should
  set its state to `SLEEPING`. Fusion shows the game as "asleep", not "not answering".
- `Wake`: sent right after Fusion resumes the process; the kit resets its watch of Fusion's
  heartbeat and goes back to `RUNNING`.
- `SetFrameCap` / `SetResolutionScale`: stage mode. `fps = 0` and `scale = 1` mean "no limit
  from Fusion": go back to the game's own settings. Fusion only sends them when it limits a
  game, or to lift a limit it set.

Kits that ignore these still sleep (the process is suspended), they just don't get a warning.

### Kits in other languages
The generated files only describe the byte layout. A kit also needs the rules: discovery,
heartbeats, seqlocks, rings, layer buffer ownership. Rust kits use the `fusion-protocol` crate.
Java kits use `kits/common/java/fusion/kit` (`FusionGuest`, `BulkWriter`), which
`crates/fusion-protocol/tests/java_guest.rs` checks against Fusion's own Rust side.

### Heartbeats
Each side bumps a counter at least every 100 ms. If the other side's counter stops
changing for 500 ms, it counts as **lost** (crashed, frozen, or suspended). Fusion then
drops that block and keeps running. If the game's process is gone, its entities and
collision go too. A lost game's discovery entry is opened again, so a kit that gave up on
Fusion (Minecraft's waits 10 s) can connect again; when it's back, Fusion sends its collision
again from scratch.

### Version safety
The header carries a **layout hash**: a fingerprint of every struct, field, offset and
constant in the schema. If a kit was built against a different schema, opening the
channel fails with a clear "protocol mismatch" error instead of reading garbage.

### Same GPU
The header carries the adapter LUID of Fusion's Direct3D 12 device. Guests must create
their shared textures on that GPU.

## Changing the protocol

1. Edit `schema/fusion.fidl`. Fields must be naturally aligned with no hidden padding;
   the generator tells you exactly where to add a `_padN` field.
2. Run `cargo run -p fusion-codegen`. It rewrites every generated file:

   | File | For |
   |---|---|
   | `crates/fusion-protocol/src/generated.rs` | Rust (Fusion, Rust kits) |
   | `kits/common/cpp/fusion_protocol.h` | C++ kits (e.g. SKSE) |
   | `kits/common/csharp/FusionProtocol.cs` | C# kits (Unity: MelonLoader/BepInEx; C# 9, unsafe code on) |
   | `kits/common/java/fusion/protocol/FusionProtocol.java` | Java kits (Fabric; FFM API, JDK 22+); the rules are in `kits/common/java/fusion/kit` (hand-written) |
   | `schema/fusion.layout.txt` | the canonical layout and its hash |

3. Bump `version` in the schema if old kits can't work with the change.
4. `cargo test --workspace`. A test fails if the generated files are stale.
5. `cargo run -p fusion-codegen -- --check-languages` compiles small checker programs
   in C++, C# and Java and confirms each compiler's real layout matches the schema
   (Rust is checked at compile time).

### Using the generated code
- **C++:** `#include "fusion_protocol.h"`; map the channel and cast to `fusion::Channel*`.
- **C#:** `var ch = (Channel*)ptr;`. Arrays of structs are raw bytes with an accessor:
  `ch->pose.value.bones(i)` returns a `Transform*`.
- **Java:** `segment.get(JAVA_INT, Channel.OFFSET_HEADER + ChannelHeader.OFFSET_MAGIC)`.
  For atomics use `JAVA_LONG.varHandle()` with `getAcquire` / `setRelease` /
  `compareAndSet` on the same offsets.

## Tests

- `cargo test -p fusion-protocol`: unit tests, plus `tests/cross_process.rs`, which
  starts a real second process as a fake guest and checks:
  - 100,000 slot reads with no torn reads while the guest writes;
  - 50,000 events, twenty 72 KB collision chunks and 5,000 commands, all in order;
  - a crashed guest and a frozen guest are each detected in under 1 second.
- `tests/java_guest.rs` runs `kits/common/java/fusion/kit/SelfTest.java` as a guest (skipped
  without `java` on PATH): discovery, camera and controls, rings both ways, a teleport, a CPU
  layer, and a 96 KB mesh through a bulk stream.
