# Routers on the guest SDK: your plugin or program speaks Fusion

For games no kit covers (Unity Mono, Bethesda, Source, custom engines) through **your own plugin**
in the game's mod loader (BepInEx, MelonLoader, OWML, SKSE/F4SE/NVSE, a Source server plugin, an
injected DLL), and for open-source games. The SDK is one implementation with three faces:
- C#: `FusionGuest.cs` (one file; C# 7.3, .NET Framework 3.5+, no `unsafe`): `Fusion.Guest`.
- C/C++: `fusion_guest.h` + `fusion_guest_c.dll`: `fusion_guest_*`.
- Rust: the `fusion-guest` crate.
`fusion-cli where` shows where they are. Ship `fusion_guest_c.dll` next to your plugin's DLL.

## Porting a passthrough mod

`fusion-cli port <mod folder | owner/repo>` does this sorting for a whole mod and writes
`fusion/PORT.md` (each file, its fate, the lines to keep), the router, the scenario and
`FusionPort.cs` / `fusion_port.cpp`. The table is what it applies:

| In the mod | In Fusion | Do |
|---|---|---|
| its link (shared memory such as `Local\SkyCraft_v1`, UDP, WebSocket) | the SDK | delete; call the SDK |
| a fork of SkyCraft's Fabric mod | `minecraft.player` (Fusion's) | delete; your scenario uses it |
| the game draws the other game's blocks | Fusion draws every game's layers | delete |
| the game's camera drives Minecraft's | Fusion's camera drives every game | read `camera()`, move your camera there |
| raycasts into a collision field for Minecraft | `send_triangles` / `send_boxes` (16 m chunks done for you) | keep the raycasts, send the result |
| NPC proxies | `send_entities` (a block that gives `entities`) | keep the NPC finder, send a list |
| "host paused" | `Sleep` / `Wake` events | map to your pause |
| frame sync, fallback when the other game hangs | Fusion's compositor and heartbeats | delete |
| install scripts | `[router.setup]` (below) | turn them into steps |

## router.toml

`fusion-cli router new <id> --name "<Name>" --kit sdk --steam-app <N>` writes it. Then:

```toml
[router]
id = "mygame"
name = "My Game"
version = "0.1.0"
game_exe = "MyGame.exe"
units = { length = "m", up_axis = "y", health_max = 100 }   # you send Fusion's space

[router.link]
protocol = "fusion"
game_id = "mygame"              # what your plugin passes to Connect / TryConnect

[router.launch]
steam_app = 123456

[[block]]
id = "mygame.world"
name = "My Game's World"
kind = "world"
gives = ["layer:color+depth", "collision"]
cost = { ram_gb = 3.0, gpu = "medium", must_stay_awake = true }

[[block]]
id = "mygame.npcs"
name = "My Game's NPCs"
kind = "npc_group"
gives = ["entities"]
actions = ["remove", "set_health"]
cost = { ram_gb = 0.0, gpu = "none" }

[router.setup]                  # one click for players: check the loader, copy your plugin
installed_when = "{game}/BepInEx/plugins/MyGameFusion/MyGameFusion.dll"

[[router.setup.require]]
name = "BepInEx 5"
check = "{game}/BepInEx/core/BepInEx.dll"
guide = "Install BepInEx 5 (x64) into the game's folder and start the game once."
url = "https://github.com/BepInEx/BepInEx/releases"

[[router.setup.step]]
copy = "{router}/plugin"        # your built plugin + fusion_guest_c.dll, in the router folder
to = "{game}/BepInEx/plugins/MyGameFusion"
```

## The plugin's loop (C#; C has the same calls)

```csharp
Fusion.Guest guest;
void Update()                                    // the plugin's per-frame hook
{
    if (guest == null && (guest = Fusion.Guest.TryConnect("mygame")) == null) return;
    foreach (var e in guest.Update())            // once per frame
    {
        switch (e.Kind)
        {
            case Fusion.EventKind.Sleep: Pause(true); break;
            case Fusion.EventKind.Wake: Pause(false); break;
            case Fusion.EventKind.Action: guest.Reply(e.RequestId, Act(e.Entity, e.Name, e.Args)); break;
            case Fusion.EventKind.Spawn: case Fusion.EventKind.Teleport: guest.Reply(e.RequestId, false); break;
            case Fusion.EventKind.Query: guest.Answer(e.RequestId, false, "no questions"); break;
        }
    }
    if (guest.Ended) { guest.Dispose(); guest = null; return; }
    guest.SendEntities(npcs);                    // List<Fusion.Entity>: Id, Kind "mygame.npc", Name, Feet, Health...
    guest.SendTriangles(groundNearCamera);       // 9 floats per triangle, Fusion's space
}
```

**Fusion's space**: meters, Y up, right-handed, -Z forward, around an origin you pick near where
play starts. Unity: `(x, y, -z)`, yaw `-unityYawDegrees` in radians. Z-up right-handed engines
(Source in inches, Bethesda in ~1.43 cm units): `(x, z, -y) * metersPerUnit`, yaw = game yaw − 90°.
Check with F7 (collision) and F8 (entities) in Fusion.

## The picture

1. `guest.TryGetCamera(out cam)`; move your world camera to it before it renders (position:
   `CameraToWorld[12..14]`; forward: `-column 2`; up: `column 1`; `FovYDegrees`), and give it back
   after.
2. Render into a texture of `RenderSize`, and depth into a one-channel float texture.
3. GPU (Direct3D 11): from the render thread, `fusion_guest_d3d11_publish(g, ctx, color, depth,
   &mode, flags, cam.camera_id)`; from Unity's main thread, `id = guest.D3D11Queue(color.GetNativeTexturePtr(),
   depth.GetNativeTexturePtr(), Fusion.DepthMode.Device(near, far, SystemInfo.usesReversedZBuffer),
   Fusion.PictureFlags.BottomUp | Fusion.PictureFlags.Opaque, cam.CameraId)` then
   `cb.IssuePluginEvent(Fusion.Guest.D3D11RenderEvent, id); Graphics.ExecuteCommandBuffer(cb)`.
   -1 → `guest.Error`; use the CPU path.
4. CPU (anything else): `guest.Picture(w, h, true)` once per size, then
   `guest.PublishFrame(rgba8, depthMeters, cam.CameraId)` (alpha below 128 = nothing there).

## A character players play as (optional)

Block: `kind = "character"`, `needs = ["input"]`, `gives = ["pose"]`. Each frame:
`guest.TryGetInput(out input)`; while `input.Possessed != 0`, move with `MoveX`/`MoveY` and turn to
`Yaw`/`Pitch` through the game's own movement; `guest.SetPose(id, x, y, z, yaw)` and
`guest.SetView(eyeX, eyeY, eyeZ, 0, 0, Fusion.ViewFlags.InWorld)`. To walk on another game's
World, add `needs = ["collision"]` and read `Collision` events with `TryGetCollision`.

## Check

`fusion-cli router check routers/<id>`, then `fusion-cli router conform routers/<id> --start`
(the plugin installed, Fusion closed). Working examples: `example-sdk.router.toml` and Fusion's
`sdks/csharp/example` (a C# world), `tools/c-world` (C).

Sharing a router whose package carries a DLL needs a verified author (Fusion's [`docs/trust.md`](https://steonmod.com/docs/trust)).
