Database Schema Reference

Purpose: AI-readable reference for the Structs Guild Stack PostgreSQL database. Covers core game state tables, the key-value grid pattern, event categories, and ready-to-use query patterns.

Requires: Guild Stack running locally. See .cursor/skills/structs-guild-stack/SKILL.md for setup.


Schema Overview

PostgreSQL 17 with TimescaleDB extension. Six schemas:

Schema Purpose
structs Game state (~50 tables covering all game objects)
sync_state Chain indexer state: sync cursor, block log, raw blocks/events, handler error log
cache Compatibility views over sync_state.raw_* (read-only; not an event-sink)
view Computed views (view.player, view.guild, leaderboards, permission projection)
signer Transaction signing queue (TSA account/role/tx management)
sqitch Schema migration tracking

Full table enumeration: schemas/database-schema.md.

Chain events are ingested by the structs-sync-state service (not structsd). It polls the chain RPC and writes directly into structs.* and sync_state.*.

Database Roles

Role Access Used By
structs Superuser (owner) Administration, migrations
structs_indexer Read/write on structs.*, sync_state.*, cache.* (views) structs-sync-state, structs-grass
structs_webapp Read/write on most structs.*, full on signer.* Webapp, TSA
structs_crawler Read-only on select tables Crawler

For agent queries, use structs_indexer via the GRASS container (see guild-stack skill).


Core Game State Tables

structs.player

Column Type Notes
id varchar PK 1-{index} (e.g., 1-142)
index integer Numeric portion of ID
guild_id varchar Guild membership (e.g., 0-1)
guild_rank bigint Player’s rank within their guild (1 = highest, 101 = default on join, 0 = unset)
planet_id varchar Home planet (e.g., 2-105)
fleet_id varchar Fleet (e.g., 9-142)
primary_address varchar Cosmos address
creator varchar Who created this player
username varchar Player display name (chain UGC; from MsgPlayerUpdateName or signup proxy)
pfp varchar Profile picture URI (chain UGC; from MsgPlayerUpdatePfp or signup proxy)

structs.fleet

Column Type Notes
id varchar PK 9-{index}
owner varchar Player ID
status varchar on_station, away
location_type varchar planet
location_id varchar Planet ID where fleet is located
command_struct varchar Command Ship struct ID
space_slots / air_slots / land_slots / water_slots integer Available slots per ambit

structs.struct

Column Type Notes
id varchar PK 5-{index}
type integer FK to struct_type.id (1-22)
owner varchar Player ID
location_type varchar fleet or planet
location_id varchar Fleet/planet ID
operating_ambit varchar space, air, land, water
slot integer Position within ambit (0-3)
is_destroyed boolean Destruction state
destroyed_block bigint Block height when destroyed

structs.struct_type

The authoritative reference for all game balance data (~60 columns). Key columns:

Weapons (primary + secondary prefixed):

Defense:

Other:

structs.struct_attribute

Key-value attributes per struct instance.

Column Type Notes
object_id varchar Struct ID
attribute_type varchar health, status, protectedStructIndex, etc.
val integer Attribute value

structs.struct_defender

Defense assignments.

Column Type Notes
defending_struct_id varchar PK The defender
protected_struct_id varchar The struct being protected

structs.planet

Column Type Notes
id varchar PK 2-{index}
owner varchar Player ID
max_ore integer Maximum ore capacity
space_slots / air_slots / land_slots / water_slots integer Slot counts
status varchar Planet status
name text Planet display name (chain UGC; from MsgPlanetUpdateName or optional name on planet-explore)
seized_ore numeric Cumulative ore taken from this planet across raids (planet-level total; per-raid totals live in planet_raid.seized_ore)

structs.planet_attribute

Column Type Notes
object_id varchar Planet ID
attribute_type varchar planetaryShield, etc.
val integer Attribute value

structs.planet_raid

Column Type Notes
planet_id varchar Target planet
fleet_id varchar Raiding fleet
status varchar initiated, completed, etc.
seized_ore numeric Ore taken

The Grid Table (Key-Value Pattern)

structs.grid is a key-value store for resource attributes. This is the most common source of query errors.

Column Type Notes
object_id varchar Player/planet/struct/etc ID
attribute_type varchar ore, alpha, structsLoad, capacity, fuel, power, etc.
val numeric The value

Wrong (no ore column exists):

SELECT ore FROM structs.grid WHERE object_id = '1-142';

Correct (filter by attribute_type):

SELECT val FROM structs.grid WHERE object_id = '1-142' AND attribute_type = 'ore';

Multiple attributes (use JOINs):

SELECT p.id,
    COALESCE(g_ore.val, 0) as ore,
    COALESCE(g_cap.val, 0) as capacity,
    COALESCE(g_load.val, 0) as structs_load
FROM structs.player p
LEFT JOIN structs.grid g_ore ON g_ore.object_id = p.id AND g_ore.attribute_type = 'ore'
LEFT JOIN structs.grid g_cap ON g_cap.object_id = p.id AND g_cap.attribute_type = 'capacity'
LEFT JOIN structs.grid g_load ON g_load.object_id = p.id AND g_load.attribute_type = 'structsLoad'
WHERE p.id = '1-142';

Grid Attribute Types

attribute_type Found On Meaning
ore player, planet Ore balance (player = mined/stealable; planet = remaining)
capacity player, reactor, substation Energy capacity
structsLoad player Energy consumed by active structs
fuel reactor Total ualpha infused
power infusion Energy generated from fuel
connectionCapacity substation Available capacity per connection
connectionCount substation Active connections
load player Base player load

planet_activity (Event Log)

TimescaleDB hypertable for all planet-level events.

Column Type Notes
time timestamp Event time
seq integer Sequence number (monotonically increasing, use as high-water mark)
planet_id varchar Planet where event occurred
category enum Event type (see below)
detail JSONB Event-specific data
block_height bigint Block height when the event occurred (populated by sync-state)

Event Categories

Category Description
block New block committed
raid_status Raid initiated/completed/failed
fleet_arrive Fleet arrived at planet
fleet_advance Fleet movement in progress
fleet_depart Fleet departed from planet
struct_attack Combat attack event
struct_defense_add Defense assignment added
struct_defense_remove Defense assignment removed
struct_status Struct status change (online/offline/destroyed)
struct_move Struct moved between slots/ambits
struct_block_build_start Build PoW started
struct_block_ore_mine_start Mining PoW started
struct_block_ore_refine_start Refining PoW started
struct_health Struct health changed (damage)
guild_consensus Guild consensus events
guild_meta Guild metadata changes
guild_membership Membership changes
player_consensus Player state changes (including UGC username/pfp updates)
guild_meta Guild off-chain metadata (description, tag, logo, services — not chain UGC name/pfp)

Polling Pattern (Real-Time Monitoring)

-- Initialize: set high-water mark
SELECT COALESCE(MAX(seq), 0) as last_seq
FROM structs.planet_activity
WHERE planet_id IN ('2-105');

-- Poll every ~6 seconds
SELECT seq, planet_id, category, detail::text
FROM structs.planet_activity
WHERE planet_id IN ('2-105', '2-127')
    AND seq > $LAST_SEQ
ORDER BY seq ASC;

The detail column for struct_attack includes attackerStructId, targetStructId, and eventAttackShotDetail with per-shot damage breakdowns.


Energy Commerce Tables

Table Key Columns Notes
reactor id, guild_id, validator, owner Links validator address to guild; owner is PlayerId
infusion destination_id, address, fuel, power, commission Composite PK: (destination_id, address)
allocation id, source_id, destination_id, controller Energy routing; controller is PlayerId (not address)
substation id, owner Power distribution nodes
provider id, rate_amount, rate_denom, access_policy Energy marketplace listings
agreement id, provider/consumer refs, capacity, duration Active purchase contracts

structs.guild

Column Type Notes
id varchar PK 0-{index} (e.g., 0-1)
entry_rank bigint Default guild rank assigned to new members (chain default: 101)
name varchar Guild display name (chain UGC; from MsgGuildUpdateName)
pfp varchar Guild profile picture (chain UGC; from MsgGuildUpdatePfp)

structs.guild_meta (off-chain guild config)

Guild presentation and infrastructure metadata — not chain UGC name/pfp (those live on structs.guild).

Column Type Notes
id varchar PK Guild ID, mirrors structs.guild.id
name varchar Legacy display name field (prefer structs.guild.name for on-chain UGC)
description text Guild description
tag varchar Short guild tag
logo varchar Logo URI
socials jsonb Social links
denom jsonb Guild token denomination map
services jsonb Guild API / GRASS / webapp endpoints
domain varchar Guild domain
website varchar Guild website
base_energy numeric Base energy allocation
this_infrastructure boolean Whether this guild stack hosts this guild’s infra

structs.substation (with UGC fields)

Column Type Notes
id varchar PK Substation ID
owner varchar Owner player ID
name text Substation name (chain UGC; from MsgSubstationUpdateName)
pfp text Substation profile picture (chain UGC; from MsgSubstationUpdatePfp)

structs.permission_guild_rank

Column Type Notes
object_id varchar The object permissions are set on
guild_id varchar The guild whose members receive the permission
permission bigint Single permission bit (power of 2)
rank bigint Worst-allowed guild rank; 0 = revoked
updated_at timestamptz Last update timestamp

Primary key: (object_id, guild_id, permission). See permissions.md for the guild rank permission system.

The view.permission_player (keyed by player_id) and view.permission_address (keyed by address) views project structs.permission rows joined with permission_guild_rank, exposing one boolean column per permission bit (perm_play, perm_admin, …, perm_hash_build/perm_hash_mine/perm_hash_refine/perm_hash_raid). The raw integer bitmask (val) lives on the base table structs.permission. The PermAll mask is 33554431 (bits 0..24 set).

sync_state schema (indexer)

Written by structs-sync-state. Key operator tables:

Table Purpose
sync_cursor Per-chain ingest pointer (last_height, status, lag_blocks, tip_height)
block_log One row per ingested block (tx/event counts, handler error count)
handler_error_log Per-event handler failures (for debugging replay)
raw_blocks, raw_tx_results, raw_events, raw_attributes Raw chain event storage
verification_report Output of sync-state verify runs

Monitor indexer health:

SELECT chain_id, last_height, status, lag_blocks, tip_height
FROM sync_state.sync_cursor;

The cache schema exposes four views (blocks, tx_results, events, attributes) over sync_state.raw_* for webapp backward compatibility.

structs.banned_word

Seed data for UGC name validation. The chain rejects any Msg*UpdateName (player, guild, planet, substation) whose name contains a substring matching any row in this table; webapps surface the same list via api/webapp/banned-word.md so client-side forms can preflight.

Column Type Notes
word text PK Banned token (lowercase)

structs.address_tag

Labelled address records. Each (address, label) pair tags a Cosmos address with a human-readable label, plus an entry integer for ordering.

Column Type Notes
address varchar Cosmos address
label text Tag name
entry bigint Sort/insert order within label

Primary key: (address, label); reverse-lookup index (label, entry) (table-address-tag-idx-label-entry).

structs.setting

Live tunables — chain economy and gameplay constants exposed unauthenticated through api/webapp/setting.md.

Column Type Notes
name text PK Setting key
value text Setting value (string-encoded; numeric where applicable)

Seeded keys: REACTOR_RATIO, PLAYER_RESUME_CHARGE, PLANETARY_SHIELD_BASE, PLAYER_PASSIVE_DRAW, PLANET_STARTING_ORE, PLANET_STARTING_SLOTS. Treat the table as an open name/value map — keys are added over time.

structs.defusion

In-flight reactor defusion records — Alpha Matter being unbonded from a reactor.

Column Type Notes
validator_address varchar Validator operator address
delegator_address varchar Delegator account address
defusion_type varchar Defusion category
amount numeric Amount being unbonded
denom varchar Token denom
created_at / completes_at timestamptz Lifecycle timestamps

Old rows are reaped by the structs.CLEAN_DEFUSION() cron. Read endpoints live in api/webapp/defusion.md.


Aggregated Views

View Purpose
view.guild_bank Per-guild Central Bank position — minted/redeemed token balances, collateral, and outstanding supply, joined from structs.guild, the on-chain bank module, and ledger movements
view.leaderboard_guild Ranked guild scoreboard (members, ore mined, planets completed, raids launched, raids successful) for UI surfaces
view.leaderboard_player Same shape as view.leaderboard_guild but per player

Use views, not raw tables, when building leaderboard or treasury surfaces — the views absorb the seized_ore, ledger, and infusion joins so the upstream surface stays stable when underlying tables change.


Other Time-Series Tables (TimescaleDB)

Hypertable Purpose Key Columns
ledger Financial transaction log time, address, amount, action, direction, denom
stat_ore Ore value history time, object_type, object_index, value
stat_capacity Capacity history Same pattern
stat_fuel Fuel history Same pattern
stat_load Load history Same pattern
stat_power Power history Same pattern
stat_struct_health Struct health over time time, object_index, value
stat_struct_status Struct status over time Same pattern
stat_structs_load structsLoad over time Same pattern

Ledger Action Types

genesis, received, sent, migrated, infused, defusion_started, defusion_cancelled, defusion_completed, mined, refined, seized, forfeited, minted, burned, diversion_started, diversion_completed


Signer Schema (Transaction Signing Agent)

The TSA (Transaction Signing Agent, playstructs/structs-tsa) manages a pool of signing accounts. Services insert rows into signer.tx with status='pending'. TSA claims them, signs, and broadcasts.

Table Key Columns Notes
signer.role id, player_id, guild_id, status Status: stub, generating, pending, ready
signer.account id, role_id, address, status Status: stub, generating, pending, available, signing
signer.tx id, module, command, args (JSONB), status Status: pending, claimed, broadcast, error. 100+ command types as of v0.16.0.

signer.signer_tx_type (UGC enum values)

Seven enum values cover the UGC chain message types:

Enum Value Wraps
guild-update-name MsgGuildUpdateName
guild-update-pfp MsgGuildUpdatePfp
player-update-name MsgPlayerUpdateName
player-update-pfp MsgPlayerUpdatePfp
substation-update-name MsgSubstationUpdateName
substation-update-pfp MsgSubstationUpdatePfp
planet-update-name MsgPlanetUpdateName

signer.tx_* wrappers

The signing layer ships 12 PL/pgSQL wrapper functions that queue UGC updates into signer.tx. They split into two groups by permission preflight only — both groups ultimately broadcast the same chain messages (MsgPlayerUpdateName, MsgPlayerUpdatePfp, MsgPlanetUpdateName, MsgSubstationUpdateName, MsgSubstationUpdatePfp):

There is no MsgGuildModerate* chain message — moderation is the same Msg*Update* message, gated by the actor’s PermGuildUGCUpdate on the owner’s guild. The chain emits a ugc_moderated event whenever the actor differs from the target object’s owner.

signer.UPDATE_PENDING_ACCOUNT defaults to PermAll = 33554431 (bits 0..24) so newly provisioned signer addresses receive every permission, including PermGuildUGCUpdate, by default.

PLAYER_PENDING_JOIN_PROXY (v0.16.0)

The PLAYER_PENDING_JOIN_PROXY trigger was modified to thread username and pfp from the pending row through to signer.CREATE_TRANSACTION for the guild-membership-join-proxy command, packaged into the ugc JSONB argument. The webapp’s signup flow becomes:

webapp signup
  -> structs.player_pending row (with username, pfp)
  -> PLAYER_PENDING_JOIN_PROXY trigger
  -> signer.tx (command=guild-membership-join-proxy, args.ugc={username, pfp, ...})
  -> TSA claims, signs MsgGuildMembershipJoinProxy with playerName/playerPfp
  -> chain validates name/pfp, creates player, sync-state writes username/pfp to structs.player

ID Format Reference

All game object IDs follow {type_prefix}-{index}. See entity-relationships.md for the complete type code table.

Prefix Object Type Example
0- Guild 0-1
1- Player 1-142
2- Planet 2-105
3- Reactor 3-1
4- Substation 4-5
5- Struct 5-1165
6- Allocation 6-10
9- Fleet 9-142
10- Provider 10-2
11- Agreement 11-1

See Also