# i3D.net integration

**URL:** https://heroiclabs.com/docs/nakama/guides/concepts/i3d-integration/
**Summary:** Integrate Nakama matchmaking with i3D.net to allocate and manage dedicated servers. This guide covers Arcus SDK setup, Nakama plugin configuration, matchmaking callbacks, and the session lifecycle.
**Keywords:** i3d.net, i3d integration, matchmaking, multiplayer, i3d, fleet management, arcus
**Categories:** nakama, i3d-integration, concepts

---


# Overview

Integrate Nakama matchmaking with i3D.net to spin up dedicated servers on demand. This guide shows how to set up the Arcus SDK on your headless server, configure the Nakama i3D fleet manager, wire matchmaker callbacks so players get connection details automatically, and manage the session lifecycle.

# Prerequisites

{{< note "important" "Nakama version" >}}
The Nakama–i3D plugin has been tested with Nakama **3.36+**.
{{< /note >}}

Before you begin, make sure you have:

- An **[i3D.net](https://www.i3d.net/)** account with registered hosts and an uploaded game server binary.
- A running **Nakama** server (self-hosted or [Heroic Cloud](https://heroiclabs.com/heroic-cloud/)).
- The **Arcus SDK** for your platform (Unity, Unreal, or C++).
- **Go 1.23.5+** for server development.

## Architecture overview

The Nakama–i3D integration uses i3D.net’s One API to allocate servers. When players are matched, the plugin:

- Requests a server allocation from i3D.net.
- Waits for the server to report ready via Arcus.
- Notifies players with connection details.
- Manages the server lifecycle through status updates.

## Install the Nakama–i3D plugin

This guide assumes you already have a Nakama Go runtime project and know how to host a Nakama instance (for example, on Heroic Cloud). The plugin implements Nakama’s Fleet Manager interface for i3D.net and is available as an open‑source module.

### Install the plugin

Add the i3D fleet manager to your Nakama Go module:

```bash
go get github.com/i3d/nakama-fleetmanager
```

### Register the fleet manager and matchmaker callback

Create and register the fleet manager in `InitModule`:

```go
import (
    "github.com/i3d/nakama-fleetmanager"
    "github.com/heroiclabs/nakama/runtime"
)

func InitModule(ctx context.Context, nk runtime.NakamaModule, initializer runtime.Initializer, logger runtime.Logger) error {
    // Load config
    cfg, err := fleetmanager_config.NewConfigFromRuntime(ctx)
    if err != nil {
        cfg, err = fleetmanager_config.NewConfig()
        if err != nil {
            return fmt.Errorf("config error: %w", err)
        }
    }

    // Create and register the fleet manager
    fm, err := fleetmanager.NewI3dFleetManager(ctx, logger, initializer, nk, cfg)
    if err != nil {
        return err
    }
    if err := initializer.RegisterFleetManager(fm); err != nil {
        return err
    }

    // Register the matchmaker handler
    return initializer.RegisterMatchmakerMatched(MatchmakerMatched)
}
```

## Create a headless game server

Your dedicated server must implement the **Arcus** protocol so i3D.net and Nakama can track status and readiness. This section covers what the integration needs. Use your networking library’s docs for gameplay sync (Nakama authoritative matches, NGO, Mirror, etc.).

### Unity

1. Download and import the [Unity Arcus SDK](https://docs.i3d.net/game-hosting/game-integration/sdk-overview/sdk-unity-plugin).
2. Initialize Arcus in your server bootstrap scene or script.

### Unreal Engine

1. Install the [Unreal Arcus plugin](https://docs.i3d.net/game-hosting/game-integration/sdk-overview/sdk-unreal-plugin).
2. Initialize the Arcus subsystem in your server `GameMode`/module.

### C++

1. Follow the [C++ integration guide](https://docs.i3d.net/game-hosting/game-integration/sdk-overview/integration-guide) and link the Arcus library.
2. Initialize the Arcus client during server startup.

#### Minimal initialization (Unity)

```csharp
void Start()
{
    arcusClient.Initialize();

    // Read metadata passed from Nakama (map, mode, etc.)
    var meta = arcusClient.GetMetadata();
    var mapName = meta.TryGetValue("map", out var m) ? m : "default";
    LoadMap(mapName);

    // Signal that the server is online and ready for allocation
    arcusClient.SetStatus(ArcusStatus.Online); // status 4
}
```

## Maintain state synchronization

Arcus keeps state synchronized between your servers and Nakama:

- Servers report readiness before players connect.
- Metadata (map, mode, etc.) from Nakama is passed to the server.
- Lifecycle is coordinated through status updates.

{{< note "warning" "Important" >}}
Your game server **must** implement the Arcus protocol. It handles critical lifecycle events and ensures players connect only to ready servers.
{{< /note >}}

### Arcus status lifecycle

Your server drives state transitions so allocation and player joins are coordinated.

{{< table name="nakama.guides.concepts.i3d-integration.arcus-status-lifecycle" >}}

{{< note "important" "Tip" >}}
When a session ends, set **Online (4)** to reuse the process, or exit for a clean restart and fresh ports.
{{< /note >}}

## Configure i3D services

Configure via **environment variables** (recommended) or a `local.yml` file mounted in your Nakama data directory.

### Environment variables

Set these variables in your Nakama deployment:

{{< table name="nakama.guides.concepts.i3d-integration.environment-variables" >}}

{{< note "important" "Security note" >}}
Prefer OAuth2 M2M in production. Keep tokens out of source control and use your platform’s secret manager.
{{< /note >}}

### Authentication Setup

#### Basic Authentication

For basic authentication, you only need:
- `I3D_APPLICATION_ID`: Your application ID from i3D.net
- `I3D_ACCESS_TOKEN`: Your API access token

#### M2M OAuth2 Authentication

For enhanced security using OAuth2:
- Set `I3D_USE_BEARER_AUTH=true`
- Provide your OAuth2 credentials:
   - `I3D_CLIENT_ID`
   - `I3D_CLIENT_SECRET`
   - `I3D_AUDIENCE`
   - `I3D_AUTHENTICATION_URL`

## Implement matchmaking

### Handle matchmaker events

Allocate a server when players are matched:

```go
func MatchmakerMatched(
    ctx context.Context,
    logger runtime.Logger,
    db *sql.DB,
    nk runtime.NakamaModule,
    entries []runtime.MatchmakerEntry,
) (string, error) {
    // Extract user IDs from matched players
    userIDs := make([]string, len(entries))
    for i, entry := range entries {
        userIDs[i] = entry.Presence.UserId
    }

    // Define callback for when server is ready
    callback := func(
        status runtime.FmCreateStatus,
        inst *runtime.InstanceInfo,
        sessions []*runtime.SessionInfo,
        metadata map[string]any,
        err error,
    ) {
        if err != nil {
            logger.Error("Failed to create game session", err)
            return
        }

        // Notify players with connection details
        for _, session := range sessions {
            notification := map[string]any{
                "IpAddress": inst.IpAddress,
                "Port":      inst.Port,
                "SessionId": session.SessionId,
            }

            content, _ := json.Marshal(notification)
            nk.NotificationSend(ctx, session.UserId, "Game Session Created", content, 9000, "", false)
        }
    }

    // Metadata for the game server
    metadata := map[string]any{
        "map":        "desert_arena",
        "gameMode":   "team_deathmatch",
        "maxPlayers": len(entries),
    }

    // Create the game session
    fm := nk.GetFleetManager()
    return "", fm.Create(ctx, len(entries), userIDs, nil, metadata, callback)
}
```

### Use allocation filters

Filter allocations by fleet, region, build, and more:

```go
// Create a filter builder
fb := fleetmanager.NewFilterBuilder()

// Add filters as needed
fb.Add(fleetmanager.FleetId, "your-fleet-id")
fb.Add(fleetmanager.RegionName, "eu-west") // region name as defined in i3D.net
fb.Add(fleetmanager.ApplicationBuildId, "your-build-id")

// Apply filters to metadata
metadata := fb.AddFiltersToMetaData(map[string]any{
    "map":      "desert_arena",
    "gameMode": "ranked",
})

// Create session with filters
fm.Create(ctx, maxPlayers, userIDs, nil, metadata, callback)
```

#### Available filters

- `deploymentEnvironmentId` / `deploymentEnvironmentName`
- `fleetId` / `fleetName`
- `hostId`
- `applicationBuildId` / `applicationBuildName`
- `dcLocationId` / `dcLocationName`
- `regionId` / `regionName`

## Advanced features

### Health check RPC

Register a basic health check endpoint:

```go
func InitModule(ctx context.Context, ...) error {
    // ... other initialization code ...

    // Register health check RPC
    if err := initializer.RegisterRpc("healthcheck", fleetmanager.RpcHealthCheck); err != nil {
        return err
    }

    return nil
}
```

### List active sessions

Query active game sessions (occupied servers only):

```go
func ListActiveSessions(ctx context.Context, nk runtime.NakamaModule) {
    fm := nk.GetFleetManager()

    query := "+value.playerCount:2" // Sessions with 2+ players
    limit := 10
    cursor := ""

    for {
        instances, nextCursor, err := fm.List(ctx, query, limit, cursor)
        if err != nil {
            logger.Error("Failed to list sessions", err)
            break
        }

        // Process instances
        for _, instance := range instances {
            logger.Info("Active session",
                "id", instance.Id,
                "players", instance.PlayerCount,
                "ip", instance.IpAddress,
                "port", instance.Port)
        }

        if nextCursor == "" {
            break
        }
        cursor = nextCursor
    }
}
```

## Best practices

### Server lifecycle management

- **Exit after game completion**. After players leave, exit the server process. i3D.net restarts it with fresh ports for a clean state.
- **Validate connections**. Use Arcus to ensure players connect only when the server is ready.

### Performance optimization

- **Tune retries**. Set retry attempts and delays to meet your latency targets.
- **Allocate by region**. Use region filters to place servers near players.
- **Monitor allocation times**. Use the health check RPC and logs to track fleet manager performance.

## Limitations

- **List** returns only occupied servers. i3D.net focuses on allocation, not session persistence.
- **Latency-based allocation** isn’t automatic. Implement your own selection logic using region filters.
- **Join** only validates capacity against `maxPlayers`. Player management is handled between clients and game servers.

## FAQ

**My game server isn’t receiving metadata from Nakama**  
Verify your server initializes the Arcus protocol correctly. Check the Arcus SDK docs for your platform.

**Players can’t connect to the allocated server**  
Verify that:  
- The server sets status to **Allocated (5)** before accepting connections.  
- The correct ports are configured in your i3D.net deployment.  
- Network rules allow player connections.

**How do I handle server crashes?**  
Exit with a non‑zero code other than 134 to trigger an automatic restart. If the server fails to start three times, i3D.net removes the instance. Check the portal for crash logs.

**Can I reuse servers for multiple sessions?**  
Yes. Set status to **Online (4)** after a game completes. For a clean state, prefer exiting the process.

**How do I implement region‑based matchmaking?**  
Use region filters when creating sessions to place players in their preferred regions. Combine with Nakama matchmaker properties for best results.
