Skip to main content

ic-7zue: GH#8: Tier-2 Spec: World State Data Model

Snapshot: 2026-03-30T08:40:31Z

FieldValue
Statusopen
Assignee(unassigned)
Priority2
Labelsatlas
Created bygithub-bridge
Created2026-03-28T19:00:09Z
Updated2026-03-28T19:00:09Z

Description

GitHub issue: b4arena/spellkave#8 URL: https://github.com/b4arena/spellkave/issues/8

Context

The world state data model defines what exists in Spellkave and how it's stored. Following the SpacetimeDB + BitCraft architecture (#3), this means: component tables keyed by entity_id, with each aspect of an entity in its own table.

BitCraft uses ~150 component tables for its MMORPG. Spellkave needs a different set — D&D-style worlds emphasize social relationships, faction politics, reputation, memory, and narrative consequence over crafting/terrain. This is where FoundryVTT's D&D 5e data model is the primary reference.

What Atlas Should Deliver

A Tier-2 specification defining:

1. Entity Primitives

What kinds of entities exist and how they're identified:

  • Characters (human-played and AI-inhabited)
  • Locations (towns, dungeons, wilderness, buildings)
  • Items (equipment, treasures, consumables)
  • Factions (organizations, guilds, alliances)
  • Events (historical occurrences — singular, irreversible per PRD)

2. Component Tables (Phase 0 minimum)

For each entity type, the state tables needed. Inspired by BitCraft's pattern (entity_id as shared key across tables):

Character components (reference: FoundryVTT dnd5e character.mjs):

  • AbilityScoreState — STR, DEX, CON, INT, WIS, CHA
  • HealthState — HP, max HP, temp HP, death saves
  • LocationState — position in the world
  • FactionMembershipState — which factions, standing, role
  • RelationshipState — entity-to-entity relationships (ally, enemy, debtor, etc.)
  • MemoryState — what this character knows/remembers (critical for AI agents)
  • ReputationState — how this character is perceived by others
  • InventoryState — what they carry

Location components:

  • LocationDescriptionState — terrain, features, dangers
  • LocationPopulationState — who's here now
  • LocationOwnershipState — who controls this place

Faction components:

  • FactionState — name, goals, resources, territory
  • FactionRelationshipState — faction-to-faction standings

Event log:

  • WorldEventLog — timestamped, tagged, irreversible events (the "world memory")

3. Spatial Indexing

How location-based queries work. BitCraft uses chunk_index btree. Spellkave's world may be more abstract (rooms/areas vs. hex grid) — define the approach.

4. Static Data

Game constants loaded from CSV/config (à la BitCraft's static_data/):

  • Ability score names, skill mappings → Rust enums
  • Damage types, conditions, proficiency levels
  • Spell tables, monster stat blocks
  • Reference: dnd5e config.mjs

5. Data Lifecycle

How old data is archived to manage RAM ceiling:

  • Event log compaction strategy
  • Memory summarization for AI agents
  • Archive thresholds

Key Reference Material

BitCraft (architecture patterns)

FoundryVTT D&D 5e (data model reference — steal the schemas)

  • character.mjsthe gold file: ability scores, skills, HP, levels, exhaustion
  • npc.mjs — NPC-specific: CR, legendary actions, lair
  • item/ directory — weapon, spell, feature, equipment schemas
  • activity/ directory — action types: attack, save, check, damage, heal
  • shared/ directory — composable field templates (movement, senses, currency)
  • config.mjs — all D&D 5e constants → translate to Rust enums

FoundryVTT system abstraction (pluggability proof)

Dependencies

  • Depends on #3 (SpacetimeDB architecture)
  • Depends on #4 (Temporal Pacing) — time model affects state storage
  • Feeds into #6 (Rules Engine) — the rules operate on these tables
  • Feeds into #7 (Phase 0 Minimal Module) — the minimum table set

Acceptance Criteria

  • Component tables defined with Rust struct pseudocode and SpacetimeDB annotations
  • Entity-component relationship pattern documented (shared entity_id)
  • Phase 0 minimum table set clearly identified (vs. "later" tables)
  • Static data strategy defined (CSV loading, Rust enums)
  • Data lifecycle / archival strategy for RAM management
  • Spatial indexing approach decided
  • Committed to spellkave repo

Conversation

github-bridgeMar 28, 08:20 PMsystem
[GH @durandom] ## Addendum: BitCraft Entity-Component Pattern (from code exploration) We read through BitCraft's actual source code (`BitCraftPublic/BitCraftServer/packages/game/src/`). Key patterns directly transferable to Spellkave's data model: ### Entity-Component via Tables An entity is a shared `u64` across multiple tables. BitCraft generates entity IDs with a region prefix for sharding: ```rust pub fn create_entity(ctx: &ReducerContext) -> u64 { let mut globals = ctx.db.globals().version().find(&0).unwrap(); globals.entity_pk_counter += 1; let pk = globals.entity_pk_counter; let pk = pk | ((globals.region_index as u64) << 56); // region-scoped IDs ctx.db.globals().version().update(globals); pk } ``` ### Concrete Table Examples from BitCraft (adapt for D&D) **Spatial state** — with BTree indexes for chunk-based queries: ```rust #[spacetimedb::table(name = mobile_entity_state, public, index(name = chunk_index, btree(columns = [chunk_index])))] #[repr(C)] pub struct MobileEntityState { #[primary_key] pub entity_id: u64, pub chunk_index: u64, pub location_x: i32, pub location_z: i32, pub destination_x: i32, pub destination_z: i32, pub is_walking: bool, } const _: () = assert!(size_of::<MobileEntityState>() == 48); ``` Note the `#[repr(C)]` + `const _: () = assert!(size_of::<...>() == N)` pattern — BitCraft uses this for performance-critical tables to enable SpacetimeDB's zero-copy serialization fast-path. **Health state:** ```rust #[spacetimedb::table(name = health_state, public)] pub struct HealthState { #[primary_key] pub entity_id: u64, pub health: f32, pub last_health_decrease_timestamp: Timestamp, pub died_timestamp: i32, } ``` **NPC state** — with `next_action_timestamp` for AI tick scheduling and `previous_buildings` for behavioral memory: ```rust #[spacetimedb::table(name = npc_state, public)] pub struct NpcState { #[primary_key] pub entity_id: u64, pub npc_type: NpcType, pub building_entity_id: u64, pub next_action_timestamp: Timestamp, pub previous_buildings: Vec<u64>, // basic memory pub traveling: bool, } ``` **Enemy state** — with herd grouping and status enum state machine: ```rust pub enum EnemyStatus { Inactive, Idle, ReturningToIdle, Evasive, Investigating, Fighting, Retreating, } #[spacetimedb::table(name = enemy_state, public, index(name = herd_entity_id, btree(columns = [herd_entity_id])))] pub struct EnemyState { #[primary_key] pub entity_id: u64, pub herd_entity_id: u64, pub status: EnemyStatus, pub enemy_type: EnemyType, } ``` **Singleton config** — `version = 0` pattern for global state: ```rust #[spacetimedb::table(name = config, public)] pub struct Config { #[primary_key] pub version: i32, // always 0 — single row pub env: String, pub agents_enabled: bool, } ``` ### Key Differences: Spellkave vs BitCraft Data Model BitCraft focuses on: spatial state, crafting, resources, terrain, building. ~150 tables. Spellkave needs tables BitCraft doesn't have: - **RelationshipState** (entity-to-entity: ally, enemy, debtor, romantic) - **FactionMembershipState** (which faction, standing, role) - **MemoryState** (what the character knows — critical for AI agents, grows over time) - **ReputationState** (how others perceive this character) - **WorldEventLog** (singular irreversible events — the "world memory") - **AbilityScoreState** (D&D stats: STR, DEX, CON, INT, WIS, CHA) ### FoundryVTT D&D 5e → Rust Struct Translation Guide The FoundryVTT dnd5e system has battle-tested schemas. Key files for transliteration: - [`character.mjs`](https://github.com/foundryvtt/dnd5e/blob/master/module/data/actor/character.mjs) → `CharacterState` struct (ability scores, skills, HP, classes, levels) - [`config.mjs`](https://github.com/foundryvtt/dnd5e/blob/master/module/config.mjs) → Rust enums for all D&D constants (ability names, skill→ability mappings, damage types, conditions) - [`weapon.mjs`](https://github.com/foundryvtt/dnd5e/blob/master/module/data/item/weapon.mjs) → `WeaponState` (damage, properties, range) - [`spell.mjs`](https://github.com/foundryvtt/dnd5e/blob/master/module/data/item/spell.mjs) → `SpellState` (level, school, components) - [`activity/`](https://github.com/foundryvtt/dnd5e/tree/master/module/data/activity) → reducer function signatures (attack, save, check, damage, heal) ### Data Lifecycle for RAM Management At scale, memories and events accumulate. Strategy: - **Memory compaction**: Summarize old memories into higher-level abstractions ("I traded with dwarves for months" instead of 200 individual trade memories) - **Event log rotation**: Archive events older than N days to DuckDB analytics sidecar - **Relationship pruning**: Weak relationships that haven't been reinforced decay