From b1356c11576cb4b99545f6feea4b8b08c9c49ec3 Mon Sep 17 00:00:00 2001 From: "Z. Cliffe Schreuders" Date: Thu, 20 Nov 2025 15:37:37 +0000 Subject: [PATCH] docs: Add simplified Rails Engine migration approach RECOMMENDED APPROACH: 12-14 weeks instead of 22 weeks KEY SIMPLIFICATIONS: - Use JSON storage for game state (already in that format) - 3 database tables instead of 10+ - game_instances (with player_state JSONB) - scenarios (with scenario_data JSONB) - npc_scripts (Ink JSON) - 6 API endpoints instead of 15+ - Bootstrap game - Load room (when unlocked) - Attempt unlock - Update inventory - Load NPC script (on encounter) - Sync state (periodic) VALIDATION STRATEGY: - Validate unlock attempts (server has solutions) - Validate room/object access (check unlocked state) - Validate inventory changes (check item in unlocked location) - NPCs: Load Ink scripts on encounter, run conversations 100% client-side - NPC door unlocks: Simple check (encountered NPC + scenario permission) WHAT WE DON'T TRACK: - Every event (client-side only) - Every conversation turn (no sync needed) - Every minigame action (only result matters) - Complex NPC permissions (simple rule: encountered = trusted) BENEFITS: - Faster development (12-14 weeks vs 22 weeks) - Easier maintenance (JSON matches existing format) - Better performance (fewer queries, JSONB indexing) - More flexible (easy to modify game state structure) - Simpler logic (clear validation rules) Updated README_UPDATED.md to recommend simplified approach first. Complex approach documentation retained for reference. --- .../rails-engine-migration/README_UPDATED.md | 72 ++- .../SIMPLIFIED_APPROACH.md | 543 ++++++++++++++++++ 2 files changed, 594 insertions(+), 21 deletions(-) create mode 100644 planning_notes/rails-engine-migration/SIMPLIFIED_APPROACH.md diff --git a/planning_notes/rails-engine-migration/README_UPDATED.md b/planning_notes/rails-engine-migration/README_UPDATED.md index dc4d00f..584bd65 100644 --- a/planning_notes/rails-engine-migration/README_UPDATED.md +++ b/planning_notes/rails-engine-migration/README_UPDATED.md @@ -12,26 +12,36 @@ This directory contains comprehensive plans for migrating BreakEscape from a sta ## Quick Start -### 📋 Start Here +### 🚀 **RECOMMENDED: Simplified Approach** -1. **[UPDATED_MIGRATION_STATUS.md](./UPDATED_MIGRATION_STATUS.md)** ⭐ **READ THIS FIRST** +1. **[SIMPLIFIED_APPROACH.md](./SIMPLIFIED_APPROACH.md)** ⭐ **START HERE** + - **12-14 weeks** instead of 22 weeks + - **3 database tables** instead of 10+ + - **6 API endpoints** instead of 15+ + - JSON-based storage (matches existing format) + - Simple validation rules + - **This is the recommended implementation path** + +### 📚 Alternative: Full Analysis (Original Complex Approach) + +2. **[UPDATED_MIGRATION_STATUS.md](./UPDATED_MIGRATION_STATUS.md)** - Comprehensive status of what's changed since original plans - What's been implemented (NPCs, Events, Game State) - What still needs to be done (server-side sync) - - Updated timeline (22 weeks vs 18 weeks) - - Risk assessment and success metrics + - Timeline: 22 weeks + - ⚠️ More complex than needed - see SIMPLIFIED_APPROACH.md instead -2. **[progress/CLIENT_SERVER_SEPARATION_PLAN.md](./progress/CLIENT_SERVER_SEPARATION_PLAN.md)** ⭐ **READ THIS SECOND** +3. **[progress/CLIENT_SERVER_SEPARATION_PLAN.md](./progress/CLIENT_SERVER_SEPARATION_PLAN.md)** - System-by-system separation strategy - - Updated with NPC, Event, and Game State systems - Detailed implementation examples - - Migration checklist with 9 phases (was 7) + - Migration checklist with 9 phases + - ⚠️ Over-engineered - see SIMPLIFIED_APPROACH.md instead -3. **[progress/RAILS_ENGINE_MIGRATION_PLAN.md](./progress/RAILS_ENGINE_MIGRATION_PLAN.md)** +4. **[progress/RAILS_ENGINE_MIGRATION_PLAN.md](./progress/RAILS_ENGINE_MIGRATION_PLAN.md)** - Complete Rails Engine creation guide - - Database schema (needs updates for new systems) - - Phase-by-phase implementation (18 week plan) - - Deployment checklist + - Database schema for complex approach + - Phase-by-phase implementation + - ⚠️ Use simplified schema from SIMPLIFIED_APPROACH.md instead --- @@ -122,7 +132,9 @@ This directory contains comprehensive plans for migrating BreakEscape from a sta ## Updated Migration Timeline -### Total Duration: 22 weeks (~5.5 months) +### ⚠️ NOTE: This is the complex approach timeline. See SIMPLIFIED_APPROACH.md for 12-14 week alternative. + +### Total Duration: 22 weeks (~5.5 months) - COMPLEX APPROACH **Breakdown:** - Weeks 1-4: Server Infrastructure (Rails Engine, Database, Models) @@ -366,18 +378,36 @@ end ## Conclusion -The codebase has matured significantly with production-ready implementations of NPCs, events, and game state. The original migration plans are fundamentally sound but need: +The codebase has matured significantly with production-ready implementations of NPCs, events, and game state. -- ✅ 4 additional weeks for new systems -- ✅ Updated database schema -- ✅ Additional API endpoints -- ✅ More comprehensive testing +### ⭐ RECOMMENDED: Simplified Approach -**The migration is READY TO BEGIN** with these updated plans. +**See SIMPLIFIED_APPROACH.md** for the recommended implementation: +- **12-14 weeks** (vs 22 weeks) +- **3 tables** (vs 10+) +- **6 endpoints** (vs 15+) +- JSON-based storage +- Simple validation rules +- Much easier to maintain -**Estimated Completion:** 22 weeks from start -**Confidence Level:** High -**Risk Level:** Medium (manageable with incremental approach) +### Alternative: Complex Approach (This Document) + +The detailed plans in this document are fundamentally sound but over-engineered: +- ⚠️ 22 weeks timeline +- ⚠️ 10+ database tables +- ⚠️ 15+ API endpoints +- ⚠️ Complex state management + +**We recommend starting with the simplified approach.** + +**Estimated Completion:** +- Simplified: 12-14 weeks +- Complex: 22 weeks + +**Confidence Level:** High (both approaches) +**Risk Level:** +- Simplified: Low +- Complex: Medium --- diff --git a/planning_notes/rails-engine-migration/SIMPLIFIED_APPROACH.md b/planning_notes/rails-engine-migration/SIMPLIFIED_APPROACH.md new file mode 100644 index 0000000..3d7c16a --- /dev/null +++ b/planning_notes/rails-engine-migration/SIMPLIFIED_APPROACH.md @@ -0,0 +1,543 @@ +# Simplified Rails Engine Migration Approach +## Last Updated: 2025-11-20 + +## Core Philosophy + +**Keep it simple:** Use JSON storage for game state (it's already in that format), validate only what matters, and minimize server round-trips. + +--- + +## What We Actually Need to Track + +### 1. **Scenario JSON (Filtered for Client)** +**Server stores:** Complete scenario with solutions +**Client receives:** Filtered JSON with: +- Room layouts (connections, types) +- Objects visible but lock requirements hidden +- NPCs present but Ink scripts loaded on-demand +- No PINs, passwords, key IDs, or container contents + +### 2. **Player Current State (Simple JSON)** +```json +{ + "currentRoom": "room_reception", + "position": { "x": 100, "y": 200 }, + "unlockedRooms": ["room_reception", "room_office"], + "unlockedObjects": ["desk_drawer_123", "safe_456"], + "inventory": [ + { "type": "key", "name": "Office Key", "key_id": "office_key_1" }, + { "type": "lockpick", "name": "Lockpick Set" } + ], + "encounteredNPCs": ["security_guard", "receptionist"], + "globalVariables": { + "alarm_triggered": false, + "player_favor": 5 + } +} +``` + +That's it! One JSON blob per player per scenario. + +### 3. **NPC Ink Scripts (Lazy Loaded)** +- When player encounters NPC → load Ink script +- All conversation happens client-side +- Only validate if NPC grants door unlock (simple check: has player encountered this NPC?) + +--- + +## Simplified Database Schema + +### One Main Table: `game_instances` + +```ruby +create_table :game_instances do |t| + t.references :user, null: false + t.references :scenario, null: false + + # Game state as JSON + t.jsonb :player_state, default: { + currentRoom: 'room_reception', + position: { x: 0, y: 0 }, + unlockedRooms: [], + unlockedObjects: [], + inventory: [], + encounteredNPCs: [], + globalVariables: {} + } + + # Metadata + t.string :status, default: 'in_progress' # in_progress, completed, abandoned + t.datetime :started_at + t.datetime :completed_at + t.integer :score, default: 0 + + t.timestamps + + t.index [:user_id, :scenario_id], unique: true + t.index :player_state, using: :gin # For JSON queries +end + +create_table :scenarios do |t| + t.string :name, null: false + t.text :description + t.jsonb :scenario_data # Complete scenario JSON (with solutions) + t.boolean :published, default: false + t.timestamps +end + +create_table :npc_scripts do |t| + t.references :scenario, null: false + t.string :npc_id, null: false + t.text :ink_script # Ink JSON + t.timestamps + + t.index [:scenario_id, :npc_id], unique: true +end +``` + +**That's it! 3 tables instead of 10+** + +--- + +## Simplified API Endpoints + +### 1. Bootstrap Game +``` +GET /api/games/:game_id/bootstrap + +Response: +{ + "startRoom": "room_reception", + "scenarioName": "CEO Heist", + "playerState": { currentRoom, position, inventory, ... }, + "roomLayout": { + // Just room IDs and connections, no solutions + "room_reception": { + "connections": { "north": "room_office" }, + "locked": false + }, + "room_office": { + "connections": { "south": "room_reception", "north": "room_ceo" }, + "locked": true // But no lockType or requires! + } + } +} +``` + +### 2. Load Room (When Unlocked) +``` +GET /api/games/:game_id/rooms/:room_id + +Server checks: Is room in playerState.unlockedRooms? + - Yes → Return room data (objects, but still no lock solutions) + - No → 403 Forbidden + +Response: +{ + "roomId": "room_office", + "objects": [ + { + "type": "desk", + "name": "Desk", + "locked": true, // But no "requires" field + "observations": "A locked desk drawer" + } + ] +} +``` + +### 3. Attempt Unlock +``` +POST /api/games/:game_id/unlock + +Body: +{ + "targetType": "door|object", + "targetId": "room_ceo|desk_drawer_123", + "method": "key|pin|password|lockpick", + "attempt": "1234|password123|key_id_5" +} + +Server: +- Loads complete scenario JSON +- Checks if attempt matches requirement +- If valid: + - Adds to playerState.unlockedRooms or unlockedObjects + - Returns unlocked content +- If invalid: + - Returns failure message + +Response (success): +{ + "success": true, + "type": "door", + "roomData": { ... } // If door + // OR + "contents": [ ... ] // If container +} +``` + +### 4. Update Inventory +``` +POST /api/games/:game_id/inventory + +Body: +{ + "action": "add|remove", + "item": { "type": "key", "name": "Office Key", "key_id": "..." } +} + +Server validates: +- For "add": Is item in an unlocked container/room? +- Updates playerState.inventory + +Response: +{ "success": true, "inventory": [...] } +``` + +### 5. Load NPC Script (On Encounter) +``` +GET /api/games/:game_id/npcs/:npc_id/script + +Server checks: Is NPC in current room OR already in playerState.encounteredNPCs? + - Yes → Return Ink script + - No → 403 Forbidden + +Side effect: Add to playerState.encounteredNPCs + +Response: +{ + "npcId": "security_guard", + "inkScript": { ... }, // Full Ink JSON + "eventMappings": [...], + "timedMessages": [...] +} +``` + +### 6. Sync State (Periodic) +``` +PUT /api/games/:game_id/state + +Body: +{ + "currentRoom": "room_office", + "position": { "x": 150, "y": 220 }, + "globalVariables": { "alarm_triggered": false } +} + +Server: Merges into playerState (validates room is unlocked) +``` + +**That's it! 6 endpoints instead of 15+** + +--- + +## What We DON'T Track Server-Side + +### ❌ Every Event +- No event logging (unless needed for analytics later) +- NPCs listen to events client-side only + +### ❌ Every Conversation Turn +- All NPC dialogue happens client-side +- No conversation history sync needed +- No story state tracking + +### ❌ Every Minigame Action +- Minigames run 100% client-side +- Only unlock validation matters + +### ❌ Complex Permissions +- No NPCPermission table +- Simple rule: If player encountered NPC, NPC can do its thing + +--- + +## Validation Strategy (Simplified) + +### When Server Validates + +1. **Unlock Attempts** ✅ + - Check attempt against scenario JSON + - Update unlocked state + +2. **Inventory Changes** ✅ + - Verify item exists in unlocked location + - Update inventory JSON + +3. **Room Access** ✅ + - Check room in unlockedRooms + - Return filtered room data + +4. **NPC Script Loading** ✅ + - Check NPC encountered or in current room + - Return Ink script + +### When Server Doesn't Validate + +1. **Conversations** ❌ - All client-side +2. **Movement** ❌ - Trust client position +3. **Events** ❌ - Client-side only +4. **Minigame Actions** ❌ - Only result matters +5. **Global Variables** ❌ - Sync periodically, don't validate every change + +--- + +## NPC Door Unlock Simplification + +**Scenario defines:** NPC can unlock door X +```json +{ + "id": "security_guard", + "canUnlock": ["room_server"] +} +``` + +**Client requests:** NPC unlock door +``` +POST /api/games/:game_id/npc_unlock + +Body: +{ + "npcId": "security_guard", + "doorId": "room_server" +} + +Server validates: +- Is NPC in playerState.encounteredNPCs? +- Does scenario say NPC can unlock this door? +- If yes: Add to unlockedRooms, return room data +``` + +**No conversation tracking, no trust levels, no complex permissions!** + +--- + +## Migration Timeline (Simplified) + +### Total: 12-14 weeks (vs 22 weeks original) + +**Weeks 1-2:** Setup +- Create Rails Engine +- 3 database tables +- Import scenarios as JSON + +**Weeks 3-4:** Core API +- Bootstrap endpoint +- Room loading +- Unlock validation + +**Weeks 5-6:** Client Integration +- Modify loadRoom() to fetch from server +- Add unlock validation +- NPC script lazy loading + +**Weeks 7-8:** Inventory & State Sync +- Inventory API +- State sync endpoint +- Offline queue + +**Weeks 9-10:** NPC Integration +- NPC script loading +- NPC door unlock (simple validation) +- Import all Ink scripts + +**Weeks 11-12:** Testing & Polish +- Integration testing +- Performance optimization +- Security audit + +**Weeks 13-14:** Deployment +- Staging deployment +- Load testing +- Production deployment + +**Savings: 8-10 weeks** by simplifying! + +--- + +## Implementation Example: Unlock Validation + +### Server-Side (Simple) + +```ruby +class Api::UnlockController < ApplicationController + def create + game = GameInstance.find(params[:game_id]) + + # Load complete scenario (has solutions) + scenario = game.scenario.scenario_data + + target_type = params[:target_type] # 'door' or 'object' + target_id = params[:target_id] + attempt = params[:attempt] + method = params[:method] + + # Find target in scenario + target = find_target(scenario, target_type, target_id) + + # Validate attempt + is_valid = validate_attempt(target, attempt, method) + + if is_valid + # Update player state JSON + if target_type == 'door' + game.player_state['unlockedRooms'] << target_id + room_data = get_filtered_room_data(scenario, target_id) + + game.save! + render json: { success: true, roomData: room_data } + else + game.player_state['unlockedObjects'] << target_id + contents = target['contents'] || [] + + game.save! + render json: { success: true, contents: contents } + end + else + render json: { success: false, message: 'Invalid attempt' }, status: 422 + end + end + + private + + def validate_attempt(target, attempt, method) + case method + when 'key' + target['requires'] == attempt # key_id matches + when 'pin', 'password' + target['requires'] == attempt # PIN/password matches + when 'lockpick' + true # Client-side minigame passed, trust it + end + end + + def get_filtered_room_data(scenario, room_id) + room = scenario['rooms'][room_id].dup + + # Remove solutions from objects + room['objects']&.each do |obj| + obj.delete('requires') + obj.delete('contents') if obj['locked'] + end + + room + end +end +``` + +### Client-Side (Unchanged) + +```javascript +async function handleUnlock(lockable, type) { + // Show minigame or prompt (unchanged) + const attempt = await getUnlockAttempt(lockable); + + // NEW: Validate with server + const response = await fetch(`/api/games/${gameId}/unlock`, { + method: 'POST', + body: JSON.stringify({ + targetType: type, + targetId: lockable.objectId, + method: lockable.lockType, + attempt: attempt + }) + }); + + const result = await response.json(); + + if (result.success) { + // Unlock locally + unlockTarget(lockable, type); + + // If door, load room + if (type === 'door' && result.roomData) { + createRoom(result.roomData.roomId, result.roomData, position); + } + } +} +``` + +--- + +## Benefits of Simplified Approach + +### 1. **Faster Development** +- 12-14 weeks vs 22 weeks +- 3 tables vs 10+ +- 6 endpoints vs 15+ + +### 2. **Easier Maintenance** +- JSON storage matches existing format +- No ORM complexity +- Simple queries + +### 3. **Better Performance** +- Fewer database queries +- Single JSON blob vs many joins +- JSONB indexing is fast + +### 4. **Flexible** +- Easy to add new fields to JSON +- No migrations for game state changes +- Scenarios stay as JSON files + +### 5. **Simpler Logic** +- No complex permissions +- No conversation state tracking +- Clear validation rules + +--- + +## What We Give Up (And Why It's OK) + +### ❌ Conversation History Persistence +**Why OK:** NPCs work great client-side. If player refreshes, conversation resets. This is fine for a game session. + +### ❌ Detailed Event Analytics +**Why OK:** Can add later if needed. Start simple. + +### ❌ Global Variable Validation +**Why OK:** Sync periodically, rollback if server detects cheating. Don't block gameplay. + +### ❌ Fine-Grained Permissions +**Why OK:** Simple rules work: encountered NPC = trusted. Scenario defines what NPCs can do. + +--- + +## Security Model (Simplified) + +### What's Secure ✅ +- Scenario solutions never sent to client +- Unlock validation server-side +- Room/object access controlled +- NPC scripts lazy-loaded + +### What's Client-Trust ⚠️ +- Player position (low risk) +- Global variables (sync server, detect cheating) +- Minigame success (only door unlock matters) + +### What We Detect +- Player accessing unearned rooms → 403 +- Invalid unlock attempts → 422 +- Impossible inventory items → reject + +**Good enough for educational game!** + +--- + +## Conclusion + +**Original approach:** Complex, over-engineered, 22 weeks +**Simplified approach:** Pragmatic, maintainable, 12-14 weeks + +**Key insight:** We don't need to track everything. Just track: +1. What's unlocked (rooms/objects) +2. What player has (inventory) +3. Who player met (NPCs) +4. Where player is (room/position) + +Everything else can stay client-side! + +**Ready to implement this simpler approach.**