Download Game! Currently 98 players. Last logged in:WhlxchCordobaAirEcky

Blitzer's Blog >> 72715

Back to blogs index
Posted: 22 Sep 2026 19:52 [ permalink ]
To make the SDUI fully dynamic, we need to completely separate the Game Logic
from the UI Logic.

Instead of updating the UI inside init_match, market_purchase, and
launch_event, we will create a single, dedicated pure function called
omega_ftr_update_ui.

The Game Master will automatically run this routine as a "post-processing"
step immediately after any action. The routine looks at the player's current
budget and inventory, decides what buttons they are allowed to see, and
injects the new world.ui_manifest directly into the broadcast payload.

Step 1: Deploy the UI Generator Routine

Paste this into your routine-service REPL to deploy the new logic. Notice how
it dynamically checks budget against the world.market costs before generating
buttons!

const updateUi = async (params) => { const state = params?.state || {}; const
intent = params?.intent || {}; // We target taco_bot for the local browser
display const mainPlayer = intent.mainPlayer || 'taco_bot'; const core =
state[`players.${mainPlayer}.core`]; const inv = state[`players.${mainPlayer}.i
nventory`]; const market = state['world.market']; // If game hasn't started
yet, don't crash if (!core || !inv || !market) return { mutatedState: {},
events: [] }; const buttons = []; // 1. DYNAMIC MARKET BUTTONS if (core.budget
>= market.costs.secret_sauce) { buttons.push({ id: "btn_buy_sauce", label:
`Buy Secret Sauce ($${market.costs.secret_sauce})`, color: "#10b981", action:
{ name: "market_purchase", payload: { playerId: mainPlayer, item:
"secret_sauce" } } }); } if (core.budget >= market.costs.small_van) {
buttons.push({ id: "btn_buy_van", label: `Buy Small Van ($${market.costs.small_
van})`, color: "#3b82f6", action: { name: "market_purchase", payload: {
playerId: mainPlayer, item: "small_van" } } }); } if (core.budget >=
market.costs.michelin_special) { buttons.push({ id: "btn_buy_michelin", label:
`Buy Michelin Special ($${market.costs.michelin_special})`, color: "#f59e0b",
action: { name: "market_purchase", payload: { playerId: mainPlayer, item:
"michelin_special" } } }); } // 2. CONTEXT-AWARE ATTACK BUTTON // Only show
the attack button if they have BOTH a special AND a van to deliver it! if
(inv.michelin_special > 0 && inv.small_van > 0) { buttons.push({ id:
"btn_attack", label: "Launch Michelin Drop!", color: "#ef4444", action: {
name: "launch_event", payload: { attackerId: mainPlayer, targetId:
"sushi_bot", actionId: "SPECIAL_EVENT", asset: "small_van", payload:
"michelin_special" } } }); } // 3. ALWAYS SHOW RESOLVE ROUND buttons.push({
id: "btn_end_round", label: "Resolve Round", color: "#6366f1", action: { name:
"resolve_round", payload: { players: ["taco_bot", "sushi_bot", "noodle_bot"] }
} }); // Extract all players on the board to draw them const entities = [];
let xOffset = 100; for (const key o
f Object.keys(state)) { if (key.startsWith('players.') && key.endsWith('.core')
) { entities.push({ id: key.split('.')[1], type: "TRUCK", x: xOffset, y: 300
}); xOffset += 250; } } return { mutatedState: { 'world.ui_manifest': {
buttons, entities } }, events: [] }; }; await saveAs('omega_ftr_update_ui',
updateUi, { state: { type: 'object', required: false }, intent: { type:
'object', required: false } }); 

Step 2: Add the Post-Processing Hook to the Game Master

Open gm-server.js and find the async function processIntent(channel,
actionName, intent) block. We need to add a small section between applying
mutations and broadcasting them.

Update the function to look like this:

async function processIntent(channel, actionName, intent) { console.log(`[GM]
Processing intent '${actionName}' for ${channel}...`); try { // 1. Execute
Sandbox Logic const result = await registry.run(actionName, { state:
authoritativeState, intent }); // 2. Apply mutations to Authoritative State if
(result.mutatedState) { for (const [key, val] of Object.entries(result.mutatedS
tate)) { authoritativeState[key] = val; } } // --- NEW: DYNAMIC SDUI GENERATOR
--- // If the world exists, rebuild the UI for the new state reality if
(authoritativeState['world.status']) { try { const uiResult = await
registry.run('update_ui', { state: authoritativeState, intent: { mainPlayer:
'taco_bot' } }); if (uiResult.mutatedState['world.ui_manifest']) { const
newManifest = uiResult.mutatedState['world.ui_manifest']; authoritativeState['w
orld.ui_manifest'] = newManifest; // Piggyback the new UI manifest onto the
client broadcast! result.mutatedState['world.ui_manifest'] = newManifest; } }
catch (uiErr) { console.error('[GM] UI Pipeline Error:', uiErr.message); } }
// ----------------------------------- // 3. Broadcast synchronization to all
clients const clients = channels.get(channel) || new Set(); const syncMessage
= JSON.stringify({ type: 'STATE_SYNC', payload: { mutatedState:
result.mutatedState, events: result.events } }); clients.forEach(client => {
if (client.readyState === 1) client.send(syncMessage); }); console.log(`[GM]
Broadcasted events [${result.events.join(', ')}]`); } catch (e) {
console.error(`[GM] Execution failed:`, e.message); } } 

How to test it:

Restart your Game Master (node gm-server.js).

Refresh the browser UI and click Initialize Match.

Watch the buttons closely. Click Buy Michelin Special ($4) twice.

The moment Taco Bot's budget drops from $8 -> $4 -> $0, the Game Master will
evaluate the UI routine, realize you can no longer afford the items, and the
buy buttons will instantly vanish from your screen.

Even better, because you just bought a Michelin Special and already had a Van,
the Launch Michelin Drop button will dynamically appear!

Let me know if the Canvas UI respects the disappearing buttons!