5. How relay-cache joins tables
relay-cache is a same-host process that subscribes to every
regional frontend on loopback, keeps selected tables in memory, and
serves enriched JSON/protobuf on
https://relay.bitcraftsync.app/….
You do not need it to use the WebSocket relays — it is an optional
read API that already performed the joins community tools usually want.
Ingest model
Per region, the cache opens one v2 WebSocket to
ws://127.0.0.1:<port> and loads data in phases:
-
Base query set
One additive
Subscribewith all primary tables (claims, buildings, inventories, players, crafts, storage logs, hexite resource filters, growth, …). Wait forSubscribeApplied. -
Hexite locations (additive)
From the base dump, collect hexite
resource_stateentity ids, then subscribeSELECT * FROM location_state WHERE entity_id = …per id in a second query set. Wait for Applied again. -
Stream
Mark the region
readyand apply liveTransactionUpdates into columnar stores.
Full location_state is enormous (~millions of rows per
region); the cache only keeps non-overworld interiors
(dimension != 1) plus the hexite PK follow-up.
Endpoint → tables
Each HTTP handler reads the in-memory stores and joins by entity id
(and a few derived keys). Entity ids are JSON strings for JS safety;
protobuf uses uint64.
| Endpoint | Primary tables | Join / enrichment |
|---|---|---|
| GET /claim?name= | claim_state, player_username_state, claim_tech_state, claim_tech_desc |
Name substring match; owner username; tier from learned techs. |
| GET /claim/<id> | Above + claim_local_state, claim_tile_cost |
Supplies, treasury, tiles, upkeep, location, researched tech list. |
| …/inventory | building_state, inventory_state, building_desc, building_nickname_state, location_state (interiors), dimension_network_state |
Shared storage only (no Town Banks). Group by dimension; attach entrance for interiors. |
| …/members | claim_member_state, skills, hexcoins, player_state |
Roster + roles + skills + hexcoins + login timestamps. /citizens and /hexcoins are legacy projections. |
| …/citizens | (alias of members) | Deprecated — skills projection of /members. |
| …/hexcoins | (alias of members) | Deprecated — hexcoin projection of /members. |
| …/crafts | progressive_action_state, passive_craft_state, crafting_recipe_desc, buildings, usernames |
Progress + recipe outputs; optional completed= filter. |
| GET /player?name= | player_username_state, player_state |
Substring search; region; last login / last active / signed-in. Walkthrough with printable columns: Extract player data. |
| …/inventory | Player bags via deployable_state_v2 / housing / rent links + inventory_state |
Pockets, toolbelt, wallet, bank, wagon, cache, boat, recovery, deployable — items aggregated per bag. |
| …/housing | player_housing_state, housing desc, buildings, inventories, locations |
Resolve house entity; name from username/nickname + catalog; interior storages by dimension. |
| …/skills | experience_state, skill_desc |
XP → level via vendored thresholds. |
| GET /deposits | unowned claim_state (hexite name pattern), claim_local_state, resource_state, growth_state, hexite location_state |
Parse N/E from claim name; match resource at world x/z; respawn_at from growth end timestamp. |
| GET /storage-logs | storage_log_state + building / claim / player username lookups |
Filter by storage, player, or item(+claim|player); enrich names. |
| GET /bitme/resolve?name= | player_lowercase_username_state, user_state, user_region_state, region_connection_info, world_region_name_state, signed_in_player_state (global) |
Exact-match lowercase name → entity id, identity, home region, host/module, live sign-in. Region shards fill display name / sign-in fallback. |
| GET /bitme/session/<id> | mobile_entity_state, stamina_state, active_buff_state, player_action_state, character_stats_state ⋈ character_stat_desc, resource_desc; claim via roads overlay; resource_health_state (tracked targets only) |
Bit-Me activity snapshot: position, claim, stamina+max, buffs, action lifecycle, target health, watched spawns (citric + yield chains). The GET itself registers the session (15 min TTL). |
| GET /bitme/session/<id>/resources | roads region grid tile map (one u16 per odd-r tile: 10-bit dictionary index + origin flag + 3-bit direction for every resource_state row, paving flag for paved_tile_state, water flag from the terrain seed) + player tile from mobile_entity_state |
Packed 400×400 window anchored at the player (24-byte BMR1 header + 160k u16 LE tile words, player at relative 200,200). Merged resource + paving + water view; expand indices via the region resource-dictionary (separate resource/paving namespaces). The poll counts as session activity. |
| GET /bitme/region/<region>/resource-dictionary | resource tile map dictionary ⋈ resource_desc |
10-bit tile index → resource_id + name + harvestable flag + health/despawn/respawn gamedata, so clients can expand window payloads client-side. Refetch when a window header's dict_version changes. |
Example: claim detail join
Example: hexite deposit join
HTTP API cheat sheet
Base URL: https://relay.bitcraftsync.app. Default JSON;
send Accept: application/x-protobuf for protobuf (entity
ids are uint64 there; JSON keeps them as strings).
| Method | Params | Notes |
|---|---|---|
| GET /cache-health | — | ready + per-region ready.
Internals (memory pressure, row counts) on loopback-only
127.0.0.1:8089/internal/stats. |
| GET /proto | — | List of .proto files;
/proto/relay_cache.proto downloads one. |
| GET /claim?name= | name (substring, case-insensitive) |
Array of claim summaries (tier, owner username, …). |
| GET /claim/<id> | — | Full enrichment (supplies, upkeep, techs, location). |
| …/inventory | — | Shared storage only; grouped by dimension. |
| …/members | — | Roster + roles + skills + hexcoins + last login. |
| …/citizens | — | Deprecated alias of /members (skills projection). |
| …/hexcoins | — | Deprecated alias of /members (hexcoin projection). |
| …/crafts | completed=true|false (optional) |
Omit filter → in-progress and completed. |
| GET /player?name= | name (≥2 chars recommended) |
Cross-region search. |
| GET /player/<id> | — | Plus /inventory, /housing,
/skills, /crafts?completed=. |
| GET /deposits | region=N (optional) |
Active omit respawn_at; depleted include it. |
| GET /storage-logs | Exactly one mode:storageId= orplayerId= oritemId=+claimId= oritemId=+playerId=Optional: itemType=Item|Cargo,
region=N, limit=N |
Upstream retention ~15–16 days. Newest-first when limited. |
| GET /bitme/resolve?name= | name (exact, case-insensitive) |
Player → entity id + identity + region + host/module + signed_in. Covers all 25 regions. |
| GET /bitme/session/<id> | — (entity id in path) | 1 Hz activity snapshot for the Bit-Me app: position, claim, stamina, buffs, actions, target health, watched spawns. Polling = session registration (15 min TTL). |
| GET /bitme/session/<id>/resources | — (entity id in path) | Packed 400×400 map centered on the player — binary application/octet-stream (BMR1, ~320 KB), not JSON. Merged resource + paving + water view (tile word bits: 10-bit index, origin, direction, paving, water). Poll only while the map screen is open. |
| GET /bitme/region/<region>/resource-dictionary | — | Tile-word index → resource_id + gamedata + harvestable flag. Fetch once per region; refetch when a window header's dict_version changes. |
Bit-Me mobile app API
The Bit-Me companion app is the first JSON-only consumer: it never
opens a SpacetimeDB socket. Four GET endpoints cover it —
/bitme/resolve (name → ids/region),
/bitme/session/<player_entity_id> (position, claim,
stamina, buffs, action + target health, and watched spawns such as the
Citric Giant berry bushes),
/bitme/session/<player_entity_id>/resources (packed
400×400 resource map centered on the player — all resource types, served
from the roads region's dense tile map), and
/bitme/region/<region>/resource-dictionary (tile index
→ resource_id + gamedata, for expanding the window). Polling at ~1 Hz is
the intended cadence and doubles as the session registration;
countdowns are computed client-side from bundled gamedata.
| Signal | Where it comes from |
|---|---|
| Player / region resolution | Global exact-match chain (lowercase name → entity id → identity → region → host/module), plus live sign-in presence. |
| Current claim | Player tile → roads overlay claim index (same data as /roads/region/:id/map). |
| Target health | resource_health_state retained only for targets of polled sessions — bounded, no whole-table storage. |
| Citric / respawn events | Live resource_state inserts whose id is a destroy-yield chain target or one of the three vendored Citric Giant bush ids, scoped to the player's claim / wilderness. |
| Resource map window | Dense per-region tile map (one u16 word per odd-r tile; 10-bit dictionary index + origin flag + 3-bit direction + paving flag + water flag), stamped server-side for every resource_state and paved_tile_state row — multi-hex footprints land on every occupied tile; water is seeded once from terrain elevation. |
Client walkthrough with the BMR1 resource-window format:
Bit-Me app API & BMR1/BME1 maps. Full
field-by-field reference with real payload examples:
spacetimedb-bitcraft-mirror/crates/relay-cache/BITME-API.md
in the relay repo.
Calling the public HTTP API
curl -s 'https://relay.bitcraftsync.app/claim?name=concordia' | jq .
curl -s 'https://relay.bitcraftsync.app/claim/1234567890/citizens' | jq .
curl -s 'https://relay.bitcraftsync.app/claim/1234567890/crafts?completed=false' | jq .
curl -s 'https://relay.bitcraftsync.app/deposits?region=14' | jq .
curl -s 'https://relay.bitcraftsync.app/storage-logs?storageId=1008806316593474517&limit=50' | jq .
curl -s https://relay.bitcraftsync.app/cache-health | jq .
# Bit-Me app endpoints (JSON polling; see BITME-API.md):
curl -s 'https://relay.bitcraftsync.app/bitme/resolve?name=whisper' | jq .
curl -s 'https://relay.bitcraftsync.app/bitme/session/504403158290646123' | jq .
# Bit-Me resource map window (binary BMR1, ~320 KB — NOT JSON):
curl -s 'https://relay.bitcraftsync.app/bitme/session/504403158290646123/resources' -o window.bmr1
# World-anchored variants (region derived from x/z; elevation = binary BME1,
# 134x134 packed super-hex terrain cells, ~140 KB):
curl -s 'https://relay.bitcraftsync.app/bitme/world/11170/13850/resources' -o pan.bmr1
curl -s 'https://relay.bitcraftsync.app/bitme/world/11170/13850/elevation' -o pan.bme1
# Tile words expand via the per-region dictionary (refetch on dict_version change):
curl -s 'https://relay.bitcraftsync.app/bitme/region/7/resource-dictionary' | jq .
curl -sH 'Accept: application/x-protobuf' \
'https://relay.bitcraftsync.app/player/1297036692699996362/skills' \
-o skills.pb
# Protobuf schemas:
curl -s https://relay.bitcraftsync.app/proto
curl -OJ https://relay.bitcraftsync.app/proto/relay_cache.proto