Skip to content

About

Cross-LAN Minecraft multiplayer runtime component

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Conic Nexus

Conic Nexus is the cross-LAN multiplayer runtime component of the Conic launcher. One player hosts a Minecraft LAN world; remote friends join it as if they were on the same LAN.

The launcher does not link against Conic Nexus. It loads the library at runtime (typically through libloader) and drives the whole multiplayer flow exclusively through the C ABI declared in include/conic_nexus.h. This guide is the integration contract: everything an embedder needs to know is in this document and in include/conic_nexus.h — no source reading required.

  • License: MIT OR Apache-2.0
  • Crate type: cdylib + rlib
  • Edition 2024, rust-version 1.85

1. Project Overview

┌──────────┐   loads at runtime    ┌─────────────────────────────────────┐
│  Conic   │ ────────────────────▶ │         libconic_nexus              │
│ Launcher │                       │  host / guest flows, mesh, events   │
└──────────┘ ◀──────────────────── │  driven only through the C ABI      │
      │  UI / state               └─────────────────────────────────────┘
      │  driven by
      │  poll_event() + get_state()

Conic Nexus is a component, not a framework. It never calls into the embedder; the embedder drives it with commands and observes it by polling. The launcher decides when to start or stop a session and what to show the user; Conic Nexus decides how a room is created, joined, and kept alive.

What the component provides:

  • Hosting — scans the LAN for a real Minecraft server, opens a room, and publishes a virtual LAN server that remote guests can reach.
  • Joining — a guest dials in with a room code, gets a local 127.0.0.1:<port> to point Minecraft at, and keeps its player roster in sync.
  • Mesh transport — an embedded EasyTier node connects both sides over the Internet (or a private relay) so the guest's 127.0.0.1 port really reaches the host's Minecraft server.
  • Observability — an event queue plus a point-in-time state snapshot, so the launcher can render progress and failures with minimal effort.

2. Building

cargo build            # produces target/debug/libconic_nexus.dylib (+ .rlib)
cargo test --lib       # room-code parse/mint tests

Why the first build downloads EasyTier

Conic Nexus embeds a full EasyTier runtime (easytier-core and easytier-cli, v2.6.4) so the delivered library is self-contained: the launcher does not need to install or manage anything. During the first build, build.rs downloads the prebuilt EasyTier release for the target platform from the EasyTier GitHub releases, repacks it with LZMA2 + BCJ into .easytier/v2.6.4/<platform>/easytier.7z, and embeds that archive into the library. The build therefore needs network access once per EasyTier version per checkout; afterwards the cached archive is reused.

EasyTier is LGPL-3.0 licensed, and its binaries are redistributed inside the library. The license text and copyright notice are preserved in THIRD_PARTY_LICENSE.

How EasyTier is released at runtime

The archive is not kept in memory. On the first session (and on every conic_nexus_create), the binaries are extracted to <data_dir>/embedded-easytier and spawned as normal child processes on demand. Extraction happens lazily and only if the target directory is missing or stale.

What data_dir is for

data_dir (a field of ConicNexusConfig) is the runtime working directory. It holds:

  • the extracted EasyTier binaries (<data_dir>/embedded-easytier/);
  • the machine identity used for player profiles.

It is also where per-process state is separated: two instances with different data_dir values get independent EasyTier installations and machine identities. The default is <system-temp>/conic-nexus. The embedder normally picks a persistent, writable location (e.g. the launcher's data directory) and sets it once via conic_nexus_configure.


3. Getting Started

The whole API surface is small. A complete session is: create → configure → start → poll → stop. The sections below follow that order.

3.1 Creating an instance

conic_nexus_handle h = conic_nexus_create();   // NULL on failure

conic_nexus_create() starts a session engine: a worker runtime, the event queue, and the scaffolding server (see §13). Creating an instance does not touch the network; it is cheap and can happen once at launcher startup.

Configuration is applied with conic_nexus_configure():

ConicNexusConfig cfg = {0};
cfg.data_dir = cn_string(my_dir);              // runtime directory (see §2)
cfg.motd     = cn_string("§a§lMy Server");     // host banner; NULL = default

const char *peers[] = {"tcp://relay.example.net:11010"};
ConicNexusString public_nodes[1] = {{peers[0], strlen(peers[0])}};
cfg.public_nodes       = public_nodes;
cfg.public_nodes_count = 1;

conic_nexus_configure(h, &cfg);

The config fields:

field purpose default
public_nodes EasyTier rendezvous servers the mesh uses to find peers. Each entry is a ConicNexusString. Overrides the built-in public list. built-in public list (see §14)
public_nodes_count length of public_nodes 0
data_dir runtime directory (EasyTier extraction, machine identity) <temp>/conic-nexus
motd banner shown on the guest's virtual LAN server; the host also uses it to ignore LAN servers it should not host "§a§lConic Nexus"

Lifecycle rules:

  • create() → configure() → start a session → destroy().
  • configure() may be called again between sessions (while Waiting), but a session is always started after the final configure().
  • configure() while a session is running returns CONIC_NEXUS_ERR_BAD_STATE.
  • destroy() tears everything down. After destroy() the handle is permanently invalid (any further call returns CONIC_NEXUS_ERR_INVALID_HANDLE).

3.2 Hosting a room

create()
   │
   ▼
configure()
   │
   ▼
create_room(player_name, NULL)          ── room code is minted for you
   │
   ▼
┌───────────────────────  poll loop  ───────────────────────┐
│  poll_event()  │  get_state()  │  (re)draw UI            │
└───────────────────────────────────────────────────────────┘
   │
   ▼  StateChanged → HOST_SCANNING → HOST_STARTING → HOST_OK
   ▼  HostReady event fires: the room is open
   ▼  ... guests join / leave (PlayerJoined / PlayerLeft) ...
   │
   ▼
reset_to_waiting()                       ── or destroy()
// Mint the room code for you. The code is returned to you in the HostReady
// event payload AND in the state snapshot's room_code field.
int rc = conic_nexus_create_room(h, "MyServerName", NULL);
if (rc != CONIC_NEXUS_OK) { /* see §10 */ }

for (;;) {
    conic_nexus_event ev;
    if (conic_nexus_poll_event(h, &ev) == CONIC_NEXUS_OK) {
        if (ev.type == CONIC_NEXUS_EVENT_HOST_READY) {
            // ev.payload == {"room":"U/XXXX-XXXX-XXXX-XXXX","port":25565}
            show_room_code_to_user(payload_room(&ev.payload));
        }
        if (ev.type == CONIC_NEXUS_EVENT_FAULT) { /* command failed, §10 */ }
        conic_nexus_free_event(&ev);
    }
    update_ui(get_state(h));             // §7
    sleep_ms(100);
}

What happens on the host side, in order:

  1. create_room returns CONIC_NEXUS_OK immediately; the session enters HOST_SCANNING.
  2. The host scans the LAN for a real Minecraft server on port 25565 (deadline 60 s). If none is found the session lands in EXCEPTION (error.code = 9, §8).
  3. On success the session moves to HOST_STARTING: the EasyTier node and the scaffolding API come up.
  4. HOST_OK — the room is live. The HostReady event carries the room code and the mirrored Minecraft port. From this point the host's Minecraft server is reachable by any guest who enters the code. get_state() reports state = CONIC_NEXUS_STATE_HOST_OK, room_code, and the current roster.
  5. Guests join and leave; you receive PlayerJoined / PlayerLeft events and see the roster in get_state().detail.profiles.
  6. The room stays open until you call reset_to_waiting() (or the host process exits).

The host player is always present in the roster as the first entry (kind = "HOST").

3.3 Joining a room

create()
   │
   ▼
configure()
   │
   ▼
join_room("U/XXXX-XXXX-XXXX-XXXX", player_name)
   │
   ▼
┌───────────────────────  poll loop  ───────────────────────┐
│  poll_event()  │  get_state()  │  (re)draw UI            │
└───────────────────────────────────────────────────────────┘
   │
   ▼  StateChanged → GUEST_CONNECTING → GUEST_STARTING → GUEST_OK
   ▼  GuestReady event fires: the tunnel is up
   │
   ▼  connect Minecraft to the URL from the event / snapshot
   │
   ▼
reset_to_waiting()                       ── or destroy()
int rc = conic_nexus_join_room(h, "U/XXXX-XXXX-XXXX-XXXX", "MyName");
if (rc != CONIC_NEXUS_OK) { /* see §10 */ }

for (;;) {
    conic_nexus_event ev;
    if (conic_nexus_poll_event(h, &ev) == CONIC_NEXUS_OK) {
        if (ev.type == CONIC_NEXUS_EVENT_GUEST_READY) {
            // ev.payload == {"url":"127.0.0.1:25565"}  (or 127.0.0.1:<port>)
            point_minecraft_at(payload_url(&ev.payload));
        }
        conic_nexus_free_event(&ev);
    }
    update_ui(get_state(h));
    sleep_ms(100);
}

What happens on the guest side, in order:

  1. join_room validates the code (CONIC_NEXUS_ERR_INVALID_ROOM_CODE if it is malformed) and returns immediately; the session enters GUEST_CONNECTING.

  2. The guest's EasyTier node starts; the session moves to GUEST_STARTING.

  3. The guest locates the host inside the mesh by its advertised hostname, verifies it with the scaffolding fingerprint, and installs local port-forwards to the host's scaffolding and Minecraft services.

  4. GUEST_OK — the tunnel is up. The GuestReady event carries the local listening address:

    • "127.0.0.1" when the port is 25565, otherwise "127.0.0.1:<port>".

    The guest connects Minecraft to exactly this address. The local port mirrors the host's Minecraft port; if that port is already taken locally the engine falls back to a free port.

  5. The guest keeps the roster in sync with the host's authoritative list and emits PlayerJoined / PlayerLeft as players come and go. It also health-checks the tunnel; if the host disappears the session moves to EXCEPTION.

Validate the code before the join. conic_nexus_room_code_is_valid() is a pure, never-failing query — run it on every input box to reject obviously bad codes before starting a session.


4. State Machine

States are reported in two places: the state field of ConicNexusState (get_state()) and the payload of StateChanged events. They use the same integer values.

                        create()
                           │
                           ▼
                    ┌───────────┐
          ┌────────▶│  WAITING  │◀─────────────────────────┐
          │         └───────────┘                          │
          │  create_room          join_room                │
          │            │              │                    │
          │            ▼              ▼                    │
          │     ┌─────────────┐ ┌──────────────┐           │
          │     │ HOST_       │ │ GUEST_       │           │
          │     │ SCANNING    │ │ CONNECTING   │           │
          │     └─────────────┘ └──────────────┘           │
          │            │              │                    │
          │            ▼              ▼                    │
          │     ┌─────────────┐ ┌──────────────┐           │
          │     │ HOST_       │ │ GUEST_       │           │
          │     │ STARTING    │ │ STARTING     │           │
          │     └─────────────┘ └──────────────┘           │
          │            │              │                    │
          │            ▼              ▼                    │
          │     ┌─────────────┐ ┌──────────────┐           │
          │     │  HOST_OK    │ │  GUEST_OK    │           │
          │     └─────────────┘ └──────────────┘           │
          │            ╲              ╱                    │
          │             ╲  (any state, any failure)        │
          │              ╲          ╱                      │
          │               ▼        ▼                       │
          │             ┌───────────┐                      │
          │             │ EXCEPTION │──────────────────────┘
          │             └───────────┘
          │                    │
          │    reset_to_waiting()
          └────────────────────┘

Per-state contract

state value entered left when embedder should allowed calls
WAITING 0 create / reset create_room / join_room idle; safe to configure or start all
HOST_SCANNING 1 create_room MC server found → 2; timeout → 7 show "scanning for LAN server" get_state, poll_event, reset_to_waiting
HOST_STARTING 2 server found EasyTier + scaffolding up → 3; spawn fail → 7 show "starting" get_state, poll_event, reset_to_waiting
HOST_OK 3 startup done reset_to_waiting; host MC lost / EasyTier exit → 7 show code, render roster all
GUEST_CONNECTING 4 join_room EasyTier up → 5; spawn fail → 7 show "connecting" get_state, poll_event, reset_to_waiting
GUEST_STARTING 5 EasyTier up locate + fingerprint + forwards done → 6; any failure → 7 show "joining / verifying host" get_state, poll_event, reset_to_waiting
GUEST_OK 6 tunnel up reset_to_waiting; host gone / EasyTier exit → 7 give user the url, render roster all
EXCEPTION 7 any failure only reset_to_waiting read detail.error, prompt user, then reset get_state, poll_event, reset_to_waiting

Two properties are worth highlighting:

  • Progress is only ever forward on the happy path: there is no "back" from STARTING to SCANNING, or from GUEST_STARTING back to GUEST_CONNECTING.
  • EXCEPTION does not recover by itself. Nothing retries for you. The embedder reads detail.error, tells the user what went wrong, and calls reset_to_waiting() to start over. This keeps failure handling simple and predictable (see §10).

5. Event System

Why events and state?

  • Events are a stream: things that happen over time — a guest joined, a guest left, the room became ready. They drive UI updates and sounds.
  • State is a snapshot: everything true right now. It drives rendering and recovery.

A single StateChanged event tells you the session moved somewhere, but not what the full picture is; get_state() gives you the full picture, but not the history. Neither alone is sufficient — events without a snapshot force you to track history yourself, and a snapshot without events hides roster churn. Use them together (§11).

The queue

Events are delivered through a bounded queue (capacity 256) drained with conic_nexus_poll_event. When the queue is full, the newest event is dropped (older ones are kept). This is safe because the latest authoritative picture is always available from get_state() — an embedder that polls frequently enough never loses information it cares about.

Event reference

sequence is a monotonically increasing counter the engine assigns as events are produced; it is an ordering hint, not a gap-free ledger.

type value payload (JSON) fired when embedder usually
STATE_CHANGED 0 {"state":<int>,"version":<uint64>} the session moved to another state sync UI to the new state; optionally read get_state()
PLAYER_JOINED 1 {"profile":{...}} — profile shape in §8 a player appeared in the roster show "X joined"
PLAYER_LEFT 2 {"machine_id":"..."} a player vanished (guest left, or host-side staleness pruning after 30 s) show "X left"
HOST_READY 3 {"room":"U/XXXX-XXXX-XXXX-XXXX","port":25565} host reached HOST_OK reveal the room code to the user
GUEST_READY 4 {"url":"127.0.0.1"} or {"url":"127.0.0.1:<port>"} guest reached GUEST_OK point Minecraft at url
FAULT 5 {"code":<int>,"message":"..."} — see §10 a command failed (invalid code, session already active) handle as a recoverable command error; the session may still be usable

StateChanged does not replace the others, and vice versa. A roster change sends PlayerJoined/PlayerLeft (and bumps the snapshot version) but does not change state; reaching HOST_OK sends both StateChanged and HostReady.

Free every event you poll: conic_nexus_free_event(&ev). The payload string is owned by the library.


6. Polling Model

Conic Nexus uses a command + polling model. There are no callbacks and the component never calls into the embedder.

  • Commands — create_room, join_room, reset_to_waiting — are asynchronous. They validate input synchronously (so you get an immediate error for a bad code or a busy session) and return CONIC_NEXUS_OK once the command is accepted. The actual work happens on the engine's worker thread.
  • Observation — poll_event() drains the event queue; get_state() reads the current snapshot. The embedder calls both repeatedly.

This asymmetry is deliberate: the launcher owns its UI thread and event loop, and never blocks on network work. The library never surprises the embedder by running code on its thread.

Recommended poll cadence: 100–200 ms. Poll faster only if you need near-instant PlayerJoined feedback; poll slower if you want to minimize wakeups. poll_event never blocks — it returns CONIC_NEXUS_ERR_NO_EVENT when the queue is empty.

embedder                        conic-nexus
   │  create_room(...)                │
   │ ─────────────────────────────────▶│  validates, returns OK
   │  OK                               │
   │  ...                              │  worker does the work
   │  poll_event()                     │
   │ ─────────────────────────────────▶│
   │  StateChanged(HOST_SCANNING) ◀────│
   │  ...                              │
   │  poll_event()                     │
   │ ─────────────────────────────────▶│
   │  HostReady(...) ◀─────────────────│
   │  get_state()                      │
   │ ─────────────────────────────────▶│
   │  {state:HOST_OK, room_code,...} ◀─│

7. get_state

conic_nexus_get_state(h, &out) fills a ConicNexusState snapshot. Call conic_nexus_free_state(&out) when done.

typedef struct ConicNexusState {
    uint64_t version;
    int32_t  state;        // enum ConicNexusState (§4)
    ConicNexusString room_code;
    ConicNexusString detail;   // JSON (§8)
} ConicNexusState;
field meaning changes
version monotonically increasing revision of the session every committed mutation (state changes and roster changes)
state current session state (§4) on every StateChanged
room_code the active room code set when a room is created/joined, cleared on reset_to_waiting
detail JSON detail for the current state (§8) with the state, the roster, and the overlay status

Why call get_state() even though events exist?

  1. It cannot miss anything. Dropped or missed events are irrelevant: the snapshot always reflects the latest truth. This is your recovery source of truth.
  2. It is the only source for some data. The roster (detail.profiles), the overlay status (detail.overlay), and the guest connection URL (detail.url) are authoritative here.
  3. It makes rendering trivial. Draw your screen from one snapshot instead of folding dozens of events.

Recommended pairing (§11): use poll_event() for the delta (toasts, sounds, one-off actions like revealing the room code) and get_state() for the full frame (roster list, connection status, error panel).


8. detail JSON

The detail field of ConicNexusState is a JSON object whose shape depends on the current state. All fields below are stable ABI: consumers may rely on their names and types.

HOST_SCANNING, HOST_STARTING, GUEST_CONNECTING, WAITING

{}

HOST_OK

{
  "port": 25565,
  "profiles": [
    {"machine_id": "0123456789abcdef...", "name": "MyServerName",
     "vendor": "Conic Nexus 0.1.0, EasyTier v2.6.4", "kind": "HOST"},
    {"machine_id": "fedcba...", "name": "Alice", "vendor": "...", "kind": "GUEST"}
  ],
  "overlay": {"pid": 1234, "alive": true, "rpc_port": 23556}
}

GUEST_STARTING, GUEST_OK

{
  "url": "127.0.0.1",
  "profiles": [
    {"machine_id": "...", "name": "MyName", "vendor": "...", "kind": "LOCAL"},
    {"machine_id": "...", "name": "MyServerName", "vendor": "...", "kind": "HOST"}
  ],
  "overlay": {"pid": 1234, "alive": true, "rpc_port": 23556}
}
  • url — the local address to point Minecraft at (127.0.0.1, or 127.0.0.1:<port> when the port is not 25565).
  • profiles — the authoritative roster. kind is one of HOST, LOCAL (your own profile), GUEST. vendor is the self-reported client identification. The list is sorted and deduplicated by machine_id.

EXCEPTION

{
  "error": {
    "code": 4,
    "message": "Minecraft server connection lost"
  }
}

code uses the session-error codes below; message is the same information in human-readable form and is intended for direct display.

Session error codes

code macro meaning
0 CONIC_NEXUS_SESSION_ERR_HOST_UNREACHABLE cannot reach the host (locate/fingerprint timeout)
1 CONIC_NEXUS_SESSION_ERR_FINGERPRINT_MISMATCH a peer answered TCP but failed the scaffolding fingerprint — definitively not a genuine host
2 CONIC_NEXUS_SESSION_ERR_SCAFFOLDING_REJECTED the host's scaffolding server rejected a request
3 CONIC_NEXUS_SESSION_ERR_SCAFFOLDING_INVALID a scaffolding response was malformed
4 CONIC_NEXUS_SESSION_ERR_SERVER_LOST the host's Minecraft server connection was lost
5 CONIC_NEXUS_SESSION_ERR_HOST_OVERLAY_EXIT the host's EasyTier process exited
6 CONIC_NEXUS_SESSION_ERR_GUEST_OVERLAY_EXIT the guest's EasyTier process exited
7 CONIC_NEXUS_SESSION_ERR_OVERLAY_SPAWN_FAILED failed to spawn an EasyTier process
8 CONIC_NEXUS_SESSION_ERR_PORT_FORWARD_FAILED port-forward rules could not be installed
9 CONIC_NEXUS_SESSION_ERR_HOST_SCAN_TIMEOUT the host found no LAN Minecraft server in time

9. reset_to_waiting

conic_nexus_reset_to_waiting(h) aborts the active session (host or guest) and returns to WAITING. It is much more than "close the room":

  • stop and reap the EasyTier node;
  • shut down the virtual LAN server and release the local port;
  • clear the room code, the roster, and any error;
  • emit a final StateChanged(WAITING).

After the reset completes the same handle can be reused for a new create_room() or join_room() — no need to destroy() and recreate.

Use it for all of these:

scenario why reset, not destroy
host closes the room reuse the handle for the next session
guest leaves the room same
user cancels a connection in progress aborts SCANNING/CONNECTING/STARTING
session in EXCEPTION the only way out (see §4)

reset_to_waiting is asynchronous like the other commands: it returns CONIC_NEXUS_OK immediately and the session moves to WAITING shortly after (visible via a StateChanged event / get_state()). If the session is already WAITING, the call is a no-op.

destroy() also stops any running session, but it permanently invalidates the handle. Prefer reset_to_waiting() when you expect to host/join again.


10. Error Handling

There are two independent error surfaces. Do not confuse them.

ABI errors — function return codes

Returned synchronously by every command. They mean the call itself was rejected; the session is untouched (still in WAITING unless otherwise noted).

code returned when embedder should
CONIC_NEXUS_OK = 0 success —
CONIC_NEXUS_ERR_INVALID_HANDLE = -1 null/stale/destroyed handle drop the handle, recreate the instance
CONIC_NEXUS_ERR_INVALID_ARGUMENT = -2 null out-pointer, non-UTF-8 string fix the caller
CONIC_NEXUS_ERR_BAD_STATE = -3 configure while a session is running call it while WAITING
CONIC_NEXUS_ERR_INVALID_ROOM_CODE = -4 malformed room code in create_room/join_room validate earlier with room_code_is_valid
CONIC_NEXUS_ERR_ALREADY_ACTIVE = -5 create_room/join_room while a session is running reset_to_waiting first
CONIC_NEXUS_ERR_INTERNAL = -6 unexpected internal failure treat as a bug; retry once, then report
CONIC_NEXUS_ERR_OUT_OF_MEMORY = -7 allocation failure back off
CONIC_NEXUS_ERR_NO_EVENT = -8 poll_event queue empty (not an error) loop again after a short sleep
CONIC_NEXUS_ERR_SHUTTING_DOWN = -9 reserved; the engine is shutting down stop calling

Session errors — the EXCEPTION state

A runtime failure inside an active session. It is not returned by any command; it appears as the EXCEPTION state with detail.error (code/message, see §8). Example: the host's Minecraft server dies, or the guest's EasyTier node exits.

Recommended flow on EXCEPTION:

StateChanged/EXCEPTION seen
   │
   ▼
read detail.error.code + message     (get_state())
   │
   ▼
present the message to the user
   │
   ▼
reset_to_waiting()
   │
   ▼
optionally start a new session

Command failures — the FAULT event

A third, smaller surface: the FAULT event (type 5) fires when an accepted command fails asynchronously, e.g. an invalid room code slipped through or a session was already active. Its payload is {"code","message"} with these codes: 1 invalid room code, 2 a session is already active, 3 internal, 4 not implemented. The session may still be usable — treat it as a prompt to tell the user, not as a fatal state.


11. Typical Main Loop

The recommended embedder loop combines the event stream with the state snapshot:

for (;;) {
    /* 1. drain events — the delta */
    conic_nexus_event ev;
    while (conic_nexus_poll_event(h, &ev) == CONIC_NEXUS_OK) {
        handle_event(&ev);              // toasts, sounds, one-off actions
        conic_nexus_free_event(&ev);
    }

    /* 2. read the snapshot — the full frame */
    conic_nexus_state st;
    if (conic_nexus_get_state(h, &st) == CONIC_NEXUS_OK) {
        render(&st);                    // roster, status, room code, error
        conic_nexus_free_state(&st);
    }

    /* 3. let the UI breathe */
    sleep_ms(100);                      // poll cadence: 100–200 ms
}

Why both?

  • Events tell you what happened — "Alice joined", "the room is ready". You act on them once.
  • State tells you where you are — "guest connecting", "error code 4". You render from it every frame, and it is always correct even if you missed events.

get_state() on a frame where nothing changed is cheap; do not try to optimize it away. This loop is also how you make the UI respond instantly to reset_to_waiting and to cancellations.


12. Room Codes

A room code looks like U/XXXX-XXXX-XXXX-XXXX and is the only secret a guest needs to join.

U/2K4Q-0H7N-9ZP2-6RDE
└┘ └────────────┘└─────┘
 │  first 8 digits    last 8 digits
 │  ─────────────────  ─────────────
 │      network name     network secret
 │   scaffolding-mc-2K4Q-0H7N  9ZP2-6RDE
  • The 16 digits after U/ encode a 128-bit value in base-34, least significant digit first. To keep codes human-friendly, I is read as 1, O as 0, and L is omitted from the alphabet.

  • A code is valid only if its value is divisible by 7. This is a checksum: most typos produce a value that is not, so room_code_is_valid() catches them early.

  • The engine derives all EasyTier mesh credentials from the code:

    • network name: scaffolding-mc-<first 8 digits>
    • network secret: <last 8 digits>

    There is nothing else to exchange — the code alone pins the mesh, which is why both sides can join the same virtual network with no other setup.

  • Parsing tolerates case and leading garbage (it scans for the U/ marker), so pasting codes from chat works even when the surrounding text leaks in.

Use conic_nexus_room_code_is_valid() to validate user input before join_room().


13. Scaffolding Protocol

This section documents the on-the-wire protocol Conic Nexus speaks inside the mesh. It is the interoperability contract between a host and a guest and is fixed for cross-implementation compatibility.

The host runs a small TCP scaffolding server inside the mesh (on port 13448 when available, otherwise an OS-assigned port) and advertises it in its mesh hostname (scaffolding-mc-server-<port>), which is how guests locate it. Before trusting anything, the guest verifies the server by fingerprinting c:ping with a fixed 16-byte challenge.

Framing — requests and responses share one shape:

  • request: [kind-len:u8][kind "ns:path"][body-len:u32 BE][body]
  • response: [status:u8][body-len:u32 BE][body]

Handlers (namespace c):

handler body returns
c:ping echo payload same payload (guest verifies a fixed 16-byte fingerprint)
c:protocols — five NUL-separated protocol names
c:server_port — host's Minecraft port, 2-byte big-endian
c:player_ping JSON {machine_id,name,vendor} updates the host roster
c:player_profiles_list — JSON array of player profiles (kind: HOST/LOCAL/GUEST)

14. EasyTier Integration

Conic Nexus uses EasyTier as its mesh transport. A room is simply an EasyTier network whose name and secret are derived from the room code (§12); host and guest each run an embedded easytier-core node and join it. This section describes how the integration behaves — it is not an EasyTier tutorial.

Node arguments

Both sides start their node with:

--network-name <scaffolding-mc-...>   (from the room code)
--network-secret <...>                (from the room code)
-p <rendezvous server>...             (see below)
--no-tun --compression zstd --multi-thread --latency-first --enable-kcp-proxy
-l udp://0.0.0.0:0 -l tcp://0.0.0.0:0
--p2p-only

plus role-specific flags:

  • host: --hostname scaffolding-mc-server-<scaffolding_port>, --ipv4 10.144.144.1, --tcp-whitelist <scaffolding_port>, --tcp-whitelist <mc_port>, --udp-whitelist <mc_port>
  • guest: -d (DHCP address), plus runtime port-forwards to the host's scaffolding and Minecraft services.

Two flags deserve explanation because they directly affect whether a mesh can form:

  • Listeners bind ephemeral ports (-l ...:0), so a host and guest that happen to run on the same machine never collide on the well-known 11010 listener port.
  • --p2p-only is used: it keeps traffic on the direct peer path once reachable. (Do not use --no-listener — a dial-out-only node cannot accept the inbound connection a P2P tunnel requires.)

Rendezvous servers

The node is started with the public_nodes from ConicNexusConfig, falling back to the built-in public EasyTier rendezvous list:

tcp://public.easytier.top:11010
tcp://public2.easytier.cn:54321
https://etnode.zkitefly.eu.org/node1
https://etnode.zkitefly.eu.org/node2

Setting public_nodes in the config replaces this list, which is how an embedder routes traffic through its own relay nodes instead of the public servers.

Lifecycle

The node is spawned when a session starts, reaped on reset_to_waiting() or destroy() (the process is killed). Node failures surface as session errors 5/6/7 (§8). Diagnostics are available through conic_nexus_recent_logs() and peer/NAT status through conic_nexus_query_peers() (§15).


15. C ABI Reference

All symbols use the conic_nexus_ prefix; types use the ConicNexus prefix. The authoritative declaration is include/conic_nexus.h. Result codes, enum values, and struct layouts there are the ABI contract — see §10 for the return codes.

Functions are grouped by lifecycle role.

Lifecycle

function description
conic_nexus_create() create a session engine; returns a handle or NULL
conic_nexus_destroy(handle) tear everything down; the handle is permanently invalid afterwards

Configuration

function description
conic_nexus_configure(handle, cfg) apply ConicNexusConfig; only while WAITING (CONIC_NEXUS_ERR_BAD_STATE otherwise)

Host

function description
conic_nexus_create_room(handle, player_name, room_code) open a room; pass room_code = NULL to mint one, player_name = NULL for the default

Guest

function description
conic_nexus_join_room(handle, room_code, player_name) join a room by code; room_code is required, player_name optional

Session control

function description
conic_nexus_reset_to_waiting(handle) abort the current host/guest session and return to WAITING (§9)

Polling / observation

function description
conic_nexus_poll_event(handle, &event) pop the next event; CONIC_NEXUS_ERR_NO_EVENT when empty (§5)
conic_nexus_get_state(handle, &state) current snapshot: version, state, room code, detail JSON (§7)

Utility

function description
conic_nexus_room_code_is_valid(code) pure query; NULL/bad code → false; never fails
conic_nexus_version() static, NUL-terminated version string
conic_nexus_recent_logs(limit, &out) the limit most recent log lines as a JSON array of strings (most recent last); not per-handle — it is a process-wide ring buffer. Free with conic_nexus_free_string

Mesh introspection

function description
conic_nexus_query_peers(handle, &out, &count) current mesh peers + NAT types (EasyTier codes 0–9; see the header). CONIC_NEXUS_ERR_BAD_STATE when no node is running, CONIC_NEXUS_ERR_INTERNAL on query failure; empty list → out = NULL, count = 0. Free with conic_nexus_free_peers
conic_nexus_free_peers(peers, count) free a peer array from query_peers

Memory management

Strings returned by the library are owned by the library and reference-counted by the caller. Every aggregate you poll or query must be freed with its matching free function:

returned by free with
poll_event conic_nexus_free_event(&event)
get_state conic_nexus_free_state(&state)
query_peers conic_nexus_free_peers(peers, count)
recent_logs (payload) conic_nexus_free_string(&out)

ConicNexusString fields are not NUL-terminated; always honor len.


16. Examples

Two small, self-contained binaries demonstrate the C ABI against a real session. Build the library first (cargo build at the crate root), then run each from its own directory. Both load libconic_nexus.dylib at runtime through libloader — point them at a local build with CONIC_NEXUS_LIB.

The host example needs a real LAN Minecraft server on port 25565 to mirror; without one it exits after the scan deadline (60 s).

# terminal 1 — the host: opens a room and prints the code
cd examples/host
cargo run [player_name]

# terminal 2 — the guest: joins with the code the host printed
cd examples/guest
cargo run -- U/XXXX-XXXX-XXXX-XXXX [player_name]
  • examples/host/ — cargo run [player_name]. Mints a room code, creates a room, prints the code, and streams session events (StateChanged, HostReady, PlayerJoined, PlayerLeft, …) until Ctrl-C. Share the printed code with a guest.
  • examples/guest/ — cargo run -- <room_code> [player_name]. Takes the room code as a command-line argument, validates it, joins the room, and once ready prints the local address to connect Minecraft to. Streams events until Ctrl-C.

Environment overrides used by both examples:

variable purpose
CONIC_NEXUS_LIB path to libconic_nexus.dylib (default ../../target/debug/libconic_nexus.dylib)
CONIC_NEXUS_MESH_PEERS whitespace/comma-separated EasyTier peer URLs handed to the library as public_nodes

17. Project Layout

build.rs        downloads/repacks/embeds EasyTier binaries (7z)
include/        conic_nexus.h (C ABI declaration — the integration contract)
LICENSE-MIT / LICENSE-APACHE   project licenses
THIRD_PARTY_LICENSE            EasyTier LGPL-3.0 text and copyright notice
src/ffi.rs      C ABI, handle table, result codes
src/engine.rs   session state machine, event queue
src/room.rs     room-code minting/parsing
src/wire/       scaffolding client/server + framing
src/lan/        LAN server scanner + fake server
src/overlay/    easytier-core spawn, RPC peer queries, port-forwards
src/flows/      host and guest orchestration
src/ports.rs    dynamic port allocation

Thanks

Conic Nexus is built on top of EasyTier as its underlying virtual network. We thank the EasyTier authors and contributors for providing a stable peer-to-peer networking layer that makes cross-network Minecraft LAN multiplayer possible.

License

Conic Nexus is dual-licensed under the MIT License and the Apache License Version 2.0, at your option:

The scaffolding wire protocol and room-code scheme are required for interoperability with the Terracotta ecosystem and are documented in this README (§12, §13) as the protocol contract.

Third-party notices

Conic Nexus embeds and redistributes the prebuilt EasyTier binaries (easytier-core, easytier-cli, v2.6.4), which are licensed under the GNU Lesser General Public License version 3.0. EasyTier is developed by the EasyTier project (github.com/EasyTier/EasyTier). The full LGPL-3.0 text and the EasyTier copyright notice are preserved in THIRD_PARTY_LICENSE.

About

Cross-LAN Minecraft multiplayer runtime component

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages