# From your passthrough mod to a Fusion router

For authors of passthrough mods (a SkyCraft port, "Minecraft in <game>", a ReShade bridge) who
want their game in Fusion. Start with [`making-routers.md`](https://steonmod.com/docs/making-routers) if you haven't.

## The fast way: `fusion-cli port`

In your mod's folder (or with its GitHub name), run:

```
fusion-cli port .                      # or: fusion-cli port yourname/YourCraft
```

It reads your mod without building it and writes `fusion/`:
- **`PORT.md`**: every file of your mod sorted by the job it does. What Fusion already does
  (the link, drawing Minecraft, input, frame sync: delete), what you learned about your game
  (the player, collision, NPCs, pause: keep, with the lines that touch the game and what each
  becomes in Fusion), what Fusion has no port for yet (items, digging), and your Minecraft-side
  changes that aren't in SkyCraft's mod.
- **`routers/<game>/`**: the router, with your loader's one-click setup (SKSE, BepInEx, OWML...)
  and the game's launch. It passes `fusion-cli router check`.
- **`scenarios/<your mod>.toml`**: your mod as a Fusion scenario.
- **`FusionPort.cs`** or **`fusion_port.cpp`**: Fusion's side of your plugin, with a TODO for each
  job, naming the code of yours that does it today.

Then tell your agent: **"Bring this mod into Fusion: follow fusion/PORT.md."** It reads one page
of a few thousand tokens instead of your whole repository (the median mod on steonmod.com is
about 690 KB of code; its `PORT.md` is about 7 KB).

The rest of this page explains what `PORT.md` applies, part by part.

**What you keep is what you learned about your game**: which object is the player, how to get
past its menus, where its NPCs and their health are, how it pauses, which build works. **What
you drop is everything Fusion now does for every game**: the link, the Minecraft side, drawing
the other game, frame sync, the installer. A port that took weeks becomes a router of tens of
lines (a kit) or your plugin minus most of its code (the SDK).

## The one big change: your game is a guest, not the host

In your mod, your game is the **host**: it runs in front, draws everything (its world and
Minecraft's blocks), and drives a hidden Minecraft through a private link.

In Fusion, **Fusion is the host**, and every game is a guest:
- your game gives a **World** block: it draws its world **from Fusion's camera**, and sends its
  ground and walls (collision) and its NPCs;
- Minecraft is the `minecraft.player` Character block, already in Fusion;
- Fusion layers the pictures with their depth, hands your collision to Minecraft, runs both
  games' sleep and wake, and puts the player's camera where Minecraft's eyes are.

"Minecraft in your game" is then a scenario, not a mod:

```toml
[scenario]
name = "Minecraft in My Game"
description = "Play as the Minecraft player in My Game's world."

[[block]]
use = "mygame.world"

[[block]]
use = "minecraft.player"
```

And because your game is now a block, the same router also fuses it with every other game in
Fusion: another game's character in your world, your NPCs in someone else's.

## Part by part

| In your mod | In Fusion | You |
|---|---|---|
| The link: shared memory (`Local\SkyCraft_v1`), UDP, a WebSocket, its message schema | the Fusion protocol, through a kit or the guest SDK | **delete it** |
| Minecraft's Fabric mod (SkyCraft's, plus your patch) | `routers/minecraft`, kept up to date with Fusion. Your patch's choices (creative mode, no digging) become the player's own modpack or Lua addons | **delete the fork**; note in `notes.md` what your patch did |
| Your game draws Minecraft's blocks and body | Fusion draws Minecraft's meshes itself, lit and depth-sorted | **delete** |
| Camera sync (your camera drives Minecraft's) | Fusion's camera drives every game. Your game draws from it: a kit does that by itself; with the SDK you move your camera to `camera()` each frame | **turn it around**: read Fusion's camera instead of sending yours |
| Collision for Minecraft (raycasts into a block field, BSP brushes...) | your World gives `collision`: a kit traces it by itself; with the SDK you send triangles or boxes, in 16 m chunks (done for you). The Collision Hub hands it to Minecraft and to every other game | **keep your collision code, send its result** |
| NPC proxies, damage formulas | `npc_group` block: list them (`entities`), with health and team; Fusion's entity registry, Lua and the cards use the list. Units (`health_max`) replace pair-specific formulas | **keep your NPC finder, send a list** |
| Pause ("host paused" flag), focus tricks, a hidden window | sleep and wake (`Sleep`/`Wake` events); kits keep the game running unfocused | **map pause to `Sleep`** |
| Frame sync (wait for frame N, reuse N-1), fallback when the other game hangs | Fusion's compositor and heartbeats | **delete** |
| Installer scripts, "copy these files", JVM flags | `[router.setup]`: requirement checks (is BepInEx there?) and copy steps, which players run with one click in My Games, and can undo | **turn the script into steps** |
| "Works with version X" | `tested_builds` (`router recon` prints the build's hash); `fusion-cli game diff` after an update | **list the build** |
| README credits, what works | `contributors`, `based_on`, `license` in `router.toml`; `notes.md`; `MODLOG.md` | **move them over**, keep SkyCraft's MIT notice if you kept its code |

## Which path

| Your game | Do this |
|---|---|
| Unreal 4/5 | Drop your plugin: write a router on the Unreal kit ([`agent/unreal.md`](https://steonmod.com/docs/agent-unreal)). Most of what your plugin found is one line of `[block.bind]` data each |
| Unity IL2CPP | The same on the Unity kit ([`agent/unity.md`](https://steonmod.com/docs/agent-unity)) |
| Unity Mono (BepInEx 5, OWML, MelonLoader on Mono) | Keep your plugin; swap its link for **`FusionGuest.cs`** (below). The Unity kit doesn't run Mono games yet |
| Skyrim, Fallout (SKSE/F4SE/NVSE), Source (a server plugin), an injected DLL | Keep your plugin; swap its link for **`fusion_guest.h`** (below) |
| Open source | Link the SDK into its source ([`guest-sdk.md`](https://steonmod.com/docs/guest-sdk)) |

## With the guest SDK: your plugin, its link swapped

Make the router first: `fusion-cli router new mygame --name "My Game" --kit sdk --steam-app <N>`.
It writes `routers/mygame/router.toml`; set `gives` on the world block to what you'll send
(`layer:color+depth`, `collision`, `entities`) and add an `npc_group` block if you list NPCs.

Then, in your plugin (C# here; C/C++ has the same calls, `fusion_guest_*`):

```csharp
Fusion.Guest guest;      // FusionGuest.cs; put fusion_guest_c.dll next to your plugin's DLL

void Update()            // your plugin's per-frame hook
{
    if (guest == null)
    {
        guest = Fusion.Guest.TryConnect("mygame");       // the router's game_id; null until Fusion asks
        if (guest == null) return;
    }
    foreach (var e in guest.Update())                    // once per frame: heartbeat, sleep, frame cap
    {
        if (e.Kind == Fusion.EventKind.Sleep) PauseGame(true);      // your old "host paused" code
        else if (e.Kind == Fusion.EventKind.Wake) PauseGame(false);
        else if (e.Kind == Fusion.EventKind.Action) guest.Reply(e.RequestId, DoAction(e.Entity, e.Name));
        else if (e.Kind == Fusion.EventKind.Spawn || e.Kind == Fusion.EventKind.Teleport) guest.Reply(e.RequestId, false);
        else if (e.Kind == Fusion.EventKind.Query) guest.Answer(e.RequestId, false, "not supported");
    }
    if (guest.Ended) { guest.Dispose(); guest = null; return; }   // Fusion closed: give the game back

    guest.SendEntities(FindNpcs());                      // a few times a second is plenty
    guest.SendTriangles(CollisionNear(cameraPosition));  // your collision code's result, Fusion's space
}
```

**Fusion's space** is meters, Y up, right-handed, -Z forward. Pick an origin near where play
starts (floats lose precision far away) and convert at your plugin's edge:

| Your engine | To Fusion |
|---|---|
| Unity (meters, Y up, left-handed) | `(x, y, -z)` minus the origin; yaw: `-unityYawDegrees` in radians |
| Z-up, right-handed (Source: inches; Bethesda: units of about 1.43 cm) | `(x, z, -y) * metersPerUnit` minus the origin; yaw: game yaw − 90° |

Check with Fusion's **F7** (collision) and **F8** (entities): boxes in the wrong place mean the
conversion is off.

### The picture: draw from Fusion's camera

Each frame, before your world camera renders:

1. `guest.TryGetCamera(out cam)`: Fusion's camera. `cam.CameraToWorld` is column-major; the
   camera looks down its -Z. Convert its position and its forward (`-column 2`) and up (`column 1`)
   to your engine and put your world camera there, with `cam.FovYDegrees`.
2. Have the camera draw into a texture of `RenderSize` (and its depth into a one-channel float
   texture), instead of the screen.
3. Hand both to Fusion:
   - **On the GPU (Direct3D 11), best:** from the render thread (a C++ `Present` hook):
     `fusion_guest_d3d11_publish(g, context, color, depth, &mode, flags, cam.camera_id)`. In Unity,
     which draws on its own render thread: `id = guest.D3D11Queue(color.GetNativeTexturePtr(),
     depth.GetNativeTexturePtr(), Fusion.DepthMode.Device(near, far, SystemInfo.usesReversedZBuffer),
     Fusion.PictureFlags.BottomUp | Fusion.PictureFlags.Opaque, cam.CameraId)`, then
     `commandBuffer.IssuePluginEvent(Fusion.Guest.D3D11RenderEvent, id)` and
     `Graphics.ExecuteCommandBuffer(commandBuffer)` after the camera rendered.
     About 1 frame behind Fusion's camera.
   - **On the CPU, works everywhere** (Direct3D 9, OpenGL, another GPU): read the pixels back
     (Unity: `AsyncGPUReadback`) and `guest.PublishFrame(rgba8, depthMeters, cam.CameraId)`. A few
     frames behind.
   - If the GPU path returns -1, `guest.Error` says why; switch to the CPU path.
4. Give your camera back its own place after it rendered, so the game's own code never sees it
   moved.

Fusion's Unity kit does exactly this for IL2CPP games (`kits/unity/mod/Picture.cs`: the camera
that really draws the world, depth from a command buffer, the render event) and is the reference
for a Mono plugin. The Unity-side lines above are a guide, not yet run in a Mono game.

### Characters (optional)

To let players play as your game's character, add a block `kind = "character"`,
`needs = ["input"]`, `gives = ["pose"]`. Each frame: `guest.TryGetInput(out input)`; while
`input.Possessed != 0`, move your player with `MoveX`/`MoveY` and turn it to `Yaw`/`Pitch` the way
your game's own controls do, then `guest.SetPose(id, feet...)` and `guest.SetView(eyes...,
Fusion.ViewFlags.InWorld)`. Fusion's camera follows the eyes.

### Setup for players

Turn your install instructions into `[router.setup]` ([`block-manifest.md`](https://steonmod.com/docs/block-manifest), "`[router.setup]`: one-click setup"):

```toml
[router.setup]
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"
to = "{game}/BepInEx/plugins/MyGameFusion"
```

A router that carries a plugin DLL is shared only by verified authors ([`trust.md`](https://steonmod.com/docs/trust)); ask to
become one when you publish.

## Check it

```
fusion-cli router check routers/mygame             # the files, no game needed
fusion-cli router conform routers/mygame --start   # starts the game; numbers for every port
fusion-cli check-scenario scenarios/minecraft-in-mygame.toml
```

`router conform` measures what used to need eyes: whether the picture arrives and how many
frames it lags behind the camera (from the picture itself), whether collision and NPCs arrive,
whether it survives sleep and wake, and whether it ignores what it can't do.

Then play the scenario in Fusion. **F7** shows the collision Minecraft walks on, **F8** the NPCs.
