Download Game! Currently 69 players and visitors. Last logged in:DurtleLmtWandererRanma

Blitzer's Blog >> 72507

Back to blogs index
Posted: 15 Sep 2026 23:39 [ permalink ]
This is the final piece of the microservice architecture. Because this bot
runs as a completely independent Node.js process, you can launch ten of them,
kill them, or restart them without ever stuttering the main FSO Engine.
Here is the skeleton for an autonomous NPC bot. It connects to the
SignalingBridge as a BOT client, listens globally for events addressed to its
ID, queries your local Gemma cluster, and fires back the FSO_RPC_V1 JSON
payload.
The Autonomous Bot (npc-bot.js)
You will need the ws package installed (npm install ws).
import WebSocket from 'ws';

/ Configuration
const BRIDGE_URL = "ws://localhost:8080";
const GEMMA_URL = "http://100.111.184.107:8080/v1/chat/completions";
const BOT_ID = "npc_wanderer";

const ws = new WebSocket(BRIDGE_URL);

/ 1. Authenticate with the Bridge
ws.on('open', () => {
    console.log(`[SYS] ${BOT_ID} connected to Live Control Plane.`);
    ws.send(JSON.stringify({
        intent: 'AUTH',
        id: BOT_ID,
        type: 'BOT',
        initial_room: 'GLOBAL' // Listens to all routed events for this ID
    }));
});

/ 2. Listen for Engine Broadcasts
ws.on('message', async (rawData) => {
    try {
        const payload = JSON.parse(rawData);

        // Did a player just say something to us?
        if (payload.event_type === 'NPC_HEAR' && payload.target_npc ===
BOT_ID) {
            const { room_id, player_id } = payload.context;
            console.log(`[HEAR] ${player_id} in ${room_id} says:
"${payload.message}"`);
            
            // Trigger the asynchronous LLM thinking process
            await thinkAndRespond(room_id, payload.message);
        }
    } catch (err) {
        console.error("[FAULT] Failed to parse bridge event:", err);
    }
});

/ 3. The LLM Brain
async function thinkAndRespond(roomId, playerMessage) {
    const systemPrompt = `
You are a cryptic wanderer in a procedural text adventure.
Keep your responses to exactly one sentence. Be mysterious.
    `.trim();

    const requestBody = {
        model: "gemma-2-9b-it",
        messages: [
            { role: "system", content: systemPrompt },
            { role: "user", content: playerMessage }
        ],
        temperature: 0.7 // A little higher for creative conversation
    };

    try {
        const response = await fetch(GEMMA_URL, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(requestBody)
        });

        if (!response.ok) throw new Error("Gemma cluster unreachable");
        
        const data = await response.json();
        const replyText = data.choices[0].message.content.trim();

        console.log(`[SPEAK] Generated reply: ${replyText}`);

        // 4. Fire the RPC back to the Engine
        const rpcPayload = {
            protocol: "FSO_RPC_V1",
            bot_id: BOT_ID,
            intent: "NPC_SPEAK",
            broadcast: {
                target_room: roomId,
                message: `[Wanderer]: "${replyText}"`
            },
            mutation: null // We could inject state changes here if we wanted!
        };

        ws.send(JSON.stringify(rpcPayload));

    } catch (err) {
        console.error("[FAULT] Bot brain error:", err.message);
    }
}

ws.on('close', () => console.log(`[SYS] ${BOT_ID} disconnected.`));

How to Run the Ecosystem
Because you built this in a decentralized way, your startup sequence is now
incredibly resilient:
 * Start the Gemma Cluster: (Already running on 100.111.184.107:8080).
 * Start the FSO VM: node cli.js world.md (which spins up the port 8080
SignalingBridge).
 * Release the Bots: In a separate terminal, run node npc-bot.js.
The bot will silently attach to the Control Plane. When you walk into the Cove
and type say to npc_wanderer "What is this place?", the CLI routes it to the
bridge, the bridge routes it to the bot, the bot queries Gemma, and two
seconds later, the Wanderer's reply appears directly in your terminal.