Files
BreakEscape/planning_notes/rails-engine-migration-json/03_DATABASE_SCHEMA.md
Claude a982d9e05e docs: Add complete Rails Engine migration plan (JSON-centric approach)
Comprehensive implementation plan for converting BreakEscape to a Rails Engine.

DOCUMENTATION CREATED:
- 00_OVERVIEW.md: Project aims, philosophy, decisions summary
- 01_ARCHITECTURE.md: Technical design, models, controllers, API
- 02_IMPLEMENTATION_PLAN.md: Phases 1-6 with bash/rails commands
- 02_IMPLEMENTATION_PLAN_PART2.md: Phases 7-12 with client integration
- 03_DATABASE_SCHEMA.md: 3-table JSONB schema reference
- 04_TESTING_GUIDE.md: Fixtures, tests, CI setup
- README.md: Quick start and navigation guide

KEY APPROACH:
- Simplified JSON-centric storage (3 tables vs 10+)
- JSONB for player state (one column, all game data)
- Minimal client changes (move files, add API client)
- Dual mode: Standalone + Hacktivity integration
- Session-based auth with polymorphic player
- Pundit policies for authorization
- ERB templates for scenario randomization

TIMELINE: 12-14 weeks (vs 22 weeks complex approach)

ARCHITECTURE DECISIONS:
- Static assets in public/break_escape/
- Scenarios in app/assets/scenarios/ with ERB
- .ink and .ink.json files organized by scenario
- Lazy-load NPC scripts on encounter
- Server validates unlocks, client runs dialogue
- 6 API endpoints (not 15+)

Each phase includes:
- Specific bash mv commands
- Rails generate and migrate commands
- Code examples with manual edits
- Testing steps
- Git commit points

Ready for implementation.
2025-11-20 10:28:33 +00:00

5.6 KiB

BreakEscape - Database Schema Reference

Overview

3 tables using JSONB for flexible storage


Tables

1. break_escape_scenarios

Stores scenario definitions with complete game data.

Column Type Null Default Notes
id bigint NO AUTO Primary key
name string NO - Unique identifier (e.g., 'ceo_exfil')
display_name string NO - Human-readable name
description text YES - Scenario brief
scenario_data jsonb NO - Complete scenario with solutions
published boolean NO false Visible to players
difficulty_level integer NO 1 1-5 scale
created_at timestamp NO NOW() -
updated_at timestamp NO NOW() -

Indexes:

  • name (unique)
  • published
  • scenario_data (gin)

scenario_data structure:

{
  "startRoom": "room_reception",
  "scenarioName": "CEO Exfiltration",
  "scenarioBrief": "Break into the CEO's office...",
  "rooms": {
    "room_reception": {
      "type": "reception",
      "connections": {"north": "room_office"},
      "locked": false,
      "objects": [...]
    },
    "room_office": {
      "type": "office",
      "connections": {"south": "room_reception"},
      "locked": true,
      "lockType": "password",
      "requires": "admin123",  // Server only!
      "objects": [...]
    }
  },
  "npcs": [
    {"id": "guard", "displayName": "Security Guard", ...}
  ]
}

2. break_escape_npc_scripts

Stores Ink dialogue scripts per NPC per scenario.

Column Type Null Default Notes
id bigint NO AUTO Primary key
scenario_id bigint NO - Foreign key → scenarios
npc_id string NO - NPC identifier
ink_source text YES - Original .ink file (optional)
ink_compiled text NO - Compiled .ink.json
created_at timestamp NO NOW() -
updated_at timestamp NO NOW() -

Indexes:

  • scenario_id
  • [scenario_id, npc_id] (unique)

Foreign Keys:

  • scenario_idbreak_escape_scenarios(id)

3. break_escape_game_instances

Stores player game state (one JSONB column!).

Column Type Null Default Notes
id bigint NO AUTO Primary key
player_type string NO - Polymorphic (User/DemoUser)
player_id bigint NO - Polymorphic
scenario_id bigint NO - Foreign key → scenarios
player_state jsonb NO {...} All game state here!
status string NO 'in_progress' in_progress, completed, abandoned
started_at timestamp YES - When game started
completed_at timestamp YES - When game finished
score integer NO 0 Final score
health integer NO 100 Current health
created_at timestamp NO NOW() -
updated_at timestamp NO NOW() -

Indexes:

  • [player_type, player_id, scenario_id] (unique)
  • player_state (gin)
  • status

Foreign Keys:

  • scenario_idbreak_escape_scenarios(id)

player_state structure:

{
  "currentRoom": "room_office",
  "position": {"x": 150, "y": 200},
  "unlockedRooms": ["room_reception", "room_office"],
  "unlockedObjects": ["desk_drawer_123"],
  "inventory": [
    {
      "type": "key",
      "name": "Office Key",
      "key_id": "office_key_1",
      "takeable": true
    }
  ],
  "encounteredNPCs": ["security_guard"],
  "globalVariables": {
    "alarm_triggered": false,
    "player_favor": 5
  }
}

4. break_escape_demo_users (Standalone Mode Only)

Simple user model for standalone/testing.

Column Type Null Default Notes
id bigint NO AUTO Primary key
handle string NO - Username
role string NO 'user' admin, pro, user
created_at timestamp NO NOW() -
updated_at timestamp NO NOW() -

Indexes:

  • handle (unique)

Relationships

Scenario (1) ──→ (∞) GameInstance
Scenario (1) ──→ (∞) NpcScript

GameInstance (∞) ←── (1) Player [Polymorphic]
  - User (Hacktivity)
  - DemoUser (Standalone)

Migration Commands

# Generate migrations
rails generate migration CreateBreakEscapeScenarios
rails generate migration CreateBreakEscapeNpcScripts
rails generate migration CreateBreakEscapeGameInstances
rails generate migration CreateBreakEscapeDemoUsers

# Run migrations
rails db:migrate

# Import scenarios
rails db:seed

Querying Examples

# Find player's active games
GameInstance.where(player: current_user, status: 'in_progress')

# Get unlocked rooms for a game
game.player_state['unlockedRooms']

# Check if room is unlocked
game.room_unlocked?('room_office')

# Unlock a room
game.unlock_room!('room_office')

# Add inventory item
game.add_inventory_item!({'type' => 'key', 'name' => 'Office Key'})

# Query scenarios
Scenario.published.where("scenario_data->>'startRoom' = ?", 'room_reception')

# Complex JSONB queries
GameInstance.where("player_state @> ?", {unlockedRooms: ['room_ceo']}.to_json)

Benefits of JSONB Approach

  1. Flexible Schema - Add new fields without migrations
  2. Fast Queries - GIN indexes on JSONB
  3. Matches Game Data - Already in JSON format
  4. Simple - One table vs many joins
  5. Atomic Updates - Update entire state in one transaction

Performance Considerations

  • GIN indexes on all JSONB columns
  • Unique index on [player, scenario] prevents duplicates
  • player_state updates are atomic (PostgreSQL JSONB)
  • Scenarios cached in memory after first load