A native desktop app for Structs — wraps the structs-webapp game client in a Tauri shell with GPU-accelerated proof-of-work hashing, native OS notifications, an embedded MCP server for AI agents, and CORS-free API access.
WARNING: This software is untested, unsafe, unreasonable, unstable and should not be run. Do so at your own risk.
In the distant future the species of the galaxy are embroiled in a race for Alpha Matter, the rare and dangerous substance that fuels galactic civilization. Players take command of Structs, a race of sentient machines, and must forge alliances, conquer enemies and expand their influence to control Alpha Matter and the fate of the galaxy.
Structs is a decentralized game in the Cosmos ecosystem, operated and governed by our community of players--ensuring Structs remains online as long as there are players to play it.
- GPU Hashing — Multi-threaded CPU + GPU SHA256 proof-of-work at ~200M hashes/sec via wgpu
- MCP Server — AI agents can play Structs through the Model Context Protocol (7 tools, 4 prompts, compendium resources)
- Native Notifications — Desktop alerts for raids, fleet movements, mining completion, etc.
- Policy Engine — Standing orders with delta tracking (auto-refine, power alerts, combat mode)
- Guild Configuration — Connect to any guild's infrastructure with stored configs
- CORS Proxy — All API calls routed through Rust to bypass browser CORS restrictions
- Pixel-Perfect Rendering — CSS
zoomreplacestransform: scale()for crisp pixel art in WKWebView - Background Operation — App stays active when minimized (no App Nap throttling)
- Debug Tab — In-app diagnostics showing player identity, infrastructure endpoints, MCP status, hash engine, and policies
git clone --recursive https://github.com/playstructs/structs-universe.git
cd structs-universe
npm install
make sync # Pull latest webapp, patch, webpack build, sync compendium
make build # Production build + code sign
make launch # Open the .app bundlemake dev Launch in development mode (hot reload, devtools)
make build Production build + code sign
make sync Pull latest structs-webapp + rebuild frontend + sync compendium
make release Full rebuild from latest submodule
make clean Remove build artifacts
make sign Re-sign existing .app bundle
make launch Open the signed release bundle
make launch-debug Launch from terminal (shows stderr logs)
make check Check Rust compilation without building
make test Run Rust tests
make tag v=X.Y.Z Create a release tag (triggers CI builds)
make help Show all targets
Tag a version to trigger GitHub Actions CI builds for all platforms:
make tag v=0.2.0
git push origin v0.2.0
# Builds .app (macOS), .exe/.msi (Windows), .deb/.AppImage (Linux)structs-universe/
├── structs-webapp/ # Git submodule (upstream game UI)
├── scripts/
│ ├── sync.sh # Full build pipeline + compendium sync
│ ├── build-frontend.sh # Conditional build gate
│ ├── apply-patches.sh # Endpoint + renderer patches
│ └── sign.sh # macOS code signing
├── frontend/ # Built output served by Tauri
│ ├── index.html # Static HTML (converted from Twig)
│ └── structs-config.js # Config injection, notifications, Worker shim, sync, debug tab
├── src-tauri/
│ ├── tauri.conf.json
│ ├── Cargo.toml
│ ├── Info.plist # macOS ATS exception for HTTP connections
│ └── src/
│ ├── main.rs # App entry, init scripts, fetch proxy, pixel rendering
│ ├── guild_config.rs # Multi-guild config storage
│ ├── http_proxy.rs # CORS bypass via reqwest with cookie persistence
│ ├── notifications.rs # Native OS notifications (UNUserNotificationCenter / notify-rust)
│ ├── game_state.rs # JS→Rust gameState sync, charge tracking, combat mode sync interval
│ ├── hasher/ # GPU/CPU SHA256 proof-of-work engine
│ │ ├── mod.rs # Task management, GPU init, Tauri commands
│ │ ├── cpu.rs # Multi-threaded rayon hasher
│ │ ├── gpu.rs # wgpu compute shader dispatch
│ │ ├── sha256.wgsl # WGSL SHA256 compute shader
│ │ ├── difficulty.rs # Difficulty calculation + check
│ │ └── types.rs # TaskParams, TaskRegistry, snapshots
│ └── mcp/ # Embedded MCP server
│ ├── server.rs # HTTP server (rmcp + axum) with bearer token auth
│ ├── handler.rs # Tool/resource/prompt routing + ServerHandler impl
│ ├── config.rs # MCP config persistence (port, token)
│ ├── cosmos_client.rs # Typed Cosmos REST API client
│ ├── event_buffer.rs # NATS event ring buffer (1000 events)
│ ├── policy.rs # Standing orders / automation engine with delta tracking
│ ├── tx_queue.rs # Transaction signing bridge (Rust ↔ JS CosmJS)
│ ├── error_translator.rs # Chain error → human message
│ ├── prompts.rs # 4 built-in agent workflow prompts
│ ├── resources.rs # Compendium file serving (structs-ai docs)
│ └── tools/
│ ├── dashboard.rs # Player situational awareness
│ ├── query.rs # Entity lookup with enrichment
│ ├── hasher.rs # Hash task management with ETAs
│ ├── action.rs # Game actions with preflight checks
│ ├── intel.rs # Strategic intelligence
│ ├── policy.rs # Standing order management
│ └── format.rs # Shared formatting utilities
├── .mcp.json # Claude Code MCP server config
├── .github/workflows/ # CI/CD
│ ├── ci.yml # Build check on push/PR (macOS, Windows, Linux)
│ └── release.yml # Multi-platform release on version tags
└── Makefile # Build system
The app runs an MCP server on localhost:8420 with bearer token authentication. AI agents interact with the game through 7 tools, 4 prompts, and compendium resources.
| Tool | Purpose |
|---|---|
structs_dashboard |
Full player overview: power, charge, resources, structs, hash tasks, recent events |
structs_query |
Query any game entity with enriched output (resolved names, decoded flags, formatted units) |
structs_hash |
Manage proof-of-work tasks with ETAs (list, start, stop, progress) |
structs_action |
Execute game actions with preflight checks (explore, build, mine, attack, defend, etc.) |
structs_intel |
Strategic intelligence (what can I build, power forecast, economy status, timeline) |
structs_policy |
Standing orders (auto_refine, power_alert, never_build_unsafe, auto_defend) |
| Prompt | Purpose |
|---|---|
structs_first_session |
Orientation for new agents — check dashboard, identify priorities |
structs_game_loop |
One tick: dashboard → assess → plan → execute → verify |
structs_state_assessment |
Deep analysis with risk ratings: power, threats, economy, operations |
structs_combat_planning |
Scout, simulate, recommend attack/wait/abort |
The structs-ai compendium is bundled as MCP resources (synced during make sync). Agents can read game documentation on demand:
structs://knowledge/mechanics/combat.md— Combat systemstructs://knowledge/mechanics/power.md— Energy systemstructs://playbooks/phases/early-game.md— Strategy guidesstructs://QUICKSTART.md— Getting started
The MCP connection details are shown in the app's Debug tab and browser console. Copy the .mcp.json config from the Debug tab, or add manually:
{
"mcpServers": {
"structs-game": {
"type": "http",
"url": "http://127.0.0.1:8420/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN_HERE"
}
}
}
}The bearer token is generated on first launch and stored in ~/Library/Application Support/structs-app/mcp_config.json (macOS). Find it in the Debug tab or browser console.
The MCP server requires a bearer token on every request. Without it, requests return 400 Bad Request. This prevents unauthorized access from other processes or websites on the same machine.
The app replaces the webapp's JavaScript WebWorker hasher with a Rust-native implementation:
- CPU: Multi-threaded via rayon with hardware SHA256 instructions (~3M h/s)
- GPU: wgpu compute shader dispatching 1M nonces per batch (~200M h/s)
- Auto-selection: GPU used when available, falls back to CPU
- Worker shim: Transparently intercepts
new Worker()calls for TaskWorker.js, routing to Rust while maintaining the existing TaskManager.js interface - ETA estimates: Each task shows percent complete, current difficulty, hashrate, and estimated time remaining
structs-config.jsinstalls aProxyonwindow.Worker- When TaskWorker.js is requested, a real Worker is created (for instanceof checks) but
postMessageis overridden postMessage([state])extracts TaskState fields and callsstart_hash_taskRust command- Rust starts hashing on GPU/CPU, emits
hash_progress/hash_completeTauri events - Events are forwarded back through
onmessageto TaskManager.js - TaskManager submits the completion transaction to the chain
Native OS notifications fire for game events via NATS WebSocket interception. Events are filtered by player context — you only get notified about events relevant to your planet and fleet.
| Event | When |
|---|---|
| Raid Alert | Your planet is raided (with status: initiated, retreated, successful, etc.) |
| Structs Under Attack | Your structs take damage |
| Enemy Fleet Incoming/Departed | Enemy fleet arrives at or leaves your planet |
| Your Fleet Moved | Your fleet arrives or departs |
| Mining/Refining/Build Started | PoW task initiated on your structs |
| Struct Status Changed | Build complete, went offline, destroyed |
| Alpha Matter Transfer | Sent or received |
| Power Alert | Load exceeds capacity threshold |
| Platform | API | Notes |
|---|---|---|
| macOS | UNUserNotificationCenter (objc2) | Requires .app bundle + code signing |
| Windows | notify-rust (WinRT) | Works out of the box |
| Linux | notify-rust (D-Bus) | Works out of the box |
make sync runs scripts/sync.sh:
- Updates
structs-webappgit submodule to latest - Copies source to temp build directory
- Applies 6 sed patches via
apply-patches.sh:- GuildAPI.js → configurable API URL
- index.js (GrassManager) → configurable NATS WebSocket URL
- SigningClientManager.js → configurable RPC WebSocket URL
- index.js → expose gameState to window for Tauri sync
- DefeatBannerViewModel.js → canvas renderer for crisp pixel art
- VictoryBannerViewModel.js → canvas renderer for crisp pixel art
- Runs
npm install+webpack --mode=production - Copies webpack output + static assets to
frontend/ - Syncs structs-ai compendium to
~/.config/structs-app/compendium/
macOS notifications require a signed .app bundle. The build automatically signs:
codesign --force --deep -s "Slow Ninja Inc" Structs.appStanding orders that run automatically on game state transitions:
| Policy | Default | Behavior |
|---|---|---|
auto_refine |
ON | Auto-start refine when mining completes |
power_alert |
ON (80%) | Warn when power utilization crosses threshold |
never_build_unsafe |
ON | Block builds that would cause offline |
auto_defend |
OFF | Counter-attack when attacked (ask mode — combat is the exception) |
sequence_retry |
ON (3) | Silent retry for sequence mismatch errors |
Policies use delta tracking — they compare previous vs current state snapshots to detect transitions (not just current conditions). This prevents double-triggers and missed events.
The policy engine auto-detects combat events (raids, attacks, fleet arrivals) and switches the gameState sync interval from 10s to 3s for faster response. Drops back to 10s after 30 blocks (~3 min) of quiet.
Game actions submitted through MCP flow through a Rust ↔ JS bridge:
- MCP
structs_actiontool validates preconditions (charge, power, entity state) - Rust emits
mcp_transaction_requestTauri event to the webview - JS listener in
structs-config.jsroutes to the correctSigningClientManager.queueMsg*()method - CosmJS signs and broadcasts the transaction
- JS responds back via
mcp_transaction_responseTauri command - MCP tool returns the result to the agent
The app adds a "Debug" tab to the game UI (alongside Fleet, Guild, Account) showing:
- Identity: Player ID, address (click to copy), guild, substation, fleet, planet, block height
- Infrastructure: Guild API, Reactor API, Client WS, NATS WS endpoints
- MCP Server: URL, status, bearer token (click to copy), config (click to copy full .mcp.json)
- Hash Engine: GPU availability, active tasks with status and hashrate
- Policies: All standing orders with ON/OFF status
Players join guilds that provide infrastructure. Configs stored at ~/Library/Application Support/structs-app/guild_configs.json (macOS):
| Field | Purpose |
|---|---|
guildApi |
REST API for game data |
reactorApi |
Cosmos REST endpoint |
clientWs |
Tendermint RPC WebSocket |
grassNatsWs |
NATS event stream |
A default config ships for new players. The app supports multiple guild configs with one active at a time.
Why not fork structs-webapp? Active upstream development. Sed patches at build time are more resilient than maintaining a fork.
Why proxy fetch through Rust? Guild APIs don't set CORS headers. Rust's reqwest makes requests server-side where CORS doesn't apply. A persistent cookie jar maintains session state across requests.
Why custom notifications instead of tauri-plugin-notification? The plugin uses deprecated NSUserNotification on macOS. We use UNUserNotificationCenter directly via objc2 bindings.
Why zoom instead of transform: scale()? WKWebView applies bilinear interpolation to CSS transforms, blurring pixel art and text. CSS zoom uses nearest-neighbor scaling.
Why embed an MCP server? AI agents need a standardized way to play the game. One connection gives full situational awareness, action execution, and automation — the MCP is a "competent first officer" that handles plumbing so agents can focus on strategy.
Why bearer token auth on localhost? Any website in a browser can make HTTP requests to localhost. Without authentication, a malicious page could control the game. The bearer token prevents unauthorized access.
Why 400 instead of 401 for auth failures? Claude Code interprets 401 responses as "needs OAuth" and triggers an OAuth flow instead of using the configured bearer token. Returning 400 avoids this while still rejecting unauthorized requests.
Copyright 2026 Slow Ninja Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.