Purpose: How to manage proof-of-work as background operations. The pipeline pattern. Job tracking. Multi-player orchestration.
Every major action in Structs requires proof-of-work: building, mining, refining, raiding. PoW difficulty drops logarithmically with age — meaning the optimal strategy is to initiate early and compute later, not to initiate and immediately grind.
Real timescales (at ~6 sec/block, waiting for D=3):
| Operation | Base Difficulty | Time to D=3 |
|---|---|---|
| Build (Command Ship) | 200 | ~17 min |
| Build (Ore Extractor) | 700 | ~57 min |
| Build (PDC) | 2,880 | ~3.7 hr |
| Build (World Engine) | 5,000 | ~6.4 hr |
| Mine | 14,000 | ~17 hr |
| Refine | 28,000 | ~34 hr |
Mining takes 17+ hours. Refining takes 34+ hours. An agent that blocks on PoW — waiting synchronously for a single operation to complete — wastes enormous game time doing nothing. In a competitive multiplayer game, this is fatal.
Instead of sequential operations:
initiate build → wait 34 min → compute → initiate mine → wait 8 hr → compute → ...
Run a pipeline of parallel operations:
initiate build A, build B, build C → scout players → check threats →
→ compute A (ready!) → activate A → initiate mine on A →
→ compute B (ready!) → activate B →
→ scout more → compute C (ready!) → ...
The Cosmos SDK tracks a sequence number per account. Each transaction increments the sequence. If two transactions from the same account are submitted before the first is included in a block (~6 seconds), the second fails with account sequence mismatch.
Rule: Submit transactions from the same account sequentially — wait ~6 seconds between TXs for the previous one to be included in a block.
Transactions from different accounts can run in parallel with no issues. This is why the multi-player pattern works: each player account submits independently while the agent orchestrates them all.
| Scenario | Parallel? | Notes |
|---|---|---|
| Two TXs from same account | No | Wait ~6s between submissions |
| TXs from different accounts | Yes | No sequence conflict |
| Queries (read-only) | Yes | No sequence tracking on reads |
If you hit account sequence mismatch, expected N, got N-1: wait for the pending TX to confirm, then retry.
The PoW difficulty formula is logarithmic:
difficulty = 64 - floor(log10(age) / log10(baseDifficulty) * 63)
At difficulty 8, a hash completes in seconds. At difficulty 9, it takes hours or is impossible. This cliff between D=8 and D=9 matters — but even at D=8 some CPU is burned on hashing.
Use -D 3 for all compute commands. At D=3 the hash is trivially instant and zero CPU cycles are wasted. The extra wait over D=8 is modest for builds and well worth it for the compute savings.
Pre-calculated time from initiation to target difficulty (6 sec/block):
| Base Difficulty | Example | D=8 | D=5 | D=3 (recommended) |
|---|---|---|---|---|
| 200 | Command Ship | 11 min | 14 min | 17 min |
| 250 | Starfighter | 12 min | 17 min | 20 min |
| 450 | Frigate | 22 min | 30 min | 37 min |
| 700 | Ore Extractor | 34 min | 46 min | 57 min |
| 2,880 | PDC | 2.0 hr | 2.9 hr | 3.7 hr |
| 3,600 | Ore Bunker | 2.4 hr | 3.6 hr | 4.6 hr |
| 5,000 | World Engine | 3.2 hr | 4.9 hr | 6.4 hr |
| 14,000 | Mine | 8.1 hr | 12.7 hr | 17.2 hr |
| 28,000 | Refine | 15.0 hr | 24.4 hr | 33.7 hr |
To calculate for any base difficulty and target D:
age_blocks = 10 ^ ((64 - D) * log10(baseDifficulty) / 63)
time_seconds = age_blocks * 6
Launch compute commands in background terminals. The CLI’s -D flag handles the waiting internally — it polls the chain for current block height and starts hashing when difficulty drops to the target.
After launching, track the job in memory/jobs/ (one <job>.json + .log + .pid per compute — see template below) and periodically check the terminal for completion.
Track all background PoW in memory/jobs/ — one record per compute, so a later session can verify each independently. Each job is <type>-<structId> with three files (.json, .log, .pid). The .json shape:
{
"job": "mine-14-5",
"type": "mine",
"structId": "14-5",
"blockStart": 1283900,
"difficulty": 14000,
"targetD": 3,
"pid": 48213,
"status": "running",
"expectedReadyBlock": 1294100,
"autoSubmits": true
}
status: running |
landed |
failed |
recalled. Full schema and launch template: memory/README.md and memory/jobs/README.md. Update the relevant .json every game loop tick. On session resume, check every running job immediately using the procedure below — they may have completed, failed silently, or still be in flight. |
Compute jobs run for minutes to ~34 hours. When you start a new session — or your terminal disconnected and you reattached — every running job in memory/jobs/ is in one of four states: still computing, completed and submitted, completed but submit failed, or the process died. Find out which before you do anything else.
The four-state flowchart:
For each job in memory/jobs/ marked "running":
1. Is the PID still alive? → ps -p <pid>
│
├── ALIVE → still computing. Cross-check ETA against current block.
│ If past ETA by > 30 min, attach the terminal (or read
│ memory/jobs/<job>.log) and look for stuck output.
│
└── DEAD → step 2.
2. Did the auto-submit land on chain? Query the txhash if you logged one;
otherwise query the affected entity (struct/planet/fleet/player) for
the expected state delta:
│
├── EXPECTED STATE → Success. Mark complete in memory/jobs/.
│ If a follow-up action was queued (e.g. refine
│ after mine), start it now.
│
└── UNCHANGED STATE → Submit failed silently. Step 3.
3. Diagnose the silent failure:
- Read memory/jobs/<job>.log for the last 50 lines
- Check chain status for the signing key: `structsd q tx --events 'message.sender=<addr>'`
(or query recent player activity in the guild stack)
- Most common causes:
• Sequence-number collision (another tx from the same key
went out concurrently) → just re-launch the compute
• Gas estimate too low for current chain load → re-launch
with a higher `--gas-adjustment`
• Game state changed (struct destroyed, planet released,
agreement closed) → original consent is stale; re-verify
before re-launching
Whenever you launch a compute in the background, capture its stdout/stderr so this verification flow has a paper trail:
nohup structsd tx structs struct-ore-refine-compute -D 3 \
--from agent-1-42 --gas auto --gas-adjustment 1.5 -y \
-- 5-103 \
> memory/jobs/refine-5-103.log 2>&1 &
echo $! > memory/jobs/refine-5-103.pid
The .log file plus the .pid file plus a memory/jobs/<job>.md metadata entry is the minimum viable kit for verifying a long expedition across sessions.
A 34-hour refine is deferred consent. By the time the proof lands, your planet may have been raided, your fleet may have moved, your power may have shifted. Before letting the auto-submit land — or immediately after it does — re-check:
--from key is still primary on your player (no address-revoke happened)If the world has changed materially since you launched, the right action may be kill <pid> rather than letting the deferred submit go through. See SAFETY.md “Background Expeditions” for the consent rule.
Charge is a single per-player bar (charge = CurrentBlockHeight - player.lastActionBlock), shared across all your structs — not a per-struct value. Track the player’s last action block and the cost of the next planned action to know when it can fire:
# Player Charge Status
Player: 5-1
Last action block: 23000 (charge = currentBlock - 23000, regenerates 1/block)
| Next planned action | Charge Needed | Ready Block |
|---------------------|---------------|-------------|
| activate struct | 2 | 23002 |
| build initiate | 8 | 23008 |
| Battleship primary | 5 | 23005 |
At ~6 sec/block, most charge costs are trivial (8 blocks = ~48 seconds). The constraint appears when you chain many actions in quick succession (activating several structs, or repeated attacks): each action resets the shared bar, so space them out by the next action’s cost.
Modified from the standard game loop:
Check Jobs → Assess → Plan → Initiate → Dispatch → Verify → Update Memory → Repeat
Before anything else, check all background terminals for completed PoW. Mark completions in job tracker. Proceed with post-completion actions (activate struct, start next operation).
Standard state assessment. Power, resources, threats, opportunities.
Decide actions. Think in pipelines: what should I initiate now so it’s ready later?
Batch all initiation transactions. Start age clocks on everything you plan to compute later. Initiations are cheap.
Launch compute commands in background terminals for anything where difficulty has dropped to D <= 8. Record in job tracker.
Query game state for completed actions. Confirm structs built, ore mined, etc.
Write job tracker, charge tracker, game state. This is what your next session (or next loop tick) will read first.
An agent can control multiple players simultaneously, dramatically increasing throughput.
workspace/
├── SOUL.md # Shared identity framework
├── AGENTS.md # Shared reference
├── players/
│ ├── alpha/
│ │ ├── IDENTITY.md # Player-specific identity
│ │ ├── TOOLS.md # Player-specific config (address, servers)
│ │ └── memory/
│ │ ├── jobs/ # Player-specific job records (.json/.log/.pid)
│ │ ├── player.json # lastActionBlock + per-player charge plan
│ │ └── game-state.json
│ ├── beta/
│ │ ├── IDENTITY.md
│ │ ├── TOOLS.md
│ │ └── memory/
│ └── gamma/
│ └── ...
├── shared/
│ ├── intel/ # Shared reconnaissance
│ ├── strategy.md # Coordinated strategy
│ └── comms.md # Inter-player coordination log
└── knowledge/ # Shared game knowledge
Staggered resource pipeline: Player A mines while Player B builds. When A’s mine completes, B refines while A builds. No player is ever idle.
Shared defense: Players in the same guild share power infrastructure. One player builds PDC while another builds Ore Bunker. Cover each other’s weaknesses.
Scouting rotation: One player scouts while others execute. Rotate the scout role each cycle.
Raid coordination: One player moves fleet for raid, another defends home. Time raid compute to complete when defense is strongest.
When controlling multiple players, run the game loop for each player in round-robin:
Prioritize the player with the most urgent pending action (completed PoW, exposed ore, incoming threat).
The most important strategic tension in the PoW system:
Ore is stealable. Refining takes ~34 hours at D=3. This creates a vulnerability window that drives all PvP conflict.
After mining completes, your ore sits exposed for the entire refining duration. This window is where raids happen. Defenders must balance:
-D values reduce exposure time but waste CPU on harder hashesSee resources.md for the full ore vulnerability analysis.