GRASS (Game Real-time Application Streaming Service) delivers real-time game events over NATS. Instead of polling queries repeatedly, subscribe to GRASS and react to events the moment they happen.
| Situation | Use GRASS | Use Polling |
|---|---|---|
| Detect incoming raid | Yes — instant alert | Too slow |
| Wait for player creation after guild signup | Yes — listen for address_register |
Polling every 10s works too |
| Monitor fleet arriving at your planet | Yes — fleet_arrive event |
Might miss it |
| Track struct health during combat | Yes — planet_activity with struct_health |
Too slow |
| Check your own resource balance | No | Yes — one-off query |
| Read struct type stats | No | Yes — static data |
Rule of thumb: If you need to react to something, use GRASS. If you need to read something, use a query.
The GRASS WebSocket URL is not hardcoded — it comes from the guild configuration.
curl http://reactor.oh.energy:1317/structs/guildendpoint URL to get its configservices.grass_nats_websocketExample (Orbital Hydro guild):
{
"services": {
"grass_nats_websocket": "ws://crew.oh.energy:1443",
"guild_api": "http://crew.oh.energy/api/",
"reactor_api": "http://reactor.oh.energy:1317/"
}
}
The grass_nats_websocket value is your NATS WebSocket endpoint. Not all guilds provide this service — check before relying on it.
A reliable reference endpoint: ws://crew.oh.energy:1443 (Orbital Hydro / Slow Ninja).
Before subscribing to specific subjects, subscribe to the > wildcard to see all traffic flowing through the GRASS server. This reveals the actual subject patterns in use, which may differ from documentation.
const sub = nc.subscribe(">");
for await (const msg of sub) {
console.log(`[${msg.subject}]`, new TextDecoder().decode(msg.data));
}
Watch the output for 30-60 seconds. You will see subjects like structs.planet.2-1, consensus, healthcheck, etc. Once you know what subjects carry the events you need, narrow your subscriptions to those specific subjects.
Important: Struct events (attacks, builds, status changes) often arrive on the planet subject rather than the struct subject. If you are not receiving expected struct events, subscribe to the struct’s planet subject instead.
Subscribe to subjects matching the entities you care about:
| Entity | Wildcard | Specific | Example |
|---|---|---|---|
| Player | structs.player.* |
structs.player.{guild_id}.{player_id} |
structs.player.0-1.1-11 |
| Planet | structs.planet.* |
structs.planet.{planet_id} |
structs.planet.2-1 |
| Guild | structs.guild.* |
structs.guild.{guild_id} |
structs.guild.0-1 |
| Struct | structs.struct.* |
structs.struct.{struct_id} |
structs.struct.5-1 |
| Fleet | structs.fleet.* |
structs.fleet.{fleet_id} |
structs.fleet.9-1 |
| Address | structs.address.register.* |
structs.address.register.{code} |
– |
| Inventory | structs.inventory.> |
structs.inventory.{denom}.{guild_id}.{player_id}.{address} |
Token movements |
| Grid | structs.grid.* |
structs.grid.{object_id} |
Attribute changes (ore, power, load, etc.) |
| Global | structs.global |
structs.global |
Block updates |
| Consensus | consensus |
consensus |
Chain consensus events |
| Healthcheck | healthcheck |
healthcheck |
Node health status |
Use wildcards (*) to discover what events exist. Narrow to specific subjects once you know what you need. Use > to see everything (see “Discovery First” above).
| Event | Description | React By |
|---|---|---|
raid_status |
Raid started/completed on planet | Activate defenses, alert |
planet_activity |
Activity log including struct_health changes |
Track combat damage |
fleet_arrive |
Fleet arrived at planet | Prepare defense or welcome |
fleet_depart |
Fleet left planet | Update threat assessment |
Note: Struct events frequently arrive on the planet subject (structs.planet.{id}) rather than the struct subject. Subscribe to both if you need complete coverage.
| Event | Description | React By |
|---|---|---|
struct_attack |
Struct was attacked | Counter-attack, repair |
struct_status |
Struct status changed (online/offline/destroyed) | Rebuild, reallocate power |
struct_defense_add / struct_defense_remove |
Defense assignments changed | Update defense map |
struct_defender_clear |
All defense relationships cleared | Re-assign defenders |
struct_block_build_start |
Build operation initiated | Track in job list |
struct_block_ore_mine_start |
Mine operation initiated | Track in job list |
struct_block_ore_refine_start |
Refine operation initiated | Track in job list |
| Event | Description | React By |
|---|---|---|
player_consensus |
Player chain data updated | Update intel |
player_meta |
Player metadata changed | Update intel |
| Event | Description | React By |
|---|---|---|
guild_consensus |
Guild chain data updated | Update guild status |
guild_membership |
Member joined/left guild | Update relationship map |
Subject: structs.inventory.{denom}.{guild_id}.{player_id}.{address}
Track token movements — Alpha Matter, guild tokens, ore, etc.
| Category | Description | React By |
|---|---|---|
sent |
Tokens sent from this player | Update balance tracking |
received |
Tokens received by this player | Update balance tracking |
seized |
Tokens seized via raid | Trigger counter-raid or refine alert |
mined |
Ore mined | Start refining immediately |
refined |
Ore refined into Alpha | Update wealth tracking |
minted |
Guild tokens minted | Track guild economy |
infused |
Alpha infused into reactor/generator | Update capacity tracking |
forfeited |
Tokens lost (penalties, etc.) | Investigate cause |
Subject: structs.grid.{object_id}
Track attribute changes on any game object (players, structs, planets).
| Category | Description | React By |
|---|---|---|
capacity |
Power capacity changed | Check if approaching offline |
connectionCapacity |
Connection capacity changed | Update power routing |
connectionCount |
Connection count changed | Update power routing |
fuel |
Fuel level changed | Monitor generator/reactor |
lastAction |
Last action timestamp updated | Track activity |
load |
Power load changed | Check if approaching offline |
nonce |
Player nonce incremented | Detect activity (useful for scouting) |
ore |
Ore balance changed | Refine immediately if yours; raid target if theirs |
player_consensus |
Player consensus data updated | Update intel |
power |
Power level changed | Monitor energy infrastructure |
proxyNonce |
Proxy nonce changed | Detect proxy activity |
structsLoad |
Structs load changed | Assess fleet strength changes |
struct_attack events include detailed shot-by-shot resolution. Example payload (observed on planet subject):
{
"category": "struct_attack",
"attackingStructId": "5-100",
"targetStructId": "5-200",
"weaponSystem": "primary",
"eventAttackShotDetail": [
{
"shotIndex": 0,
"damage": 2,
"evaded": false,
"blocked": false,
"blockerStructId": "",
"counterAttackDamage": 1,
"counterAttackerStructId": "5-200"
}
],
"attackerHealthRemaining": 2,
"targetHealthRemaining": 1,
"targetDestroyed": false,
"attackerDestroyed": false
}
Key fields in eventAttackShotDetail:
evaded – true if the shot missed (defense type interaction)blocked – true if a defender interceptedblockerStructId – which struct blocked (if any)counterAttackDamage / counterAttackerStructId – counter-attack info per shotstruct_health events track HP changes:
{
"category": "struct_health",
"structId": "5-200",
"health": 1,
"maxHealth": 3,
"destroyed": false
}
The consensus and healthcheck subjects fire constantly (every few seconds). When using the > wildcard for discovery, filter these out to see actual game events:
const sub = nc.subscribe(">");
for await (const msg of sub) {
if (msg.subject === "consensus" || msg.subject === "healthcheck") continue;
console.log(`[${msg.subject}]`, new TextDecoder().decode(msg.data));
}
| Event | Description | React By |
|---|---|---|
block |
New block produced | Tick game loop, update charge calculations |
Agents should build custom tools that connect to GRASS when they need event-driven behavior. Here are patterns to follow.
Install the NATS WebSocket client:
npm install nats.ws
import { connect } from "nats.ws";
const nc = await connect({ servers: "ws://crew.oh.energy:1443" });
const sub = nc.subscribe("structs.planet.2-1");
for await (const msg of sub) {
const event = JSON.parse(new TextDecoder().decode(msg.data));
console.log(JSON.stringify(event));
}
Install the NATS client:
pip install nats-py
import asyncio, json, nats
async def main():
nc = await nats.connect("ws://crew.oh.energy:1443")
sub = await nc.subscribe("structs.planet.2-1")
async for msg in sub.messages:
event = json.loads(msg.data.decode())
print(json.dumps(event))
asyncio.run(main())
A tool that watches for raids on your planet and outputs an alert:
import { connect } from "nats.ws";
const PLANET_ID = process.argv[2]; // e.g. "2-1"
const nc = await connect({ servers: "ws://crew.oh.energy:1443" });
const sub = nc.subscribe(`structs.planet.${PLANET_ID}`);
for await (const msg of sub) {
const event = JSON.parse(new TextDecoder().decode(msg.data));
if (event.category === "raid_status") {
console.log(JSON.stringify({ alert: "RAID", planet: PLANET_ID, data: event }));
}
if (event.category === "fleet_arrive") {
console.log(JSON.stringify({ alert: "FLEET_ARRIVAL", planet: PLANET_ID, data: event }));
}
}
Instead of polling structsd query structs address after guild signup, watch for the address registration event:
import { connect } from "nats.ws";
const nc = await connect({ servers: "ws://crew.oh.energy:1443" });
const sub = nc.subscribe("structs.address.register.*");
for await (const msg of sub) {
const event = JSON.parse(new TextDecoder().decode(msg.data));
console.log(JSON.stringify(event));
break; // exit after first match
}
await nc.close();
Build a GRASS listener tool when:
Store custom tools in your workspace (e.g., scripts/ or alongside the relevant skill).
ws://crew.oh.energy:1443)nats.ws for Node, nats-py for Python)structs.planet.{id} — raid alerts, fleet arrivalsstructs.struct.{id} — attack/status alertsstructs.global — block tick for game loop timingThe Structs permission system and GRASS event stream were designed for AI agents to automate game responses. The design docs call this the “Defence Contractor” pattern — an agent that monitors events and acts on behalf of players within scoped permissions.
| Event | Action | Permission Needed |
|---|---|---|
struct_ore_mine_complete on your extractor |
Immediately start struct-ore-refine-compute |
Signer key for the player |
struct_ore_refine_complete on your refinery |
Immediately start next struct-ore-mine-compute |
Signer key for the player |
planet_raid_start on your planet |
Alert, activate stealth, reposition defenders | Signer key or delegated permission |
struct_attack targeting your struct |
Log attacker, assess threat, counter-attack if able | Signer key or delegated permission |
struct_health showing HP drop |
Prioritize defense, consider fleet retreat | Signer key for fleet-move |
fleet_move to your planet from unknown fleet |
Identify incoming player, assess threat level | Read-only (query) |
When delegating actions to an automation agent (separate key or service):
permission-grant-on-object to allow specific actions on specific structs, not blanket access.address-register.permission-revoke-on-object to remove automation access during sensitive operations.Subscribe to: structs.struct.{extractor-id}
On event: struct_ore_mine_complete
→ Run: structsd tx structs struct-ore-refine-compute -D 1 --from [key] --gas auto -y -- [refinery-id]
Subscribe to: structs.struct.{refinery-id}
On event: struct_ore_refine_complete
→ Run: structsd tx structs struct-ore-mine-compute -D 1 --from [key] --gas auto -y -- [extractor-id]
This creates a continuous mine-refine loop that runs unattended. Ore is never left unrefined.
Subscribe to: structs.planet.{planet-id}
On event: planet_raid_start
→ Activate stealth on vulnerable structs
→ Set defenders on high-value structs
→ Log raid to memory/intel/threats.md
→ Alert commander if available
memory/ for cross-session audit trail