- Added server-side validation for inventory actions, ensuring items are validated before being added to the player's inventory. - Updated client-side `addToInventory()` function to include server validation and error handling for inventory actions. - Implemented container loading logic to fetch contents from the server, handling locked containers appropriately. - Enhanced player state initialization to ensure starting items are correctly added to the inventory. - Clarified NPC `itemsHeld` structure and updated validation logic to match the expected item format. - Improved error handling for room access and container interactions, including transaction safety for state mutations. - Introduced new endpoints for scenario mapping and bulk inventory synchronization. - Added performance optimizations, including caching filtered room data and implementing JSON schema validation for item structures. - Established a detailed implementation plan with phased priorities and testing gaps identified for future work.
40 KiB
Implementation Plan Review: Client Action Validation & Data Filtering
Date: November 21, 2025
Reviewer: GitHub Copilot
Status: Recommendations for Success
Confidence Level: High
Executive Summary
The implementation plan is well-structured and security-focused, with clear phasing and good separation of concerns. However, there are 9 critical areas where improvements would significantly increase chances of successful execution and reduce technical debt. This review provides actionable recommendations organized by priority and implementation impact.
1. CRITICAL ISSUES (Must Fix Before Starting)
Issue 1.1: Race Condition in Player State Initialization
Location: Phase 1.2, Method initialize_player_state!
Problem:
The current approach initializes player state on before_create callback, but:
- Multiple requests might create games simultaneously
initializedAtcheck only prevents re-initialization within Rails, not across requests- No transaction-level guarantee that state isn't initialized twice
Impact:
- Duplicate inventory items
- Starting items collected twice on concurrent requests
- Inventory inconsistency
Recommendation:
# Instead of initializedAt flag, use database uniqueness constraint
# and idempotent initialization pattern:
def initialize_player_state!
# Use safe navigation and merge pattern
base_state = {
'currentRoom' => scenario_data['startRoom'],
'unlockedRooms' => [scenario_data['startRoom']].uniq,
'unlockedObjects' => [],
'inventory' => scenario_data['startItemsInInventory']&.dup || [],
'encounteredNPCs' => [],
'globalVariables' => {},
'biometricSamples' => [],
'biometricUnlocks' => [],
'bluetoothDevices' => [],
'notes' => [],
'health' => 100,
'initializedAt' => Time.current.iso8601
}
# Only set if completely empty (first time)
self.player_state = base_state if player_state.blank? || player_state.empty?
end
Action Items:
- Create migration to backfill player_state for existing games
- Add database index on
(id, player_state)for quick state checks - Test concurrent game creation with
ABtesting tool - Document idempotent initialization in code comments
Issue 1.2: No Validation of Scenario Data Integrity
Location: Phase 1, Entire initialization flow
Problem:
- Plan assumes
scenario_data['startRoom']exists, but doesn't validate - Plan assumes
startItemsInInventoryis well-formed array, but doesn't validate - No checks for circular room connections
- No checks for orphaned items (items in rooms that don't exist)
Impact:
- Silent failures if scenario JSON is malformed
- Players stuck in non-existent rooms
- Items unreachable
Recommendation: Add validation layer before initialization:
class Game < ApplicationRecord
validates :scenario_data, presence: true
validate :scenario_data_integrity
private
def scenario_data_integrity
return if scenario_data.blank?
errors.add(:scenario_data, 'missing startRoom') unless scenario_data['startRoom'].present?
errors.add(:scenario_data, 'startRoom not found') unless scenario_data['rooms']&.key?(scenario_data['startRoom'])
# Validate startItemsInInventory structure
if scenario_data['startItemsInInventory'].present?
unless scenario_data['startItemsInInventory'].is_a?(Array)
errors.add(:scenario_data, 'startItemsInInventory must be an array')
end
end
# Validate all rooms exist (connection integrity)
scenario_data['rooms']&.each do |room_id, room_data|
next unless room_data['connections'].present?
room_data['connections'].values.flatten.each do |connected_room|
unless scenario_data['rooms'].key?(connected_room)
errors.add(:scenario_data, "room #{room_id} connects to non-existent room #{connected_room}")
end
end
end
end
end
Action Items:
- Add scenario integrity validator before game creation
- Create integration tests with malformed scenarios
- Document expected scenario format as comments in validator
- Add error messages to help developers debug scenario issues
Issue 1.3: Incomplete Specification of scenario_data Structure
Location: Phase 2-4, All filtering methods
Problem:
- Plan doesn't define what fields exist at each level (rooms, objects, NPCs, items)
- Example shows
lockType,locked,requires, but no complete list - Plan mentions
itemsHeldon NPCs but structure unclear - No guidance on optional vs required fields
Impact:
- Developers guess at field names
- Filtering logic breaks if fields are named differently
- Tests fail because mock data doesn't match real data structure
Recommendation: Create a formal scenario schema document before implementation:
# lib/break_escape/scenario_schema.rb
module BreakEscape
SCENARIO_SCHEMA = {
rooms: {
room_id: {
type: String, # e.g., "room_office"
connections: Hash, # { "north" => "office_2", "south" => "reception" }
locked: Boolean, # optional
lockType: String, # "key", "password", "pin", "biometric", optional
requires: String, # HIDDEN FROM CLIENT - answer/key_id
objects: Array, # Array of object definitions
npcs: Array # Array of NPC definitions
},
objects: {
id: String, # unique within room
name: String, # display name
type: String, # "door", "container", "key", "notes", etc.
takeable: Boolean,
interactable: Boolean, # optional
locked: Boolean, # optional
lockType: String, # optional
requires: String, # HIDDEN - answer/solution
observations: String, # description shown to player
contents: Array, # HIDDEN UNTIL UNLOCKED - nested items
itemsHeld: Array # for NPC objects
},
npcs: {
id: String, # unique NPC identifier
displayName: String, # shown to player
storyPath: String, # path to .ink or .json
npcType: String, # optional: "phone", "person", etc.
itemsHeld: Array # items NPC holds { type, id, name, ... }
}
}
}
end
Action Items:
- Create
lib/break_escape/scenario_schema.rbwith complete field definitions - Add JSON Schema validator to verify scenarios match spec
- Document which fields are client-visible in inline comments
- Generate test fixture scenarios from schema definition
2. DESIGN ISSUES (High Impact on Success)
Issue 2.1: filter_contents_field Method is Inefficient
Location: Phase 2.3, controller filtering
Problem:
- Recursively walks entire object tree for each filter operation
- Modifies objects in-place (using
.delete()), which mutates original in memory - No memoization of filtered results
- Same room data might be filtered multiple times per request
Impact:
- Performance degrades with large scenarios (many rooms/objects)
- Memory pressure from deep copies
- Defensive copying needed to prevent mutations
Recommendation: Create dedicated filtering service class:
# app/services/break_escape/data_filter_service.rb
module BreakEscape
class DataFilterService
# Hide sensitive fields during room data exposure
SENSITIVE_FIELDS = ['requires', 'contents'].freeze
def initialize(scenario_data)
@scenario_data = scenario_data
@filter_cache = {}
end
# Filter object recursively, returning new object (immutable)
def filter_object(obj, hide_fields: SENSITIVE_FIELDS)
cache_key = "#{obj.object_id}_#{hide_fields.join(',')}"
return @filter_cache[cache_key] if @filter_cache.key?(cache_key)
result = case obj
when Hash
obj.each_with_object({}) do |(key, value), acc|
next if hide_fields.include?(key)
acc[key] = filter_object(value, hide_fields: hide_fields)
end
when Array
obj.map { |item| filter_object(item, hide_fields: hide_fields) }
else
obj
end
@filter_cache[cache_key] = result
end
# Filter room data (remove 'requires' but keep 'lockType', 'locked')
def filtered_room_data(room_id)
room = @scenario_data['rooms']&.[](room_id)
return nil unless room
filter_object(room, hide_fields: ['requires', 'contents'])
end
# Filter contents of a specific container (remove nested 'requires')
def filtered_container_contents(container_id)
container = find_container_in_scenario(container_id)
return nil unless container
(container['contents'] || []).map do |item|
filter_object(item, hide_fields: ['requires', 'contents'])
end
end
end
end
Action Items:
- Create
app/services/break_escape/data_filter_service.rb - Add caching strategy for repeated filters
- Add unit tests for filter performance
- Update controller to use service instead of inline filtering
- Add instrumentation to measure filter overhead
Issue 2.2: Validation Helper Methods Are Complex and Scattered
Location: Phase 4, Inventory validation, multiple helper methods
Problem:
find_item_in_scenario,find_item_container,find_item_room,find_npc_holding_itemall do similar traversals- Each method might have different traversal logic (depth-first vs breadth-first)
- No shared traversal pattern
- Code duplication increases bug surface area
Impact:
- Bugs in one finder don't get fixed in others
- Hard to maintain consistency
- Performance suffers from repeated traversals
Recommendation: Create a scenario traversal service:
# app/services/break_escape/scenario_traverser.rb
module BreakEscape
class ScenarioTraverser
def initialize(scenario_data)
@scenario_data = scenario_data
@index = {} # Cache of item/container/NPC lookups
build_index
end
# Find item by type and ID anywhere in scenario
def find_item(item_type, item_id)
@index[:items]["#{item_type}:#{item_id}"]
end
# Find which container holds an item
def find_containing_container(item_type, item_id)
item = find_item(item_type, item_id)
return nil unless item
@index[:container_map]["#{item_type}:#{item_id}"]
end
# Find which room contains an item
def find_containing_room(item_type, item_id)
item = find_item(item_type, item_id)
return nil unless item
@index[:room_map]["#{item_type}:#{item_id}"]
end
# Find NPC holding an item
def find_npc_holding(item_type, item_id)
@index[:npc_holdings]["#{item_type}:#{item_id}"]
end
# Find container by ID
def find_container(container_id)
@index[:containers][container_id]
end
# Find NPC by ID
def find_npc(npc_id)
@index[:npcs][npc_id]
end
private
def build_index
@index = {
items: {}, # "type:id" => item_data
containers: {}, # "container_id" => container_data
npcs: {}, # "npc_id" => npc_data
container_map: {}, # "type:id" => container_id (what container holds this item)
room_map: {}, # "type:id" => room_id (what room contains this item)
npc_holdings: {} # "type:id" => npc_id (what NPC holds this)
}
@scenario_data['rooms']&.each do |room_id, room_data|
index_room_objects(room_id, room_data['objects'], container_id: nil)
index_npcs(room_id, room_data['npcs'])
end
end
def index_room_objects(room_id, objects, container_id: nil)
return unless objects
objects.each do |obj|
# Index the object itself
key = "#{obj['type']}:#{obj['id']}"
@index[:items][key] = obj
@index[:room_map][key] = room_id
# If inside a container, record that relationship
@index[:container_map][key] = container_id if container_id
# If this object is a container, index it
if obj['contents'].present?
@index[:containers][obj['id']] = obj
# Recursively index nested contents
index_room_objects(room_id, obj['contents'], container_id: obj['id'])
end
end
end
def index_npcs(room_id, npcs)
return unless npcs
npcs.each do |npc|
@index[:npcs][npc['id']] = npc
# Index items held by NPC
next unless npc['itemsHeld'].present?
npc['itemsHeld'].each do |item|
key = "#{item['type']}:#{item['id']}"
@index[:npc_holdings][key] = npc['id']
end
end
end
end
end
Action Items:
- Create
app/services/break_escape/scenario_traverser.rb - Add unit tests for each finder method
- Update inventory validation to use traverser
- Benchmark performance improvement (should eliminate duplicate traversals)
- Add memoization for scenario_traverser instances per game
Issue 2.3: Missing Container Nested Contents Handling
Location: Phase 3, Container endpoint
Problem:
- Plan says "containers can be nested" (containers inside containers)
- But
filter_contents_fieldmethod is incomplete (pseudocode) - No handling for deeply nested containers
- Unclear: Can items inside nested containers be collected?
Impact:
- Nested container structure breaks at runtime
- Items in nested containers unreachable
- Filtering logic incomplete
Recommendation: Extend container traversal to handle nesting:
# In ScenarioTraverser class:
def container_chain(container_id)
# Returns all containers in nesting order
# e.g., [safe_id, inner_box_id, final_container_id]
chain = []
current_id = container_id
while current_id
container = @index[:containers][current_id]
return nil unless container # Safety check: container not found
chain.unshift(current_id)
current_id = @index[:container_map]["#{container['type']}:#{current_id}"]
end
chain
end
def is_container_accessible?(container_id, unlocked_objects)
# Container only accessible if ALL parent containers in chain are unlocked
chain = container_chain(container_id)
return false unless chain
chain.all? { |cid| unlocked_objects.include?(cid) }
end
Action Items:
- Add container chain tracking to traverser
- Add accessibility check for nested containers
- Update container endpoint to validate entire chain is unlocked
- Add tests for nested container scenarios
- Document nesting rules in scenario schema
3. TESTING & VALIDATION ISSUES
Issue 3.1: Insufficient Test Coverage Specification
Location: Phase 9, Testing & Validation (very high level)
Problem:
- Phase 9 lists test names but no test cases/assertions
- No coverage requirements specified
- No "happy path" vs "unhappy path" breakdown
- No edge case identification
Impact:
- Tests will be incomplete or miss critical paths
- Integration test failure doesn't help developers debug
- No regression test foundation for future features
Recommendation: Create detailed test matrix:
# test/integration/client_action_validation_test.rb
describe "Client Action Validation" do
describe "Room Access Control" do
test "player can access starting room without unlock" do
# Game created, player_state['unlockedRooms'] contains start room
# Request room endpoint for startRoom → 200 OK
end
test "player cannot access locked room without unlock" do
# Request room endpoint for locked room → 403 Forbidden
end
test "player can access room after unlock" do
# Unlock room → Update unlockedRooms
# Request room endpoint → 200 OK
end
end
describe "Data Filtering" do
test "room response does not include 'requires' field" do
# Check all objects in room response
# Assert no 'requires' key exists at any level
end
test "room response does not include 'contents' of locked containers" do
# Check all containers in room response
# Assert 'contents' key does not exist if locked
end
test "room response includes 'lockType' and 'locked' status" do
# Verify these fields present for client UI
end
end
describe "Container Lazy Loading" do
test "container contents not accessible before unlock" do
# GET /container/safe_1 before unlock → 403 Forbidden
end
test "container contents accessible after unlock" do
# Unlock container → 200 OK with contents
end
test "nested containers require full chain unlock" do
# Scenario with: safe → inner_box → item
# Each must be unlocked separately
end
end
describe "Inventory Validation" do
test "item cannot be collected from locked room" do
# POST /inventory { add, item } for item in locked room
# → Error: "Room not unlocked"
end
test "item cannot be collected from locked container" do
# Item in locked container
# → Error: "Container not unlocked"
end
test "NPC-held item cannot be collected before encounter" do
# NPC in room not visited yet
# → Error: "NPC not encountered"
end
test "item can be collected from accessible location" do
# Room unlocked, container unlocked or not in container, NPC encountered
# → Success, inventory updated
end
end
describe "NPC Encounter Tracking" do
test "NPC added to encounteredNPCs when room loaded" do
# Load room with NPC
# Check player_state['encounteredNPCs'] includes NPC
end
test "NPC not in encounteredNPCs if room never visited" do
# Scenario with NPC in locked room
# encounteredNPCs should not include that NPC
end
end
describe "Unlock Tracking" do
test "room added to unlockedRooms after successful unlock" do
# Valid unlock attempt
# unlockedRooms includes the room
end
test "container added to unlockedObjects after unlock" do
# Valid container unlock
# unlockedObjects includes the container
end
end
describe "Edge Cases" do
test "simultaneous unlock attempts don't duplicate" do
# Two concurrent unlock requests for same door
# unlockedRooms includes room only once
end
test "inventory doesn't accept duplicates" do
# Collect item, remove it, collect again
# Inventory correctly updated each time
end
test "player state persists across requests" do
# Create game, unlock room, fetch game
# player_state still contains unlock
end
end
end
Action Items:
- Create comprehensive test matrix before implementation
- Assign each test to specific phase where it should pass
- Define acceptance criteria for each test
- Create fixtures (scenarios) for each test case
- Add performance benchmarks (response time, memory usage)
Issue 3.2: Missing Rollback & Recovery Strategy
Location: All phases, implicit assumption things work
Problem:
- Plan doesn't mention what happens if a phase fails
- No specification of backwards compatibility
- No plan for debugging failed unlocks
- No admin tools for resetting game state
Impact:
- If implementation partially succeeds, hard to debug
- Players can't recover from bugs
- No way to fix inconsistent state in production
Recommendation: Add Phase 11: Rollback & Recovery Tools
# app/services/break_escape/game_state_manager.rb
module BreakEscape
class GameStateManager
def initialize(game)
@game = game
end
# Snapshot current state for rollback
def create_snapshot
{
timestamp: Time.current,
snapshot_id: SecureRandom.uuid,
player_state: @game.player_state.deep_dup
}
end
# Restore from snapshot
def restore_snapshot(snapshot_id)
# Find and restore snapshot
snapshot = @game.snapshots.find_by(snapshot_id: snapshot_id)
raise "Snapshot not found: #{snapshot_id}" unless snapshot
@game.player_state = snapshot.data['player_state']
@game.save!
end
# Reset game to initial state
def reset_to_start
@game.initialize_player_state!
end
# Validate state consistency
def validate_consistency
errors = []
# Check all unlockedRooms exist in scenario
@game.player_state['unlockedRooms'].each do |room_id|
unless @game.scenario_data['rooms'].key?(room_id)
errors << "Room #{room_id} in unlockedRooms but not in scenario"
end
end
# Check all inventory items exist in scenario or are starting items
@game.player_state['inventory'].each do |item|
# Item should be findable in scenario
# OR be a starting item
end
# Check NPC encounters are in scenario
# Check container unlocks are in scenario
errors
end
end
end
Action Items:
- Add Phase 11: Recovery & Debugging Tools to plan
- Create
app/services/break_escape/game_state_manager.rb - Add state validation checks before responding to client requests
- Create admin endpoint to inspect game state
- Create admin endpoint to reset specific games
- Add logging for state changes
4. IMPLEMENTATION SEQUENCING ISSUES
Issue 4.1: Wrong Phase Dependencies
Location: Implementation TODO List, Phase ordering
Problem:
- Phase 6 (Unlock Tracking) depends on Phase 4 (Inventory Validation)
- But plan lists them in sequence as if Phase 6 is independent
- Phase 7 (Sync State Validation) depends on Phase 1 (Initialization)
- No explicit dependency graph
Impact:
- Developer might implement phases out of order and get stuck
- Unclear which tests should pass after each phase
Recommendation: Create explicit dependency diagram:
Phase 1: Data Model & Initialization
├─ Phase 2: Data Filtering
│ ├─ Phase 3: Container Lazy-Loading
│ └─ Phase 6: Unlock Validation & Tracking
├─ Phase 4: Inventory Validation
│ └─ Phase 5: NPC Encounter Tracking
└─ Phase 7: Sync State Validation
Phase 8: Routing (can start once controllers exist)
Phase 9: Testing (runs continuously after each phase)
Phase 10: Client Updates (after server complete)
Phase 11: Recovery Tools (after Phase 1-7 complete)
Recommended Implementation Order:
- Phase 1 ✓ (prerequisite for everything)
- Phase 2 ✓ (data filtering needed by Phase 6)
- Phase 8 ✓ (add routes as you go)
- Phase 3 ✓ (container endpoint)
- Phase 6 ✓ (unlock tracking)
- Phase 5 ✓ (NPC encounter tracking)
- Phase 4 ✓ (inventory validation)
- Phase 7 ✓ (sync state validation)
- Phase 11 ✓ (recovery tools)
- Phase 9 ✓ (comprehensive testing)
- Phase 10 (client updates)
Action Items:
- Create dependency graph in planning document
- Identify any additional dependencies not listed
- Reorder phases based on dependencies
- Create "ready to start" checklist for each phase
Issue 4.2: Client Updates Are Last (Should Be Earlier for Testing)
Location: Phase 10, Client Updates
Problem:
- Client updates are Phase 10, last
- But without client changes, server endpoints can't be tested
- Manual integration testing required with curl/Postman before client ready
- Creates bottleneck: server done but can't validate until client updated
Impact:
- Hard to find bugs (can't end-to-end test without client)
- Delay in finding integration issues
- Client developers blocked waiting for server
Recommendation: Split client updates into two phases:
## Phase 8B: Client Integration (After Phase 3, Before Phase 4)
- Create stub client endpoints that call new server endpoints
- Add error handling for new response format
- Test with curl/Postman until working
- Update game.js to use scenario_map instead of full scenario
- Update room endpoint calls with filtering awareness
## Phase 10: Client Polish (After Phase 9)
- Add UI for error states
- Add loading indicators
- Optimize performance
- Add user feedback for validation errors
Action Items:
- Create Phase 8B: Client Integration
- Add stub client implementation before server complete
- Create curl/Postman test suite for each endpoint
- Parallel development: Server Phase 4-7 while Client Phase 8B ongoing
- Move UI polish to Phase 10 (after all features working)
5. DOCUMENTATION & COMMUNICATION GAPS
Issue 5.1: No Specification of Error Response Formats
Location: Error Handling section, incomplete examples
Problem:
- Plan shows a few error examples but inconsistent format
- "Missing room_id parameter" vs "Container not unlocked" use different patterns
- No specification of HTTP status codes for different error types
- No error code system for client to handle programmatically
Impact:
- Client code might not correctly handle errors
- Hard to test all error paths
- Developers guess at response format
Recommendation: Formalize error response schema:
# Error Response Format:
{
success: false,
error: {
code: "ERROR_CODE", # Machine-readable error classification
message: "Human readable", # User-facing message
details: {} # Additional context (optional)
}
}
# HTTP Status Codes:
# 400 - Bad Request: Missing/invalid parameters (developer error)
# 403 - Forbidden: Permission denied (gameplay state issue)
# 404 - Not Found: Resource not found
# 422 - Unprocessable Entity: Validation failed (gameplay logic)
# 500 - Internal Server Error: Unexpected error (bug)
# Error Codes:
MISSING_PARAMETER = "MISSING_PARAMETER"
ROOM_NOT_FOUND = "ROOM_NOT_FOUND"
ROOM_NOT_ACCESSIBLE = "ROOM_NOT_ACCESSIBLE"
CONTAINER_NOT_FOUND = "CONTAINER_NOT_FOUND"
CONTAINER_NOT_UNLOCKED = "CONTAINER_NOT_UNLOCKED"
ITEM_NOT_FOUND = "ITEM_NOT_FOUND"
ITEM_NOT_TAKEABLE = "ITEM_NOT_TAKEABLE"
ITEM_LOCATION_LOCKED = "ITEM_LOCATION_LOCKED" # Room or container
NPC_NOT_ENCOUNTERED = "NPC_NOT_ENCOUNTERED"
INVALID_UNLOCK_ATTEMPT = "INVALID_UNLOCK_ATTEMPT"
Action Items:
- Create error code constants in
lib/break_escape/error_codes.rb - Create error response builder method in ApplicationController
- Update all error responses to use consistent format
- Document error codes in API documentation
- Create error handling guide for client developers
Issue 5.2: No API Documentation or Examples
Location: Implementation plan, missing entirely
Problem:
- Plan doesn't specify HTTP verbs, parameter names exactly as they'll be
- Plan shows pseudocode but not exact API contracts
- Client developers won't know what to call
- No spec for developers following later
Impact:
- Integration painful and error-prone
- Clients might call endpoints incorrectly
- Hard to onboard new developers
Recommendation: Create OpenAPI/Swagger spec:
# Break Escape API - Validation & Filtering Endpoints
/games/{id}/room/{room_id}:
get:
summary: Load room data with filtering
parameters:
- name: id
in: path
required: true
schema:
type: string
- name: room_id
in: path
required: true
schema:
type: string
responses:
200:
description: Room data (requires and contents fields hidden)
content:
application/json:
schema:
type: object
properties:
room_id: { type: string }
room: { type: object }
403:
description: Room not accessible
404:
description: Room not found
/games/{id}/container/{container_id}:
get:
summary: Load locked container contents
parameters:
- name: id
in: path
required: true
- name: container_id
in: path
required: true
responses:
200:
description: Container contents with filtering applied
403:
description: Container not unlocked
404:
description: Container not found
/games/{id}/scenario_map:
get:
summary: Get minimal scenario layout (no secrets exposed)
responses:
200:
description: Room connections and accessibility
/games/{id}/unlock:
post:
parameters:
- name: targetType
in: body
required: true
schema:
enum: [door, object]
- name: targetId
in: body
required: true
- name: attempt
in: body
required: true
- name: method
in: body
required: true
responses:
200:
description: Unlock successful
422:
description: Invalid attempt
/games/{id}/inventory:
post:
parameters:
- name: actionType
in: body
enum: [add, remove]
- name: item
in: body
schema:
type: object
required: [type, id]
responses:
200:
description: Inventory updated
422:
description: Item not collectible
Action Items:
- Create OpenAPI/Swagger spec in
docs/api.openapi.yaml - Generate interactive API docs from spec
- Add code examples for each endpoint in Ruby/JavaScript
- Create API testing guide (curl examples)
- Update README with API overview
6. PERFORMANCE & SCALABILITY ISSUES
Issue 6.1: No Consideration of Large Scenarios
Location: All phases, implicit assumption scenarios are small
Problem:
- Plan uses linear search through all rooms/objects
- No pagination or lazy-loading of scenario data
- Scenarios could have thousands of objects
- Every room request triggers full traversal
Impact:
- Slow response times for large scenarios
- High CPU/memory on server
- Client perceived as sluggish
Recommendation: Add performance considerations to Phase 2:
# app/services/break_escape/scenario_cache.rb
module BreakEscape
class ScenarioCache
# Cache filtered room data for performance
def initialize(game)
@game = game
@room_cache = {}
@filter_service = DataFilterService.new(game.scenario_data)
end
def filtered_room(room_id)
@room_cache[room_id] ||= begin
@filter_service.filtered_room_data(room_id)
end
end
def clear_cache
@room_cache.clear
end
def invalidate_room(room_id)
@room_cache.delete(room_id)
end
end
end
# Measure and optimize:
# - Response time: Room endpoint should respond < 100ms
# - Scenario size: Support scenarios up to 100 rooms, 1000 objects
# - Memory: Scenario data should fit in memory (not stream from disk)
Action Items:
- Add caching layer for filtered room data
- Profile room endpoint response time with large scenario
- Set performance targets: < 100ms per room request
- Add N+1 query detection for database calls
- Test with scenarios of varying sizes (10, 100, 1000 objects)
Issue 6.2: Inventory Validation Performance
Location: Phase 4, multiple find operations
Problem:
- Current approach does 4+ full scenario traversals per inventory add
find_item_in_scenario,find_item_container,find_item_room,find_npc_holding_item- Each traversal walks entire object tree
Impact:
- Inventory operations slow (multiple seconds for large scenario)
- Scales poorly: inventory add time grows linearly with scenario size
Recommendation: Use ScenarioTraverser index (Issue 2.2 already addresses this)
# Measurement:
# Current: O(n * m) where n = scenarios traversals, m = objects
# With index: O(1) lookup after O(m) index build
# Improvement: Build index once per game, reuse for all operations
Action Items:
- Implement ScenarioTraverser index (see Issue 2.2)
- Benchmark inventory add: before/after
- Target: Inventory add < 50ms
- Add index build time to game initialization
- Cache traverser instance in @game object
7. SECURITY REVIEW
Issue 7.1: Missing Authorization Checks
Location: All controller actions
Problem:
- Plan shows
authorize @game if defined?(Pundit)but conditional - Not all endpoints might have this
- No specification of what authorization means
- Could allow one player to access another player's game
Impact:
- Player A could unlock rooms in Player B's game
- Data leak between players
- Game state modification by unauthorized user
Recommendation: Strengthen authorization:
# app/policies/break_escape/game_policy.rb
module BreakEscape
class GamePolicy
attr_reader :user, :game
def initialize(user, game)
@user = user
@game = game
end
def show?
game.player == user # Only owner can access
end
def room?
show? # Same auth as show
end
def container?
show?
end
def unlock?
show?
end
def inventory?
show?
end
def sync_state?
show?
end
end
end
# In controller, always authorize:
module BreakEscape
class GamesController < ApplicationController
before_action :set_game, only: [:show, :scenario, :ink, :room, :container, :sync_state, :unlock, :inventory, :scenario_map]
before_action :authorize_game
private
def authorize_game
authorize @game
end
end
end
Action Items:
- Create
app/policies/break_escape/game_policy.rb - Make authorization non-conditional (always required)
- Test authorization on each endpoint
- Add integration tests for authorization failures
- Document who can perform each action
Issue 7.2: Data Filtering Completeness Not Verified
Location: All filtering methods
Problem:
- Plan assumes filter methods catch all instances of hidden fields
- But what if scenario has new field type added later?
- What if nested structure is deeper than expected?
- No way to verify filtering is complete
Impact:
- Accidentally expose "requires" field if code changes
- Security regression from future maintenance
Recommendation: Add verification layer:
# app/services/break_escape/data_verifier.rb
module BreakEscape
class DataVerifier
FORBIDDEN_FIELDS = ['requires'].freeze
def self.verify_filtered_data(data)
errors = []
verify_recursive(data, errors)
errors
end
private
def self.verify_recursive(obj, errors, path = "root")
case obj
when Hash
obj.each do |key, value|
if FORBIDDEN_FIELDS.include?(key)
errors << "Found forbidden field '#{key}' at #{path}"
end
verify_recursive(value, errors, "#{path}.#{key}")
end
when Array
obj.each_with_index do |item, idx|
verify_recursive(item, errors, "#{path}[#{idx}]")
end
end
end
end
end
# Use in tests:
test "room data never exposes requires field" do
response = get "/games/#{game.id}/room/office"
room_data = JSON.parse(response.body)['room']
errors = DataVerifier.verify_filtered_data(room_data)
assert errors.empty?, "Found forbidden fields: #{errors.join(', ')}"
end
Action Items:
- Create
app/services/break_escape/data_verifier.rb - Add verification to all filtered data responses
- Create test helper to assert no forbidden fields
- Run verification in tests automatically
- Add flag to log warnings in development if forbidden fields found
8. MINOR ISSUES (Nice-to-Have Improvements)
Issue 8.1: Debugging Visibility
Problem:
- No logging of filtering/validation operations
- Hard to debug "why can't player access room?"
- Hard to trace validation decisions
Recommendation:
class DataFilterService
def filter_object(obj, hide_fields: SENSITIVE_FIELDS)
Rails.logger.debug "[BreakEscape] Filtering object #{obj['id']} with fields: #{hide_fields}"
# ... rest of method
end
end
class Game < ApplicationRecord
def validate_unlock(...)
Rails.logger.info "[BreakEscape] Validating unlock for #{target_id}"
# ... rest of method
end
end
Action Items:
- Add structured logging using
Rails.logger.tagged - Create log format consistent with other endpoints
- Add instrumentation gem for performance monitoring
- Document logging format for debugging guide
Issue 8.2: Consistency Between Endpoints
Problem:
- Room endpoint might filter differently than container endpoint
- Scenario map might expose different fields than room
- No specification that all are consistent
Recommendation:
# Create shared constants
module BreakEscape
module DataFiltering
# Fields always hidden from client (solutions)
ALWAYS_HIDDEN = ['requires'].freeze
# Fields hidden until unlocked (contents)
HIDDEN_UNTIL_UNLOCKED = ['contents'].freeze
# Fields always visible to client (needed for UI)
ALWAYS_VISIBLE = ['lockType', 'locked', 'observations', 'id', 'name', 'type'].freeze
end
end
Action Items:
- Create
lib/break_escape/data_filtering.rbwith shared constants - Use constants in all filtering methods
- Document field visibility policy in comments
- Add assertions to tests that verify consistency
SUMMARY: Risk & Mitigation Matrix
| Risk | Probability | Impact | Mitigation | Priority |
|---|---|---|---|---|
| Race condition in initialization | High | Medium | Add idempotent pattern (Issue 1.1) | CRITICAL |
| Malformed scenario breaks game | Medium | High | Add validation (Issue 1.2) | CRITICAL |
| Incomplete scenario schema | High | Medium | Create formal schema (Issue 1.3) | CRITICAL |
| Performance degrades with large scenarios | Medium | High | Add caching + traverser index (Issues 6.1, 6.2, 2.1) | HIGH |
| Authorization bypass | Low | Critical | Strengthen auth checks (Issue 7.1) | HIGH |
| Accidentally expose secrets in future | Low | Critical | Add verification layer (Issue 7.2) | HIGH |
| Nested containers break | Medium | Medium | Extend traversal (Issue 2.3) | HIGH |
| Incomplete test coverage | High | Medium | Create test matrix (Issue 3.1) | HIGH |
| Player stuck in failed state | Medium | Medium | Add recovery tools (Issue 3.2) | MEDIUM |
| Wrong phase dependencies | Low | Medium | Create dependency graph (Issue 4.1) | MEDIUM |
| Client blocked on server | Low | Medium | Parallel Phase 8B (Issue 4.2) | MEDIUM |
| Integration testing difficult | Medium | Low | Create curl/Postman suite (Issue 4.2) | MEDIUM |
| API unclear to developers | Low | Low | Create OpenAPI spec (Issue 5.2) | LOW |
| Debugging difficult | Low | Low | Add logging (Issue 8.1) | LOW |
RECOMMENDED PRIORITY: Critical Path First
Must Fix Before Starting:
- Issue 1.1: Race condition handling
- Issue 1.2: Scenario validation
- Issue 1.3: Scenario schema documentation
Must Fix During Design Phase: 4. Issue 2.1: Efficient filtering service 5. Issue 2.2: Scenario traverser with index 6. Issue 3.1: Comprehensive test matrix 7. Issue 4.1: Dependency graph
Can Parallelize: 8. Issue 2.3: Nested container handling (parallel with Phase 3) 9. Issue 4.2: Phase 8B client integration (parallel with server) 10. Issue 7.1: Authorization policy (parallel with controller work)
Add After MVP: 11. Issue 3.2: Recovery tools (Phase 11) 12. Issue 6.1: Performance optimization 13. Issue 7.2: Data verification 14. Issue 5.2: OpenAPI documentation 15. Issue 8.1: Enhanced logging
Next Steps
- Review with team - Present this review to development team
- Prioritize - Decide which issues to address immediately vs. defer
- Update plan - Incorporate recommendations into IMPLEMENTATION_PLAN.md
- Create PR - Implement high-priority fixes to game.rb and add data_filter_service.rb
- Begin Phase 1 - Start with data model improvements and validation
Appendix: Quick Reference Checklist
Before Any Code Changes:
- Create scenario schema document
- Add scenario data validation
- Make authorization non-conditional
- Create dependency graph
During Implementation:
- Use DataFilterService for filtering
- Use ScenarioTraverser for lookups
- Create test matrix before phase
- Run DataVerifier on all responses
- Add error code constants and consistent response format
Before Calling Complete:
- Zero authorization bypasses
- No forbidden fields in any response
- Performance < 100ms per room request
- All test matrix items passing
- Recovery tools available for admins
- API documentation complete
End of Review