diff --git a/app/models/break_escape/mission.rb b/app/models/break_escape/mission.rb
index 55502c1..e3ce696 100644
--- a/app/models/break_escape/mission.rb
+++ b/app/models/break_escape/mission.rb
@@ -114,6 +114,17 @@ module BreakEscape
attr_reader :random_password, :random_pin, :random_code, :vm_context
+ # Get a VM from the context by title, or return a fallback VM object
+ # Usage in ERB:
+ # "vm": <%= vm_object('kali', {"id":1,"title":"kali","ip":"192.168.1.10","enable_console":true}) %>
+ def vm_object(title, fallback = {})
+ if vm_context && vm_context['hacktivity_mode'] && vm_context['vms']
+ vm = vm_context['vms'].find { |v| v['title'] == title }
+ return vm.to_json if vm
+ end
+ fallback.to_json
+ end
+
def get_binding
binding
end
diff --git a/public/break_escape/assets/objects/flag-station.png b/public/break_escape/assets/objects/flag-station.png
new file mode 100644
index 0000000..282163c
Binary files /dev/null and b/public/break_escape/assets/objects/flag-station.png differ
diff --git a/public/break_escape/assets/objects/vm-launcher-desktop.png b/public/break_escape/assets/objects/vm-launcher-desktop.png
new file mode 100644
index 0000000..2d3c6d2
Binary files /dev/null and b/public/break_escape/assets/objects/vm-launcher-desktop.png differ
diff --git a/public/break_escape/assets/objects/vm-launcher-kali.png b/public/break_escape/assets/objects/vm-launcher-kali.png
new file mode 100644
index 0000000..44b9ede
Binary files /dev/null and b/public/break_escape/assets/objects/vm-launcher-kali.png differ
diff --git a/public/break_escape/assets/objects/vm-launcher.png b/public/break_escape/assets/objects/vm-launcher.png
new file mode 100644
index 0000000..82835de
Binary files /dev/null and b/public/break_escape/assets/objects/vm-launcher.png differ
diff --git a/public/break_escape/js/core/game.js b/public/break_escape/js/core/game.js
index 4a27a2e..53d7357 100644
--- a/public/break_escape/js/core/game.js
+++ b/public/break_escape/js/core/game.js
@@ -364,6 +364,12 @@ export function preload() {
this.load.image('pc11', 'objects/pc11.png');
this.load.image('pc12', 'objects/pc12.png');
+ // VMs Launchers and Flag Stations
+ this.load.image('vm-launcher', 'objects/vm-launcher.png');
+ this.load.image('vm-launcher-kali', 'objects/vm-launcher-kali.png');
+ this.load.image('vm-launcher-desktop', 'objects/vm-launcher-desktop.png');
+ this.load.image('flag-station', 'objects/flag-station.png');
+
// Laptops
this.load.image('laptop7', 'objects/laptop7.png');
diff --git a/public/break_escape/js/minigames/vm-launcher/vm-launcher-minigame.js b/public/break_escape/js/minigames/vm-launcher/vm-launcher-minigame.js
index c083d16..526aa5e 100644
--- a/public/break_escape/js/minigames/vm-launcher/vm-launcher-minigame.js
+++ b/public/break_escape/js/minigames/vm-launcher/vm-launcher-minigame.js
@@ -12,9 +12,8 @@ import { MinigameScene } from '../framework/base-minigame.js';
export class VmLauncherMinigame extends MinigameScene {
constructor(container, params) {
super(container, params);
- this.vms = params.vms || [];
+ this.vm = params.vm || null;
this.hacktivityMode = params.hacktivityMode || false;
- this.selectedVm = null;
this.isLaunching = false;
}
@@ -243,22 +242,22 @@ export class VmLauncherMinigame extends MinigameScene {
const launcher = document.createElement('div');
launcher.className = 'vm-launcher';
- if (this.vms.length === 0) {
- launcher.innerHTML = this.buildNoVmsMessage();
+ if (!this.vm) {
+ launcher.innerHTML = this.buildNoVmMessage();
} else {
- launcher.innerHTML = this.buildVmList();
+ launcher.innerHTML = this.buildVmDisplay();
}
this.gameContainer.appendChild(launcher);
this.attachEventHandlers();
}
- buildNoVmsMessage() {
+ buildNoVmMessage() {
if (this.hacktivityMode) {
return `
-
No VMs Available
-
No virtual machines are configured for this mission.
+
No VM Available
+
No virtual machine is configured for this terminal.
Please provision VMs through Hacktivity first.
`;
@@ -266,85 +265,59 @@ export class VmLauncherMinigame extends MinigameScene {
return `
VM Terminal
-
You've discovered a computer terminal in the game. To interact with it, you need to launch the virtual machines on your local system.
- ${this.buildStandaloneInstructions()}
+
You've discovered a computer terminal in the game. To interact with it, you need to launch the virtual machine on your local system.
+
`;
}
}
- buildVmList() {
- const description = this.hacktivityMode
- ? 'Select a VM to open its console. A SPICE viewer file will be downloaded.'
- : 'These VMs are available for this mission.';
-
- let html = `
- ${description}
-
- `;
-
- for (const vm of this.vms) {
- const hasConsole = vm.enable_console !== false;
- const statusClass = hasConsole ? 'console' : 'online';
- const statusText = hasConsole ? 'Console' : 'Active';
-
+ buildVmDisplay() {
+ const hasConsole = this.vm.enable_console !== false;
+ const statusClass = hasConsole ? 'console' : 'online';
+ const statusText = hasConsole ? 'Console' : 'Active';
+ let html = `
You've discovered a computer terminal in the game. To interact with it, `
+
+ if (this.hacktivityMode) {
html += `
-
-
-
- ${vm.ip ? `IP: ${this.escapeHtml(vm.ip)} ` : ''}
-
-
+ click the console button, and follow the instructions.
+ `;
+ } else {
+ html += `
+ you need to launch the virtual machine on your local system.
`;
}
- html += '
';
+ html += `
+
+
+
+
+ ${this.vm.ip ? `IP: ${this.escapeHtml(this.vm.ip)} ` : ''}
+
+
+ `;
if (this.hacktivityMode) {
html += `
-
- Select a VM
+
+ Open Console: ${this.escapeHtml(this.vm.title)}
`;
- } else {
- html += this.buildStandaloneInstructions();
}
return html;
}
- buildStandaloneInstructions() {
- return `
-
-
Load These VMs in VirtualBox:
-
-
- Open VirtualBox on your local machine
- Import the kali and desktop VMs (.ova files)
- Start both VMs and wait for them to boot
- Note their IP addresses
- Return to this game to complete the mission
-
-
- `;
- }
+
attachEventHandlers() {
- // VM card selection
- const vmCards = this.gameContainer.querySelectorAll('.vm-card');
- vmCards.forEach(card => {
- this.addEventListener(card, 'click', () => this.selectVm(card));
- });
-
// Launch button (Hacktivity mode only)
const launchBtn = this.gameContainer.querySelector('#launch-console-btn');
if (launchBtn) {
@@ -352,33 +325,13 @@ export class VmLauncherMinigame extends MinigameScene {
}
}
- selectVm(card) {
- if (this.isLaunching) return;
-
- // Clear previous selection
- this.gameContainer.querySelectorAll('.vm-card').forEach(c => {
- c.classList.remove('selected');
- });
-
- // Select new card
- card.classList.add('selected');
- this.selectedVm = this.vms.find(vm => vm.id == card.dataset.vmId);
-
- // Update launch button
- const launchBtn = this.gameContainer.querySelector('#launch-console-btn');
- if (launchBtn && this.selectedVm) {
- launchBtn.disabled = false;
- launchBtn.textContent = `Open Console: ${this.selectedVm.title}`;
- }
- }
-
async launchConsole() {
- if (!this.selectedVm || this.isLaunching) return;
+ if (!this.vm || this.isLaunching) return;
this.isLaunching = true;
const launchBtn = this.gameContainer.querySelector('#launch-console-btn');
const statusEl = this.gameContainer.querySelector('#launch-status');
- const vmCard = this.gameContainer.querySelector(`[data-vm-id="${this.selectedVm.id}"]`);
+ const vmCard = this.gameContainer.querySelector('.vm-card');
launchBtn.disabled = true;
launchBtn.classList.add('launching');
@@ -391,8 +344,8 @@ export class VmLauncherMinigame extends MinigameScene {
if (window.hacktivityCable) {
// Use ActionCable integration
const result = await window.hacktivityCable.requestConsoleFile(
- this.selectedVm.id,
- this.selectedVm.event_id
+ this.vm.id,
+ this.vm.event_id
);
if (result.success) {
@@ -416,7 +369,7 @@ export class VmLauncherMinigame extends MinigameScene {
this.isLaunching = false;
launchBtn.disabled = false;
launchBtn.classList.remove('launching');
- launchBtn.textContent = `Open Console: ${this.selectedVm.title}`;
+ launchBtn.textContent = `Open Console: ${this.vm.title}`;
vmCard.classList.remove('launching');
}
}
@@ -429,7 +382,7 @@ export class VmLauncherMinigame extends MinigameScene {
start() {
super.start();
- console.log('[VmLauncher] Started with', this.vms.length, 'VMs');
+ console.log('[VmLauncher] Started with VM:', this.vm?.title || 'None');
}
}
diff --git a/public/break_escape/js/systems/interactions.js b/public/break_escape/js/systems/interactions.js
index 2ba78fc..467b855 100644
--- a/public/break_escape/js/systems/interactions.js
+++ b/public/break_escape/js/systems/interactions.js
@@ -633,13 +633,13 @@ export function handleObjectInteraction(sprite) {
if (sprite.scenarioData.type === "vm-launcher" || sprite.scenarioData.type === "vm_launcher") {
console.log('VM Launcher interaction:', sprite.scenarioData);
if (window.MinigameFramework) {
- // Get VM data from scenario or gameConfig
- const vms = sprite.scenarioData.vms || window.gameConfig?.vms || [];
- const hacktivityMode = sprite.scenarioData.hacktivityMode || window.gameConfig?.hacktivityMode || false;
+ // Get VM data from scenario
+ const vm = sprite.scenarioData.vm || null;
+ const hacktivityMode = sprite.scenarioData.hacktivityMode || window.breakEscapeConfig?.hacktivityMode || false;
window.MinigameFramework.startMinigame('vm-launcher', null, {
title: sprite.scenarioData.name || 'VM Console Access',
- vms: vms,
+ vm: vm,
hacktivityMode: hacktivityMode,
stationId: sprite.scenarioData.id || sprite.objectId
});
diff --git a/scenarios/secgen_vm_lab/scenario.json.erb b/scenarios/secgen_vm_lab/scenario.json.erb
index d3f1614..524a008 100644
--- a/scenarios/secgen_vm_lab/scenario.json.erb
+++ b/scenarios/secgen_vm_lab/scenario.json.erb
@@ -44,7 +44,7 @@
"takeable": false,
"observations": "A terminal interface for accessing the Kali Linux VM. Ready to launch.",
"hacktivityMode": <%= vm_context && vm_context['hacktivity_mode'] ? 'true' : 'false' %>,
- "vms": <%= vm_context && vm_context['vms'] ? vm_context['vms'].to_json : '[{"id":1,"title":"kali","ip":"192.168.1.10","enable_console":true}]' %>
+ "vm": <%= vm_object('kali', {"id":1,"title":"kali","ip":"192.168.1.10","enable_console":true}) %>
},
{
"type": "vm-launcher",
@@ -53,7 +53,7 @@
"takeable": false,
"observations": "A terminal interface for accessing the Desktop Linux VM. Ready to launch.",
"hacktivityMode": <%= vm_context && vm_context['hacktivity_mode'] ? 'true' : 'false' %>,
- "vms": <%= vm_context && vm_context['vms'] ? vm_context['vms'].to_json : '[{"id":2,"title":"desktop","ip":"192.168.1.20","enable_console":true}]' %>
+ "vm": <%= vm_object('desktop', {"id":2,"title":"desktop","ip":"192.168.1.20","enable_console":true}) %>
},
{
"type": "notes",
diff --git a/test/dummy/log/test.log b/test/dummy/log/test.log
index b88e53f..a38670b 100644
--- a/test/dummy/log/test.log
+++ b/test/dummy/log/test.log
@@ -53353,3 +53353,1074 @@ Processing by BreakEscape::MissionsController#index as HTML
Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 1.4ms | GC: 0.0ms)
Completed 200 OK in 2ms (Views: 1.4ms | ActiveRecord: 0.2ms (5 queries, 1 cached) | GC: 0.1ms)
[1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mActiveRecord::InternalMetadata Load (0.1ms)[0m [1m[34mSELECT * FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ? ORDER BY "ar_internal_metadata"."key" ASC LIMIT 1[0m [[nil, "schema_sha1"]]
+ [1m[36mActiveRecord::SchemaMigration Load (0.0ms)[0m [1m[34mSELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+ [1m[35m (0.1ms)[0m [1m[35mPRAGMA foreign_keys[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys = ON[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_keys = OFF[0m
+ [1m[36mFixtures Load (0.1ms)[0m [1m[31mDELETE FROM "break_escape_demo_users";
+DELETE FROM "break_escape_missions";
+INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (149617800, 'test_user', '2025-11-28 23:58:19', '2025-11-28 23:58:19');
+INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (618102942, 'other_user', '2025-11-28 23:58:19', '2025-11-28 23:58:19');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (418560898, 'ceo_exfil', 'CEO Exfiltration', 'Test scenario', 1, 3, '2025-11-28 23:58:19', '2025-11-28 23:58:19');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (636030761, 'test_unpublished', 'Unpublished Test', 'Not visible', 0, 1, '2025-11-28 23:58:19', '2025-11-28 23:58:19');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at", "secgen_scenario", "collection") VALUES (899573729, 'secgen_vm_lab', 'SecGen VM Lab - Linux Introduction', 'Test VM and flag integration with SecGen scenario', 1, 2, '2025-11-28 23:58:19', '2025-11-28 23:58:19', 'labs/introducing_attacks/1_intro_linux.xml', 'vm_labs')[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys = 0[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_keys = 1[0m
+ [1m[36mTRANSACTION (0.9ms)[0m [1m[36mcommit transaction[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_key_check[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_show_should_inject_game_configuration
+----------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.2ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.030957"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.030772"], ["updated_at", "2025-11-28 23:58:19.030772"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.1ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 1.5ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 1.7ms | GC: 0.0ms)
+Completed 200 OK in 8ms (Views: 2.9ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_unlock_endpoint_should_reject_invalid_attempts
+-------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.047591"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.047518"], ["updated_at", "2025-11-28 23:58:19.047518"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#unlock as HTML
+ Parameters: {"targetType"=>"room", "targetId"=>"office", "attempt"=>"wrong_code", "method"=>"pin", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] validate_unlock: type=room, id=office, attempt=wrong_code, method=pin
+[BreakEscape] Object not found: office
+Completed 422 Unprocessable Content in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_return_404_for_non-existent_NPC
+------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.051825"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.051784"], ["updated_at", "2025-11-28 23:58:19.051784"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink?npc=non-existent" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"npc"=>"non-existent", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] Loading ink for NPC: non-existent
+[BreakEscape] No NPCs found in scenario data
+Completed 404 Not Found in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------
+BreakEscape::GamesControllerTest: test_should_show_game
+-------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.054172"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.054130"], ["updated_at", "2025-11-28 23:58:19.054130"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 0.1ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.2ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 0.3ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_unlock_endpoint_should_accept_correct_pin_code
+-------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.056788"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.056750"], ["updated_at", "2025-11-28 23:58:19.056750"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#unlock as HTML
+ Parameters: {"targetType"=>"door", "targetId"=>"office", "attempt"=>"1234", "method"=>"pin", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] validate_unlock: type=door, id=office, attempt=1234, method=pin
+[BreakEscape] Room data: locked=true, lockType=pin, requires=1234
+[BreakEscape] Room is LOCKED, method must be valid: pin
+[BreakEscape] pin validation result: true
+[BreakEscape] validate_unlock returning: true
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Update (0.0ms)[0m [1m[33mUPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\",\"office\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-28 23:58:19.058450"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.2ms (5 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_show_should_return_HTML_with_game_container
+----------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.059813"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.059775"], ["updated_at", "2025-11-28 23:58:19.059775"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 0.1ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.1ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_game_setup_has_correct_scenario_data
+---------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.062856"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.062814"], ["updated_at", "2025-11-28 23:58:19.062814"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_scenario_endpoint_should_return_JSON
+---------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.064036"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.063997"], ["updated_at", "2025-11-28 23:58:19.063997"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/scenario" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#scenario as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+--------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_inventory_endpoint_should_add_items
+--------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.066343"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.066303"], ["updated_at", "2025-11-28 23:58:19.066303"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Update (0.1ms)[0m [1m[33mUPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[{\"id\":\"note_1\",\"type\":\"note\",\"name\":\"Test Note\",\"takeable\":true}]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-28 23:58:19.066844"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/inventory" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#inventory as HTML
+ Parameters: {"action_type"=>"add", "item"=>{"type"=>"note", "name"=>"Test Note", "id"=>"note_1"}, "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] inventory endpoint: action=add, item=#"note", "name"=>"Test Note", "id"=>"note_1"} permitted: false>
+[BreakEscape] validate_item_collectible: type=note, id=note_1, name=Test Note
+[BreakEscape] Item collection valid: note
+[BreakEscape] Adding item to inventory: note / Test Note
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Update (0.0ms)[0m [1m[33mUPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"note\",\"name\":\"Test Note\",\"id\":\"note_1\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-28 23:58:19.068472"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+[BreakEscape] Item added successfully. Current inventory: [{"type"=>"note", "name"=>"Test Note", "id"=>"note_1"}]
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.2ms (5 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_return_404_for_NPC_without_story_file
+------------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.069749"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.069711"], ["updated_at", "2025-11-28 23:58:19.069711"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink?npc=missing-npc" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"npc"=>"missing-npc", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] Loading ink for NPC: missing-npc
+[BreakEscape] No NPCs found in scenario data
+Completed 404 Not Found in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_sync_state_should_update_player_state_for_current_room
+---------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.071979"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.071935"], ["updated_at", "2025-11-28 23:58:19.071935"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started PUT "/break_escape/games/1/sync_state" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#sync_state as HTML
+ Parameters: {"currentRoom"=>"reception", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+--------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_require_npc_parameter
+--------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.074601"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.074560"], ["updated_at", "2025-11-28 23:58:19.074560"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+Completed 400 Bad Request in 0ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_create_game_and_redirect_when_showing_mission
+----------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Count (0.0ms)[0m [1m[34mSELECT COUNT(*) FROM "break_escape_games"[0m
+Started GET "/break_escape/missions/418560898" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::MissionsController#show as HTML
+ Parameters: {"id"=>"418560898"}
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Load (0.1ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."player_type" = ? AND "break_escape_games"."player_id" = ? AND "break_escape_games"."mission_id" = ? LIMIT ?[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.2ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"scenario_brief\":\"Hi! You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.\",\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office1\"},\"npcs\":[{\"id\":\"neye_eve\",\"displayName\":\"Neye Eve\",\"storyPath\":\"scenarios/ink/neye-eve.json\",\"avatar\":\"assets/npc/avatars/npc_adversary.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\"},{\"id\":\"gossip_girl\",\"displayName\":\"Gossip Girl\",\"storyPath\":\"scenarios/ink/gossip-girl.json\",\"avatar\":\"assets/npc/avatars/npc_neutral.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"timedMessages\":[{\"delay\":5000,\"message\":\"Hey! 👋 Got any juicy gossip for me today?\",\"type\":\"text\"}]},{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"storyPath\":\"scenarios/ink/helper-npc.json\",\"avatar\":\"assets/npc/avatars/npc_helper.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"unlockable\":[\"secure_vault\",\"ceo\"],\"eventMappings\":[{\"eventPattern\":\"item_picked_up:lockpick\",\"targetKnot\":\"on_lockpick_pickup\",\"onceOnly\":true,\"cooldown\":0},{\"eventPattern\":\"minigame_completed\",\"targetKnot\":\"on_lockpick_success\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":10000},{\"eventPattern\":\"minigame_failed\",\"targetKnot\":\"on_lockpick_failed\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":15000},{\"eventPattern\":\"door_unlocked\",\"targetKnot\":\"on_door_unlocked\",\"cooldown\":8000},{\"eventPattern\":\"door_unlock_attempt\",\"targetKnot\":\"on_door_attempt\",\"cooldown\":12000},{\"eventPattern\":\"object_interacted\",\"targetKnot\":\"on_ceo_desk_interact\",\"condition\":\"data.objectType === 'desk_ceo'\",\"cooldown\":10000},{\"eventPattern\":\"item_picked_up:*\",\"targetKnot\":\"on_item_found\",\"cooldown\":20000},{\"eventPattern\":\"room_entered\",\"targetKnot\":\"on_room_entered\",\"cooldown\":45000,\"maxTriggers\":3},{\"eventPattern\":\"room_discovered\",\"targetKnot\":\"on_room_discovered\",\"cooldown\":15000,\"maxTriggers\":5},{\"eventPattern\":\"room_entered:ceo\",\"targetKnot\":\"on_ceo_office_entered\",\"onceOnly\":true}]}],\"objects\":[{\"type\":\"phone\",\"name\":\"Reception Phone\",\"takeable\":false,\"readable\":true,\"voice\":\"Hi, this is the IT Team. Security breach detected in server room. Changed access code to 4829.\",\"sender\":\"IT Team\",\"timestamp\":\"2:15 AM\",\"observations\":\"The reception phone's message light is blinking urgently\"},{\"type\":\"notes\",\"name\":\"Security Log\",\"takeable\":true,\"readable\":true,\"text\":\"Unusual after-hours access detected:\\n- CEO office: 11:30 PM\\n- Server room: 2:15 AM\\n- CEO office again: 3:45 AM\",\"observations\":\"A concerning security log from last night\"},{\"type\":\"pc\",\"name\":\"Reception Computer\",\"takeable\":false,\"lockType\":\"password\",\"passwordHint\":\"Optional hint text\",\"showHint\":true,\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"requires\":\"secret123\",\"observations\":\"The reception's computer, currently locked\",\"postitNote\":\"Password: secret123\",\"showPostit\":true,\"contents\":[{\"type\":\"text_file\",\"name\":\"Private\",\"takeable\":false,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\"}]},{\"type\":\"tablet\",\"name\":\"Tablet Device\",\"takeable\":true,\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"A locked tablet device that requires Bluetooth pairing\"},{\"type\":\"bluetooth_scanner\",\"name\":\"Bluetooth Scanner\",\"takeable\":true,\"observations\":\"A device for detecting nearby Bluetooth signals\",\"canScanBluetooth\":true},{\"type\":\"key\",\"name\":\"Office Key\",\"takeable\":true,\"key_id\":\"office1_key\",\"keyPins\":[65,25,65,25],\"observations\":\"A key to access the office areas\"},{\"type\":\"pin-cracker\",\"name\":\"PIN Cracker\",\"takeable\":true,\"observations\":\"A sophisticated device that can analyze PIN entry patterns and provide feedback on attempts\"},{\"type\":\"safe\",\"name\":\"Reception Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9573\",\"observations\":\"A small wall safe behind the reception desk. Looks like it needs a 4-digit code.\",\"contents\":[{\"type\":\"notes\",\"name\":\"IT Access Credentials\",\"takeable\":true,\"readable\":true,\"text\":\"Emergency IT Admin Credentials:\\nUsername: admin\\nPassword: ITsecure2024\\n\\nServer Room Backup Code: 4829\\nCEO Office Alarm Override: 1337\",\"observations\":\"Sensitive IT credentials that could be very useful\"}]}]},\"office1\":{\"type\":\"room_office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office1_key\",\"keyPins\":[65,25,65,25],\"difficulty\":\"easy\",\"door_sign\":\"4A Hot Desks\",\"connections\":{\"north\":[\"office2\",\"office3\"],\"south\":\"reception\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"requires\":\"password\",\"hasFingerprint\":true,\"fingerprintOwner\":\"ceo\",\"fingerprintDifficulty\":\"medium\",\"observations\":\"A computer with a cybersecurity alert on screen. There might be fingerprints on the keyboard.\"},{\"type\":\"notes\",\"name\":\"IT Memo\",\"takeable\":true,\"readable\":true,\"text\":\"URGENT: Multiple unauthorized access attempts detected from CEO's office IP address\",\"observations\":\"A concerning IT department memo\"},{\"type\":\"fingerprint_kit\",\"name\":\"Fingerprint Kit\",\"takeable\":true,\"observations\":\"A kit used for collecting fingerprints from surfaces\"}]},\"office2\":{\"type\":\"room_office\",\"connections\":{\"north\":\"ceo\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"office2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: office2024\",\"showPostit\":true,\"observations\":\"A standard office computer with a sticky note on the monitor\"},{\"type\":\"notes\",\"name\":\"Shredded Document\",\"takeable\":true,\"readable\":true,\"text\":\"Partially readable: '...offshore account...transfer complete...delete all traces...'\",\"observations\":\"A partially shredded document that someone failed to dispose of properly\"},{\"type\":\"key\",\"name\":\"CEO Office Key\",\"takeable\":true,\"key_id\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"observations\":\"A spare key to the CEO's office, carelessly left behind\"}]},\"office3\":{\"type\":\"room_office\",\"connections\":{\"north\":\"server1\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"IT Staff Computer\",\"takeable\":false,\"requires\":\"bluetooth\",\"lockType\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"An IT staff computer showing network security logs\"},{\"type\":\"notes\",\"name\":\"Network Logs\",\"takeable\":true,\"readable\":true,\"text\":\"Large data transfers detected to unknown external IPs - All originating from CEO's office\",\"observations\":\"Suspicious network activity logs\"}]},\"ceo\":{\"type\":\"room_ceo\",\"connections\":{\"north\":\"closet\",\"south\":\"office2\"},\"locked\":true,\"lockType\":\"key\",\"requires\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"difficulty\":\"easy\",\"objects\":[{\"type\":\"pc\",\"name\":\"CEO Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"ceo2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: ceo2024\",\"showPostit\":true,\"observations\":\"The CEO's laptop, still warm - recently used. A sticky note is attached to the screen.\"},{\"type\":\"suitcase\",\"name\":\"CEO Briefcase\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"difficulty\":\"medium\",\"observations\":\"An expensive leather briefcase with a sturdy lock\",\"contents\":[{\"type\":\"notes\",\"name\":\"Private Note\",\"takeable\":true,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\",\"observations\":\"A hastily written note on expensive paper\"},{\"type\":\"key\",\"name\":\"Safe Key\",\"takeable\":true,\"key_id\":\"safe_key\",\"keyPins\":[52,29,44,37],\"observations\":\"A heavy-duty safe key hidden behind server equipment\"}]},{\"type\":\"phone\",\"name\":\"CEO Phone\",\"takeable\":false,\"readable\":true,\"text\":\"Recent calls: 'Offshore Bank', 'Unknown', 'Data Buyer'\",\"sender\":\"Call Log\",\"timestamp\":\"Last 24 hours\",\"observations\":\"The CEO's phone shows suspicious recent calls\"}]},\"closet\":{\"type\":\"room_closet\",\"connections\":{\"south\":\"ceo\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"7391\",\"objects\":[{\"type\":\"safe\",\"name\":\"Hidden Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"safe_key\",\"keyPins\":[52,29,44,37],\"difficulty\":\"hard\",\"observations\":\"A well-hidden wall safe behind a painting\",\"contents\":[{\"type\":\"notes\",\"name\":\"Incriminating Documents\",\"takeable\":true,\"readable\":true,\"text\":\"Contract for sale of proprietary technology\\nBank transfers from competing companies\\nDetails of upcoming corporate espionage operations\",\"observations\":\"A folder containing damning evidence of corporate espionage. Congratulations! You've recovered the incriminating documents. flag{ceo_exfil_flag}\"}]}]},\"server1\":{\"type\":\"room_servers\",\"connections\":{\"south\":\"office3\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"4829\",\"objects\":[{\"type\":\"pc\",\"name\":\"Server Terminal\",\"takeable\":false,\"observations\":\"The main server terminal showing massive data exfiltration\"},{\"type\":\"key\",\"name\":\"Briefcase Key\",\"takeable\":true,\"key_id\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"observations\":\"A small key labeled 'Personal - Do Not Copy'\"}]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.081920"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.078539"], ["updated_at", "2025-11-28 23:58:19.078539"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Redirected to http://www.example.com/break_escape/games/1
+Completed 302 Found in 5ms (ActiveRecord: 0.3ms (4 queries, 0 cached) | GC: 2.7ms)
+ [1m[36mBreakEscape::Game Count (0.0ms)[0m [1m[34mSELECT COUNT(*) FROM "break_escape_games"[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_index_should_display_published_missions
+---------------------------------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.1ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.2ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 6.2ms | GC: 0.3ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 6.2ms | GC: 0.3ms)
+Completed 200 OK in 7ms (Views: 6.1ms | ActiveRecord: 0.3ms (5 queries, 1 cached) | GC: 0.3ms)
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-----------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_show_published_mission
+-----------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+Started GET "/break_escape/missions/418560898" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::MissionsController#show as HTML
+ Parameters: {"id"=>"418560898"}
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."player_type" = ? AND "break_escape_games"."player_id" = ? AND "break_escape_games"."mission_id" = ? LIMIT ?[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"scenario_brief\":\"Hi! You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.\",\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office1\"},\"npcs\":[{\"id\":\"neye_eve\",\"displayName\":\"Neye Eve\",\"storyPath\":\"scenarios/ink/neye-eve.json\",\"avatar\":\"assets/npc/avatars/npc_adversary.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\"},{\"id\":\"gossip_girl\",\"displayName\":\"Gossip Girl\",\"storyPath\":\"scenarios/ink/gossip-girl.json\",\"avatar\":\"assets/npc/avatars/npc_neutral.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"timedMessages\":[{\"delay\":5000,\"message\":\"Hey! 👋 Got any juicy gossip for me today?\",\"type\":\"text\"}]},{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"storyPath\":\"scenarios/ink/helper-npc.json\",\"avatar\":\"assets/npc/avatars/npc_helper.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"unlockable\":[\"secure_vault\",\"ceo\"],\"eventMappings\":[{\"eventPattern\":\"item_picked_up:lockpick\",\"targetKnot\":\"on_lockpick_pickup\",\"onceOnly\":true,\"cooldown\":0},{\"eventPattern\":\"minigame_completed\",\"targetKnot\":\"on_lockpick_success\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":10000},{\"eventPattern\":\"minigame_failed\",\"targetKnot\":\"on_lockpick_failed\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":15000},{\"eventPattern\":\"door_unlocked\",\"targetKnot\":\"on_door_unlocked\",\"cooldown\":8000},{\"eventPattern\":\"door_unlock_attempt\",\"targetKnot\":\"on_door_attempt\",\"cooldown\":12000},{\"eventPattern\":\"object_interacted\",\"targetKnot\":\"on_ceo_desk_interact\",\"condition\":\"data.objectType === 'desk_ceo'\",\"cooldown\":10000},{\"eventPattern\":\"item_picked_up:*\",\"targetKnot\":\"on_item_found\",\"cooldown\":20000},{\"eventPattern\":\"room_entered\",\"targetKnot\":\"on_room_entered\",\"cooldown\":45000,\"maxTriggers\":3},{\"eventPattern\":\"room_discovered\",\"targetKnot\":\"on_room_discovered\",\"cooldown\":15000,\"maxTriggers\":5},{\"eventPattern\":\"room_entered:ceo\",\"targetKnot\":\"on_ceo_office_entered\",\"onceOnly\":true}]}],\"objects\":[{\"type\":\"phone\",\"name\":\"Reception Phone\",\"takeable\":false,\"readable\":true,\"voice\":\"Hi, this is the IT Team. Security breach detected in server room. Changed access code to 4829.\",\"sender\":\"IT Team\",\"timestamp\":\"2:15 AM\",\"observations\":\"The reception phone's message light is blinking urgently\"},{\"type\":\"notes\",\"name\":\"Security Log\",\"takeable\":true,\"readable\":true,\"text\":\"Unusual after-hours access detected:\\n- CEO office: 11:30 PM\\n- Server room: 2:15 AM\\n- CEO office again: 3:45 AM\",\"observations\":\"A concerning security log from last night\"},{\"type\":\"pc\",\"name\":\"Reception Computer\",\"takeable\":false,\"lockType\":\"password\",\"passwordHint\":\"Optional hint text\",\"showHint\":true,\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"requires\":\"secret123\",\"observations\":\"The reception's computer, currently locked\",\"postitNote\":\"Password: secret123\",\"showPostit\":true,\"contents\":[{\"type\":\"text_file\",\"name\":\"Private\",\"takeable\":false,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\"}]},{\"type\":\"tablet\",\"name\":\"Tablet Device\",\"takeable\":true,\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"A locked tablet device that requires Bluetooth pairing\"},{\"type\":\"bluetooth_scanner\",\"name\":\"Bluetooth Scanner\",\"takeable\":true,\"observations\":\"A device for detecting nearby Bluetooth signals\",\"canScanBluetooth\":true},{\"type\":\"key\",\"name\":\"Office Key\",\"takeable\":true,\"key_id\":\"office1_key\",\"keyPins\":[65,25,65,25],\"observations\":\"A key to access the office areas\"},{\"type\":\"pin-cracker\",\"name\":\"PIN Cracker\",\"takeable\":true,\"observations\":\"A sophisticated device that can analyze PIN entry patterns and provide feedback on attempts\"},{\"type\":\"safe\",\"name\":\"Reception Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9573\",\"observations\":\"A small wall safe behind the reception desk. Looks like it needs a 4-digit code.\",\"contents\":[{\"type\":\"notes\",\"name\":\"IT Access Credentials\",\"takeable\":true,\"readable\":true,\"text\":\"Emergency IT Admin Credentials:\\nUsername: admin\\nPassword: ITsecure2024\\n\\nServer Room Backup Code: 4829\\nCEO Office Alarm Override: 1337\",\"observations\":\"Sensitive IT credentials that could be very useful\"}]}]},\"office1\":{\"type\":\"room_office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office1_key\",\"keyPins\":[65,25,65,25],\"difficulty\":\"easy\",\"door_sign\":\"4A Hot Desks\",\"connections\":{\"north\":[\"office2\",\"office3\"],\"south\":\"reception\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"requires\":\"password\",\"hasFingerprint\":true,\"fingerprintOwner\":\"ceo\",\"fingerprintDifficulty\":\"medium\",\"observations\":\"A computer with a cybersecurity alert on screen. There might be fingerprints on the keyboard.\"},{\"type\":\"notes\",\"name\":\"IT Memo\",\"takeable\":true,\"readable\":true,\"text\":\"URGENT: Multiple unauthorized access attempts detected from CEO's office IP address\",\"observations\":\"A concerning IT department memo\"},{\"type\":\"fingerprint_kit\",\"name\":\"Fingerprint Kit\",\"takeable\":true,\"observations\":\"A kit used for collecting fingerprints from surfaces\"}]},\"office2\":{\"type\":\"room_office\",\"connections\":{\"north\":\"ceo\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"office2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: office2024\",\"showPostit\":true,\"observations\":\"A standard office computer with a sticky note on the monitor\"},{\"type\":\"notes\",\"name\":\"Shredded Document\",\"takeable\":true,\"readable\":true,\"text\":\"Partially readable: '...offshore account...transfer complete...delete all traces...'\",\"observations\":\"A partially shredded document that someone failed to dispose of properly\"},{\"type\":\"key\",\"name\":\"CEO Office Key\",\"takeable\":true,\"key_id\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"observations\":\"A spare key to the CEO's office, carelessly left behind\"}]},\"office3\":{\"type\":\"room_office\",\"connections\":{\"north\":\"server1\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"IT Staff Computer\",\"takeable\":false,\"requires\":\"bluetooth\",\"lockType\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"An IT staff computer showing network security logs\"},{\"type\":\"notes\",\"name\":\"Network Logs\",\"takeable\":true,\"readable\":true,\"text\":\"Large data transfers detected to unknown external IPs - All originating from CEO's office\",\"observations\":\"Suspicious network activity logs\"}]},\"ceo\":{\"type\":\"room_ceo\",\"connections\":{\"north\":\"closet\",\"south\":\"office2\"},\"locked\":true,\"lockType\":\"key\",\"requires\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"difficulty\":\"easy\",\"objects\":[{\"type\":\"pc\",\"name\":\"CEO Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"ceo2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: ceo2024\",\"showPostit\":true,\"observations\":\"The CEO's laptop, still warm - recently used. A sticky note is attached to the screen.\"},{\"type\":\"suitcase\",\"name\":\"CEO Briefcase\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"difficulty\":\"medium\",\"observations\":\"An expensive leather briefcase with a sturdy lock\",\"contents\":[{\"type\":\"notes\",\"name\":\"Private Note\",\"takeable\":true,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\",\"observations\":\"A hastily written note on expensive paper\"},{\"type\":\"key\",\"name\":\"Safe Key\",\"takeable\":true,\"key_id\":\"safe_key\",\"keyPins\":[52,29,44,37],\"observations\":\"A heavy-duty safe key hidden behind server equipment\"}]},{\"type\":\"phone\",\"name\":\"CEO Phone\",\"takeable\":false,\"readable\":true,\"text\":\"Recent calls: 'Offshore Bank', 'Unknown', 'Data Buyer'\",\"sender\":\"Call Log\",\"timestamp\":\"Last 24 hours\",\"observations\":\"The CEO's phone shows suspicious recent calls\"}]},\"closet\":{\"type\":\"room_closet\",\"connections\":{\"south\":\"ceo\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"7391\",\"objects\":[{\"type\":\"safe\",\"name\":\"Hidden Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"safe_key\",\"keyPins\":[52,29,44,37],\"difficulty\":\"hard\",\"observations\":\"A well-hidden wall safe behind a painting\",\"contents\":[{\"type\":\"notes\",\"name\":\"Incriminating Documents\",\"takeable\":true,\"readable\":true,\"text\":\"Contract for sale of proprietary technology\\nBank transfers from competing companies\\nDetails of upcoming corporate espionage operations\",\"observations\":\"A folder containing damning evidence of corporate espionage. Congratulations! You've recovered the incriminating documents. flag{ceo_exfil_flag}\"}]}]},\"server1\":{\"type\":\"room_servers\",\"connections\":{\"south\":\"office3\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"4829\",\"objects\":[{\"type\":\"pc\",\"name\":\"Server Terminal\",\"takeable\":false,\"observations\":\"The main server terminal showing massive data exfiltration\"},{\"type\":\"key\",\"name\":\"Briefcase Key\",\"takeable\":true,\"key_id\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"observations\":\"A small key labeled 'Personal - Do Not Copy'\"}]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-28 23:58:19.096018"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-28 23:58:19.095324"], ["updated_at", "2025-11-28 23:58:19.095324"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Redirected to http://www.example.com/break_escape/games/1
+Completed 302 Found in 2ms (ActiveRecord: 0.3ms (4 queries, 0 cached) | GC: 0.1ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_get_index
+----------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.1ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 1.0ms | GC: 0.1ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 1.1ms | GC: 0.1ms)
+Completed 200 OK in 2ms (Views: 1.1ms | ActiveRecord: 0.1ms (5 queries, 1 cached) | GC: 0.1ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_index_should_return_HTML_with_mission_list
+------------------------------------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-28 23:58:19 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.0ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 0.8ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.9ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 1.0ms | ActiveRecord: 0.1ms (5 queries, 1 cached) | GC: 0.1ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mActiveRecord::InternalMetadata Load (0.1ms)[0m [1m[34mSELECT * FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ? ORDER BY "ar_internal_metadata"."key" ASC LIMIT 1[0m [[nil, "schema_sha1"]]
+ [1m[36mActiveRecord::SchemaMigration Load (0.0ms)[0m [1m[34mSELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+ [1m[35m (0.1ms)[0m [1m[35mPRAGMA foreign_keys[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys = ON[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_keys = OFF[0m
+ [1m[36mFixtures Load (0.1ms)[0m [1m[31mDELETE FROM "break_escape_demo_users";
+DELETE FROM "break_escape_missions";
+INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (149617800, 'test_user', '2025-11-29 00:05:46', '2025-11-29 00:05:46');
+INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (618102942, 'other_user', '2025-11-29 00:05:46', '2025-11-29 00:05:46');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (418560898, 'ceo_exfil', 'CEO Exfiltration', 'Test scenario', 1, 3, '2025-11-29 00:05:46', '2025-11-29 00:05:46');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (636030761, 'test_unpublished', 'Unpublished Test', 'Not visible', 0, 1, '2025-11-29 00:05:46', '2025-11-29 00:05:46');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at", "secgen_scenario", "collection") VALUES (899573729, 'secgen_vm_lab', 'SecGen VM Lab - Linux Introduction', 'Test VM and flag integration with SecGen scenario', 1, 2, '2025-11-29 00:05:46', '2025-11-29 00:05:46', 'labs/introducing_attacks/1_intro_linux.xml', 'vm_labs')[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys = 0[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_keys = 1[0m
+ [1m[36mTRANSACTION (0.7ms)[0m [1m[36mcommit transaction[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_key_check[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+--------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_inventory_endpoint_should_add_items
+--------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.1ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.2ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.078939"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.078851"], ["updated_at", "2025-11-29 00:05:46.078851"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Update (0.1ms)[0m [1m[33mUPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[{\"id\":\"note_1\",\"type\":\"note\",\"name\":\"Test Note\",\"takeable\":true}]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-29 00:05:46.079681"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/inventory" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#inventory as HTML
+ Parameters: {"action_type"=>"add", "item"=>{"type"=>"note", "name"=>"Test Note", "id"=>"note_1"}, "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.1ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] inventory endpoint: action=add, item=#"note", "name"=>"Test Note", "id"=>"note_1"} permitted: false>
+[BreakEscape] validate_item_collectible: type=note, id=note_1, name=Test Note
+[BreakEscape] Item collection valid: note
+[BreakEscape] Adding item to inventory: note / Test Note
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Update (0.0ms)[0m [1m[33mUPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"note\",\"name\":\"Test Note\",\"id\":\"note_1\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-29 00:05:46.091416"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+[BreakEscape] Item added successfully. Current inventory: [{"type"=>"note", "name"=>"Test Note", "id"=>"note_1"}]
+Completed 200 OK in 4ms (Views: 0.1ms | ActiveRecord: 0.2ms (5 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_return_404_for_non-existent_NPC
+------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.093673"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.093609"], ["updated_at", "2025-11-29 00:05:46.093609"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink?npc=non-existent" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"npc"=>"non-existent", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.1ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.1ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] Loading ink for NPC: non-existent
+[BreakEscape] No NPCs found in scenario data
+Completed 404 Not Found in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_return_404_for_NPC_without_story_file
+------------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.096315"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.096268"], ["updated_at", "2025-11-29 00:05:46.096268"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink?npc=missing-npc" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"npc"=>"missing-npc", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] Loading ink for NPC: missing-npc
+[BreakEscape] No NPCs found in scenario data
+Completed 404 Not Found in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_scenario_endpoint_should_return_JSON
+---------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.098650"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.098608"], ["updated_at", "2025-11-29 00:05:46.098608"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/scenario" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#scenario as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_show_should_inject_game_configuration
+----------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.100833"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.100794"], ["updated_at", "2025-11-29 00:05:46.100794"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 1.5ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 1.7ms | GC: 0.0ms)
+Completed 200 OK in 5ms (Views: 2.9ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_game_setup_has_correct_scenario_data
+---------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.107886"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.107849"], ["updated_at", "2025-11-29 00:05:46.107849"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_unlock_endpoint_should_accept_correct_pin_code
+-------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.108950"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.108914"], ["updated_at", "2025-11-29 00:05:46.108914"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#unlock as HTML
+ Parameters: {"targetType"=>"door", "targetId"=>"office", "attempt"=>"1234", "method"=>"pin", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] validate_unlock: type=door, id=office, attempt=1234, method=pin
+[BreakEscape] Room data: locked=true, lockType=pin, requires=1234
+[BreakEscape] Room is LOCKED, method must be valid: pin
+[BreakEscape] pin validation result: true
+[BreakEscape] validate_unlock returning: true
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Update (0.0ms)[0m [1m[33mUPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\",\"office\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-29 00:05:46.110537"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.2ms (5 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_sync_state_should_update_player_state_for_current_room
+---------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.111807"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.111769"], ["updated_at", "2025-11-29 00:05:46.111769"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started PUT "/break_escape/games/1/sync_state" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#sync_state as HTML
+ Parameters: {"currentRoom"=>"reception", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------
+BreakEscape::GamesControllerTest: test_should_show_game
+-------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.114443"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.114385"], ["updated_at", "2025-11-29 00:05:46.114385"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 0.1ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.2ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 0.3ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+--------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_require_npc_parameter
+--------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.117189"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.117146"], ["updated_at", "2025-11-29 00:05:46.117146"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+Completed 400 Bad Request in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_unlock_endpoint_should_reject_invalid_attempts
+-------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.119374"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.119334"], ["updated_at", "2025-11-29 00:05:46.119334"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#unlock as HTML
+ Parameters: {"targetType"=>"room", "targetId"=>"office", "attempt"=>"wrong_code", "method"=>"pin", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] validate_unlock: type=room, id=office, attempt=wrong_code, method=pin
+[BreakEscape] Object not found: office
+Completed 422 Unprocessable Content in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_show_should_return_HTML_with_game_container
+----------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.121588"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.121549"], ["updated_at", "2025-11-29 00:05:46.121549"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 0.1ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.2ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 0.3ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_create_game_and_redirect_when_showing_mission
+----------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Count (0.0ms)[0m [1m[34mSELECT COUNT(*) FROM "break_escape_games"[0m
+Started GET "/break_escape/missions/418560898" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::MissionsController#show as HTML
+ Parameters: {"id"=>"418560898"}
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Load (0.1ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."player_type" = ? AND "break_escape_games"."player_id" = ? AND "break_escape_games"."mission_id" = ? LIMIT ?[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.2ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"scenario_brief\":\"Hi! You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.\",\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office1\"},\"npcs\":[{\"id\":\"neye_eve\",\"displayName\":\"Neye Eve\",\"storyPath\":\"scenarios/ink/neye-eve.json\",\"avatar\":\"assets/npc/avatars/npc_adversary.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\"},{\"id\":\"gossip_girl\",\"displayName\":\"Gossip Girl\",\"storyPath\":\"scenarios/ink/gossip-girl.json\",\"avatar\":\"assets/npc/avatars/npc_neutral.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"timedMessages\":[{\"delay\":5000,\"message\":\"Hey! 👋 Got any juicy gossip for me today?\",\"type\":\"text\"}]},{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"storyPath\":\"scenarios/ink/helper-npc.json\",\"avatar\":\"assets/npc/avatars/npc_helper.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"unlockable\":[\"secure_vault\",\"ceo\"],\"eventMappings\":[{\"eventPattern\":\"item_picked_up:lockpick\",\"targetKnot\":\"on_lockpick_pickup\",\"onceOnly\":true,\"cooldown\":0},{\"eventPattern\":\"minigame_completed\",\"targetKnot\":\"on_lockpick_success\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":10000},{\"eventPattern\":\"minigame_failed\",\"targetKnot\":\"on_lockpick_failed\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":15000},{\"eventPattern\":\"door_unlocked\",\"targetKnot\":\"on_door_unlocked\",\"cooldown\":8000},{\"eventPattern\":\"door_unlock_attempt\",\"targetKnot\":\"on_door_attempt\",\"cooldown\":12000},{\"eventPattern\":\"object_interacted\",\"targetKnot\":\"on_ceo_desk_interact\",\"condition\":\"data.objectType === 'desk_ceo'\",\"cooldown\":10000},{\"eventPattern\":\"item_picked_up:*\",\"targetKnot\":\"on_item_found\",\"cooldown\":20000},{\"eventPattern\":\"room_entered\",\"targetKnot\":\"on_room_entered\",\"cooldown\":45000,\"maxTriggers\":3},{\"eventPattern\":\"room_discovered\",\"targetKnot\":\"on_room_discovered\",\"cooldown\":15000,\"maxTriggers\":5},{\"eventPattern\":\"room_entered:ceo\",\"targetKnot\":\"on_ceo_office_entered\",\"onceOnly\":true}]}],\"objects\":[{\"type\":\"phone\",\"name\":\"Reception Phone\",\"takeable\":false,\"readable\":true,\"voice\":\"Hi, this is the IT Team. Security breach detected in server room. Changed access code to 4829.\",\"sender\":\"IT Team\",\"timestamp\":\"2:15 AM\",\"observations\":\"The reception phone's message light is blinking urgently\"},{\"type\":\"notes\",\"name\":\"Security Log\",\"takeable\":true,\"readable\":true,\"text\":\"Unusual after-hours access detected:\\n- CEO office: 11:30 PM\\n- Server room: 2:15 AM\\n- CEO office again: 3:45 AM\",\"observations\":\"A concerning security log from last night\"},{\"type\":\"pc\",\"name\":\"Reception Computer\",\"takeable\":false,\"lockType\":\"password\",\"passwordHint\":\"Optional hint text\",\"showHint\":true,\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"requires\":\"secret123\",\"observations\":\"The reception's computer, currently locked\",\"postitNote\":\"Password: secret123\",\"showPostit\":true,\"contents\":[{\"type\":\"text_file\",\"name\":\"Private\",\"takeable\":false,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\"}]},{\"type\":\"tablet\",\"name\":\"Tablet Device\",\"takeable\":true,\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"A locked tablet device that requires Bluetooth pairing\"},{\"type\":\"bluetooth_scanner\",\"name\":\"Bluetooth Scanner\",\"takeable\":true,\"observations\":\"A device for detecting nearby Bluetooth signals\",\"canScanBluetooth\":true},{\"type\":\"key\",\"name\":\"Office Key\",\"takeable\":true,\"key_id\":\"office1_key\",\"keyPins\":[65,25,65,25],\"observations\":\"A key to access the office areas\"},{\"type\":\"pin-cracker\",\"name\":\"PIN Cracker\",\"takeable\":true,\"observations\":\"A sophisticated device that can analyze PIN entry patterns and provide feedback on attempts\"},{\"type\":\"safe\",\"name\":\"Reception Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9573\",\"observations\":\"A small wall safe behind the reception desk. Looks like it needs a 4-digit code.\",\"contents\":[{\"type\":\"notes\",\"name\":\"IT Access Credentials\",\"takeable\":true,\"readable\":true,\"text\":\"Emergency IT Admin Credentials:\\nUsername: admin\\nPassword: ITsecure2024\\n\\nServer Room Backup Code: 4829\\nCEO Office Alarm Override: 1337\",\"observations\":\"Sensitive IT credentials that could be very useful\"}]}]},\"office1\":{\"type\":\"room_office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office1_key\",\"keyPins\":[65,25,65,25],\"difficulty\":\"easy\",\"door_sign\":\"4A Hot Desks\",\"connections\":{\"north\":[\"office2\",\"office3\"],\"south\":\"reception\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"requires\":\"password\",\"hasFingerprint\":true,\"fingerprintOwner\":\"ceo\",\"fingerprintDifficulty\":\"medium\",\"observations\":\"A computer with a cybersecurity alert on screen. There might be fingerprints on the keyboard.\"},{\"type\":\"notes\",\"name\":\"IT Memo\",\"takeable\":true,\"readable\":true,\"text\":\"URGENT: Multiple unauthorized access attempts detected from CEO's office IP address\",\"observations\":\"A concerning IT department memo\"},{\"type\":\"fingerprint_kit\",\"name\":\"Fingerprint Kit\",\"takeable\":true,\"observations\":\"A kit used for collecting fingerprints from surfaces\"}]},\"office2\":{\"type\":\"room_office\",\"connections\":{\"north\":\"ceo\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"office2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: office2024\",\"showPostit\":true,\"observations\":\"A standard office computer with a sticky note on the monitor\"},{\"type\":\"notes\",\"name\":\"Shredded Document\",\"takeable\":true,\"readable\":true,\"text\":\"Partially readable: '...offshore account...transfer complete...delete all traces...'\",\"observations\":\"A partially shredded document that someone failed to dispose of properly\"},{\"type\":\"key\",\"name\":\"CEO Office Key\",\"takeable\":true,\"key_id\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"observations\":\"A spare key to the CEO's office, carelessly left behind\"}]},\"office3\":{\"type\":\"room_office\",\"connections\":{\"north\":\"server1\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"IT Staff Computer\",\"takeable\":false,\"requires\":\"bluetooth\",\"lockType\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"An IT staff computer showing network security logs\"},{\"type\":\"notes\",\"name\":\"Network Logs\",\"takeable\":true,\"readable\":true,\"text\":\"Large data transfers detected to unknown external IPs - All originating from CEO's office\",\"observations\":\"Suspicious network activity logs\"}]},\"ceo\":{\"type\":\"room_ceo\",\"connections\":{\"north\":\"closet\",\"south\":\"office2\"},\"locked\":true,\"lockType\":\"key\",\"requires\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"difficulty\":\"easy\",\"objects\":[{\"type\":\"pc\",\"name\":\"CEO Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"ceo2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: ceo2024\",\"showPostit\":true,\"observations\":\"The CEO's laptop, still warm - recently used. A sticky note is attached to the screen.\"},{\"type\":\"suitcase\",\"name\":\"CEO Briefcase\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"difficulty\":\"medium\",\"observations\":\"An expensive leather briefcase with a sturdy lock\",\"contents\":[{\"type\":\"notes\",\"name\":\"Private Note\",\"takeable\":true,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\",\"observations\":\"A hastily written note on expensive paper\"},{\"type\":\"key\",\"name\":\"Safe Key\",\"takeable\":true,\"key_id\":\"safe_key\",\"keyPins\":[52,29,44,37],\"observations\":\"A heavy-duty safe key hidden behind server equipment\"}]},{\"type\":\"phone\",\"name\":\"CEO Phone\",\"takeable\":false,\"readable\":true,\"text\":\"Recent calls: 'Offshore Bank', 'Unknown', 'Data Buyer'\",\"sender\":\"Call Log\",\"timestamp\":\"Last 24 hours\",\"observations\":\"The CEO's phone shows suspicious recent calls\"}]},\"closet\":{\"type\":\"room_closet\",\"connections\":{\"south\":\"ceo\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"7391\",\"objects\":[{\"type\":\"safe\",\"name\":\"Hidden Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"safe_key\",\"keyPins\":[52,29,44,37],\"difficulty\":\"hard\",\"observations\":\"A well-hidden wall safe behind a painting\",\"contents\":[{\"type\":\"notes\",\"name\":\"Incriminating Documents\",\"takeable\":true,\"readable\":true,\"text\":\"Contract for sale of proprietary technology\\nBank transfers from competing companies\\nDetails of upcoming corporate espionage operations\",\"observations\":\"A folder containing damning evidence of corporate espionage. Congratulations! You've recovered the incriminating documents. flag{ceo_exfil_flag}\"}]}]},\"server1\":{\"type\":\"room_servers\",\"connections\":{\"south\":\"office3\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"4829\",\"objects\":[{\"type\":\"pc\",\"name\":\"Server Terminal\",\"takeable\":false,\"observations\":\"The main server terminal showing massive data exfiltration\"},{\"type\":\"key\",\"name\":\"Briefcase Key\",\"takeable\":true,\"key_id\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"observations\":\"A small key labeled 'Personal - Do Not Copy'\"}]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.129973"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.129238"], ["updated_at", "2025-11-29 00:05:46.129238"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.2ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Redirected to http://www.example.com/break_escape/games/1
+Completed 302 Found in 6ms (ActiveRecord: 0.5ms (4 queries, 0 cached) | GC: 2.7ms)
+ [1m[36mBreakEscape::Game Count (0.0ms)[0m [1m[34mSELECT COUNT(*) FROM "break_escape_games"[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_get_index
+----------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.1ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.2ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 6.1ms | GC: 0.2ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 6.3ms | GC: 0.4ms)
+Completed 200 OK in 7ms (Views: 6.1ms | ActiveRecord: 0.4ms (5 queries, 1 cached) | GC: 0.4ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_index_should_return_HTML_with_mission_list
+------------------------------------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.0ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 0.8ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.8ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 0.8ms | ActiveRecord: 0.1ms (5 queries, 1 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-----------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_show_published_mission
+-----------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+Started GET "/break_escape/missions/418560898" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::MissionsController#show as HTML
+ Parameters: {"id"=>"418560898"}
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."player_type" = ? AND "break_escape_games"."player_id" = ? AND "break_escape_games"."mission_id" = ? LIMIT ?[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"scenario_brief\":\"Hi! You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.\",\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office1\"},\"npcs\":[{\"id\":\"neye_eve\",\"displayName\":\"Neye Eve\",\"storyPath\":\"scenarios/ink/neye-eve.json\",\"avatar\":\"assets/npc/avatars/npc_adversary.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\"},{\"id\":\"gossip_girl\",\"displayName\":\"Gossip Girl\",\"storyPath\":\"scenarios/ink/gossip-girl.json\",\"avatar\":\"assets/npc/avatars/npc_neutral.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"timedMessages\":[{\"delay\":5000,\"message\":\"Hey! 👋 Got any juicy gossip for me today?\",\"type\":\"text\"}]},{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"storyPath\":\"scenarios/ink/helper-npc.json\",\"avatar\":\"assets/npc/avatars/npc_helper.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"unlockable\":[\"secure_vault\",\"ceo\"],\"eventMappings\":[{\"eventPattern\":\"item_picked_up:lockpick\",\"targetKnot\":\"on_lockpick_pickup\",\"onceOnly\":true,\"cooldown\":0},{\"eventPattern\":\"minigame_completed\",\"targetKnot\":\"on_lockpick_success\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":10000},{\"eventPattern\":\"minigame_failed\",\"targetKnot\":\"on_lockpick_failed\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":15000},{\"eventPattern\":\"door_unlocked\",\"targetKnot\":\"on_door_unlocked\",\"cooldown\":8000},{\"eventPattern\":\"door_unlock_attempt\",\"targetKnot\":\"on_door_attempt\",\"cooldown\":12000},{\"eventPattern\":\"object_interacted\",\"targetKnot\":\"on_ceo_desk_interact\",\"condition\":\"data.objectType === 'desk_ceo'\",\"cooldown\":10000},{\"eventPattern\":\"item_picked_up:*\",\"targetKnot\":\"on_item_found\",\"cooldown\":20000},{\"eventPattern\":\"room_entered\",\"targetKnot\":\"on_room_entered\",\"cooldown\":45000,\"maxTriggers\":3},{\"eventPattern\":\"room_discovered\",\"targetKnot\":\"on_room_discovered\",\"cooldown\":15000,\"maxTriggers\":5},{\"eventPattern\":\"room_entered:ceo\",\"targetKnot\":\"on_ceo_office_entered\",\"onceOnly\":true}]}],\"objects\":[{\"type\":\"phone\",\"name\":\"Reception Phone\",\"takeable\":false,\"readable\":true,\"voice\":\"Hi, this is the IT Team. Security breach detected in server room. Changed access code to 4829.\",\"sender\":\"IT Team\",\"timestamp\":\"2:15 AM\",\"observations\":\"The reception phone's message light is blinking urgently\"},{\"type\":\"notes\",\"name\":\"Security Log\",\"takeable\":true,\"readable\":true,\"text\":\"Unusual after-hours access detected:\\n- CEO office: 11:30 PM\\n- Server room: 2:15 AM\\n- CEO office again: 3:45 AM\",\"observations\":\"A concerning security log from last night\"},{\"type\":\"pc\",\"name\":\"Reception Computer\",\"takeable\":false,\"lockType\":\"password\",\"passwordHint\":\"Optional hint text\",\"showHint\":true,\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"requires\":\"secret123\",\"observations\":\"The reception's computer, currently locked\",\"postitNote\":\"Password: secret123\",\"showPostit\":true,\"contents\":[{\"type\":\"text_file\",\"name\":\"Private\",\"takeable\":false,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\"}]},{\"type\":\"tablet\",\"name\":\"Tablet Device\",\"takeable\":true,\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"A locked tablet device that requires Bluetooth pairing\"},{\"type\":\"bluetooth_scanner\",\"name\":\"Bluetooth Scanner\",\"takeable\":true,\"observations\":\"A device for detecting nearby Bluetooth signals\",\"canScanBluetooth\":true},{\"type\":\"key\",\"name\":\"Office Key\",\"takeable\":true,\"key_id\":\"office1_key\",\"keyPins\":[65,25,65,25],\"observations\":\"A key to access the office areas\"},{\"type\":\"pin-cracker\",\"name\":\"PIN Cracker\",\"takeable\":true,\"observations\":\"A sophisticated device that can analyze PIN entry patterns and provide feedback on attempts\"},{\"type\":\"safe\",\"name\":\"Reception Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9573\",\"observations\":\"A small wall safe behind the reception desk. Looks like it needs a 4-digit code.\",\"contents\":[{\"type\":\"notes\",\"name\":\"IT Access Credentials\",\"takeable\":true,\"readable\":true,\"text\":\"Emergency IT Admin Credentials:\\nUsername: admin\\nPassword: ITsecure2024\\n\\nServer Room Backup Code: 4829\\nCEO Office Alarm Override: 1337\",\"observations\":\"Sensitive IT credentials that could be very useful\"}]}]},\"office1\":{\"type\":\"room_office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office1_key\",\"keyPins\":[65,25,65,25],\"difficulty\":\"easy\",\"door_sign\":\"4A Hot Desks\",\"connections\":{\"north\":[\"office2\",\"office3\"],\"south\":\"reception\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"requires\":\"password\",\"hasFingerprint\":true,\"fingerprintOwner\":\"ceo\",\"fingerprintDifficulty\":\"medium\",\"observations\":\"A computer with a cybersecurity alert on screen. There might be fingerprints on the keyboard.\"},{\"type\":\"notes\",\"name\":\"IT Memo\",\"takeable\":true,\"readable\":true,\"text\":\"URGENT: Multiple unauthorized access attempts detected from CEO's office IP address\",\"observations\":\"A concerning IT department memo\"},{\"type\":\"fingerprint_kit\",\"name\":\"Fingerprint Kit\",\"takeable\":true,\"observations\":\"A kit used for collecting fingerprints from surfaces\"}]},\"office2\":{\"type\":\"room_office\",\"connections\":{\"north\":\"ceo\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"office2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: office2024\",\"showPostit\":true,\"observations\":\"A standard office computer with a sticky note on the monitor\"},{\"type\":\"notes\",\"name\":\"Shredded Document\",\"takeable\":true,\"readable\":true,\"text\":\"Partially readable: '...offshore account...transfer complete...delete all traces...'\",\"observations\":\"A partially shredded document that someone failed to dispose of properly\"},{\"type\":\"key\",\"name\":\"CEO Office Key\",\"takeable\":true,\"key_id\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"observations\":\"A spare key to the CEO's office, carelessly left behind\"}]},\"office3\":{\"type\":\"room_office\",\"connections\":{\"north\":\"server1\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"IT Staff Computer\",\"takeable\":false,\"requires\":\"bluetooth\",\"lockType\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"An IT staff computer showing network security logs\"},{\"type\":\"notes\",\"name\":\"Network Logs\",\"takeable\":true,\"readable\":true,\"text\":\"Large data transfers detected to unknown external IPs - All originating from CEO's office\",\"observations\":\"Suspicious network activity logs\"}]},\"ceo\":{\"type\":\"room_ceo\",\"connections\":{\"north\":\"closet\",\"south\":\"office2\"},\"locked\":true,\"lockType\":\"key\",\"requires\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"difficulty\":\"easy\",\"objects\":[{\"type\":\"pc\",\"name\":\"CEO Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"ceo2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: ceo2024\",\"showPostit\":true,\"observations\":\"The CEO's laptop, still warm - recently used. A sticky note is attached to the screen.\"},{\"type\":\"suitcase\",\"name\":\"CEO Briefcase\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"difficulty\":\"medium\",\"observations\":\"An expensive leather briefcase with a sturdy lock\",\"contents\":[{\"type\":\"notes\",\"name\":\"Private Note\",\"takeable\":true,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\",\"observations\":\"A hastily written note on expensive paper\"},{\"type\":\"key\",\"name\":\"Safe Key\",\"takeable\":true,\"key_id\":\"safe_key\",\"keyPins\":[52,29,44,37],\"observations\":\"A heavy-duty safe key hidden behind server equipment\"}]},{\"type\":\"phone\",\"name\":\"CEO Phone\",\"takeable\":false,\"readable\":true,\"text\":\"Recent calls: 'Offshore Bank', 'Unknown', 'Data Buyer'\",\"sender\":\"Call Log\",\"timestamp\":\"Last 24 hours\",\"observations\":\"The CEO's phone shows suspicious recent calls\"}]},\"closet\":{\"type\":\"room_closet\",\"connections\":{\"south\":\"ceo\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"7391\",\"objects\":[{\"type\":\"safe\",\"name\":\"Hidden Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"safe_key\",\"keyPins\":[52,29,44,37],\"difficulty\":\"hard\",\"observations\":\"A well-hidden wall safe behind a painting\",\"contents\":[{\"type\":\"notes\",\"name\":\"Incriminating Documents\",\"takeable\":true,\"readable\":true,\"text\":\"Contract for sale of proprietary technology\\nBank transfers from competing companies\\nDetails of upcoming corporate espionage operations\",\"observations\":\"A folder containing damning evidence of corporate espionage. Congratulations! You've recovered the incriminating documents. flag{ceo_exfil_flag}\"}]}]},\"server1\":{\"type\":\"room_servers\",\"connections\":{\"south\":\"office3\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"4829\",\"objects\":[{\"type\":\"pc\",\"name\":\"Server Terminal\",\"takeable\":false,\"observations\":\"The main server terminal showing massive data exfiltration\"},{\"type\":\"key\",\"name\":\"Briefcase Key\",\"takeable\":true,\"key_id\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"observations\":\"A small key labeled 'Personal - Do Not Copy'\"}]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:05:46.143510"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:05:46.142826"], ["updated_at", "2025-11-29 00:05:46.142826"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Redirected to http://www.example.com/break_escape/games/1
+Completed 302 Found in 2ms (ActiveRecord: 0.2ms (4 queries, 0 cached) | GC: 0.1ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_index_should_display_published_missions
+---------------------------------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-29 00:05:46 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.1ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 0.9ms | GC: 0.1ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.9ms | GC: 0.1ms)
+Completed 200 OK in 1ms (Views: 0.9ms | ActiveRecord: 0.1ms (5 queries, 1 cached) | GC: 0.1ms)
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mActiveRecord::InternalMetadata Load (0.1ms)[0m [1m[34mSELECT * FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = ? ORDER BY "ar_internal_metadata"."key" ASC LIMIT 1[0m [[nil, "schema_sha1"]]
+ [1m[36mActiveRecord::SchemaMigration Load (0.0ms)[0m [1m[34mSELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+ [1m[35m (0.1ms)[0m [1m[35mPRAGMA foreign_keys[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys = ON[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_keys = OFF[0m
+ [1m[36mFixtures Load (0.1ms)[0m [1m[31mDELETE FROM "break_escape_demo_users";
+DELETE FROM "break_escape_missions";
+INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (149617800, 'test_user', '2025-11-29 00:39:42', '2025-11-29 00:39:42');
+INSERT INTO "break_escape_demo_users" ("id", "handle", "created_at", "updated_at") VALUES (618102942, 'other_user', '2025-11-29 00:39:42', '2025-11-29 00:39:42');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (418560898, 'ceo_exfil', 'CEO Exfiltration', 'Test scenario', 1, 3, '2025-11-29 00:39:42', '2025-11-29 00:39:42');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at") VALUES (636030761, 'test_unpublished', 'Unpublished Test', 'Not visible', 0, 1, '2025-11-29 00:39:42', '2025-11-29 00:39:42');
+INSERT INTO "break_escape_missions" ("id", "name", "display_name", "description", "published", "difficulty_level", "created_at", "updated_at", "secgen_scenario", "collection") VALUES (899573729, 'secgen_vm_lab', 'SecGen VM Lab - Linux Introduction', 'Test VM and flag integration with SecGen scenario', 1, 2, '2025-11-29 00:39:42', '2025-11-29 00:39:42', 'labs/introducing_attacks/1_intro_linux.xml', 'vm_labs')[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA defer_foreign_keys = 0[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_keys = 1[0m
+ [1m[36mTRANSACTION (0.8ms)[0m [1m[36mcommit transaction[0m
+ [1m[35m (0.0ms)[0m [1m[35mPRAGMA foreign_key_check[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_unlock_endpoint_should_reject_invalid_attempts
+-------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.2ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.002409"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.002321"], ["updated_at", "2025-11-29 00:39:43.002321"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#unlock as HTML
+ Parameters: {"targetType"=>"room", "targetId"=>"office", "attempt"=>"wrong_code", "method"=>"pin", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.1ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] validate_unlock: type=room, id=office, attempt=wrong_code, method=pin
+[BreakEscape] Object not found: office
+Completed 422 Unprocessable Content in 3ms (Views: 0.1ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_sync_state_should_update_player_state_for_current_room
+---------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.015875"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.015819"], ["updated_at", "2025-11-29 00:39:43.015819"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started PUT "/break_escape/games/1/sync_state" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#sync_state as HTML
+ Parameters: {"currentRoom"=>"reception", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.2ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_unlock_endpoint_should_accept_correct_pin_code
+-------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.019223"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.019179"], ["updated_at", "2025-11-29 00:39:43.019179"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/unlock" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#unlock as HTML
+ Parameters: {"targetType"=>"door", "targetId"=>"office", "attempt"=>"1234", "method"=>"pin", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] validate_unlock: type=door, id=office, attempt=1234, method=pin
+[BreakEscape] Room data: locked=true, lockType=pin, requires=1234
+[BreakEscape] Room is LOCKED, method must be valid: pin
+[BreakEscape] pin validation result: true
+[BreakEscape] validate_unlock returning: true
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Update (0.0ms)[0m [1m[33mUPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\",\"office\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-29 00:39:43.020984"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.2ms (5 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_scenario_endpoint_should_return_JSON
+---------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.022384"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.022347"], ["updated_at", "2025-11-29 00:39:43.022347"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/scenario" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#scenario as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_show_should_inject_game_configuration
+----------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.024516"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.024479"], ["updated_at", "2025-11-29 00:39:43.024479"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 1.5ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 1.7ms | GC: 0.0ms)
+Completed 200 OK in 5ms (Views: 3.0ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-------------------------------------------------------
+BreakEscape::GamesControllerTest: test_should_show_game
+-------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.032069"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.032019"], ["updated_at", "2025-11-29 00:39:43.032019"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 0.1ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.1ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 0.3ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_return_404_for_non-existent_NPC
+------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.035027"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.034967"], ["updated_at", "2025-11-29 00:39:43.034967"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink?npc=non-existent" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"npc"=>"non-existent", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] Loading ink for NPC: non-existent
+[BreakEscape] No NPCs found in scenario data
+Completed 404 Not Found in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+--------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_inventory_endpoint_should_add_items
+--------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.037309"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.037269"], ["updated_at", "2025-11-29 00:39:43.037269"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Update (0.1ms)[0m [1m[33mUPDATE "break_escape_games" SET "scenario_data" = ?, "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[{\"id\":\"note_1\",\"type\":\"note\",\"name\":\"Test Note\",\"takeable\":true}]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-29 00:39:43.037849"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started POST "/break_escape/games/1/inventory" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#inventory as HTML
+ Parameters: {"action_type"=>"add", "item"=>{"type"=>"note", "name"=>"Test Note", "id"=>"note_1"}, "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] inventory endpoint: action=add, item=#"note", "name"=>"Test Note", "id"=>"note_1"} permitted: false>
+[BreakEscape] validate_item_collectible: type=note, id=note_1, name=Test Note
+[BreakEscape] Item collection valid: note
+[BreakEscape] Adding item to inventory: note / Test Note
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Mission Load (0.1ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Update (0.0ms)[0m [1m[33mUPDATE "break_escape_games" SET "player_state" = ?, "updated_at" = ? WHERE "break_escape_games"."id" = ?[0m [["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"note\",\"name\":\"Test Note\",\"id\":\"note_1\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["updated_at", "2025-11-29 00:39:43.039435"], ["id", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+[BreakEscape] Item added successfully. Current inventory: [{"type"=>"note", "name"=>"Test Note", "id"=>"note_1"}]
+Completed 200 OK in 1ms (Views: 0.0ms | ActiveRecord: 0.2ms (5 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_return_404_for_NPC_without_story_file
+------------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.040707"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.040668"], ["updated_at", "2025-11-29 00:39:43.040668"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink?npc=missing-npc" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"npc"=>"missing-npc", "id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+[BreakEscape] Loading ink for NPC: missing-npc
+[BreakEscape] No NPCs found in scenario data
+Completed 404 Not Found in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_show_should_return_HTML_with_game_container
+----------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.042814"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.042775"], ["updated_at", "2025-11-29 00:39:43.042775"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#show as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/games/show.html.erb within layouts/break_escape/application (Duration: 0.1ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.1ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 0.2ms | ActiveRecord: 0.1ms (4 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+--------------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_ink_endpoint_should_require_npc_parameter
+--------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.045920"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.045873"], ["updated_at", "2025-11-29 00:39:43.045873"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Started GET "/break_escape/games/1/ink" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::GamesController#ink as HTML
+ Parameters: {"id"=>"1"}
+ [1m[36mBreakEscape::Game Load (0.1ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."id" = ? LIMIT ?[0m [["id", 1], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+Completed 400 Bad Request in 1ms (Views: 0.0ms | ActiveRecord: 0.1ms (3 queries, 0 cached) | GC: 0.0ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------
+BreakEscape::GamesControllerTest: test_game_setup_has_correct_scenario_data
+---------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" WHERE "break_escape_demo_users"."id" = ? LIMIT ?[0m [["id", 149617800], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.1ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office\"},\"locked\":false,\"objects\":[]},\"office\":{\"type\":\"office\",\"connections\":{\"south\":\"reception\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"1234\",\"objects\":[]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"lockpick\",\"name\":\"Lockpick\",\"id\":\"lockpick_1\",\"takeable\":true}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.048198"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.048155"], ["updated_at", "2025-11-29 00:39:43.048155"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+-----------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_show_published_mission
+-----------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+Started GET "/break_escape/missions/418560898" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::MissionsController#show as HTML
+ Parameters: {"id"=>"418560898"}
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Load (0.1ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."player_type" = ? AND "break_escape_games"."player_id" = ? AND "break_escape_games"."mission_id" = ? LIMIT ?[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.2ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"scenario_brief\":\"Hi! You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.\",\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office1\"},\"npcs\":[{\"id\":\"neye_eve\",\"displayName\":\"Neye Eve\",\"storyPath\":\"scenarios/ink/neye-eve.json\",\"avatar\":\"assets/npc/avatars/npc_adversary.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\"},{\"id\":\"gossip_girl\",\"displayName\":\"Gossip Girl\",\"storyPath\":\"scenarios/ink/gossip-girl.json\",\"avatar\":\"assets/npc/avatars/npc_neutral.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"timedMessages\":[{\"delay\":5000,\"message\":\"Hey! 👋 Got any juicy gossip for me today?\",\"type\":\"text\"}]},{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"storyPath\":\"scenarios/ink/helper-npc.json\",\"avatar\":\"assets/npc/avatars/npc_helper.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"unlockable\":[\"secure_vault\",\"ceo\"],\"eventMappings\":[{\"eventPattern\":\"item_picked_up:lockpick\",\"targetKnot\":\"on_lockpick_pickup\",\"onceOnly\":true,\"cooldown\":0},{\"eventPattern\":\"minigame_completed\",\"targetKnot\":\"on_lockpick_success\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":10000},{\"eventPattern\":\"minigame_failed\",\"targetKnot\":\"on_lockpick_failed\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":15000},{\"eventPattern\":\"door_unlocked\",\"targetKnot\":\"on_door_unlocked\",\"cooldown\":8000},{\"eventPattern\":\"door_unlock_attempt\",\"targetKnot\":\"on_door_attempt\",\"cooldown\":12000},{\"eventPattern\":\"object_interacted\",\"targetKnot\":\"on_ceo_desk_interact\",\"condition\":\"data.objectType === 'desk_ceo'\",\"cooldown\":10000},{\"eventPattern\":\"item_picked_up:*\",\"targetKnot\":\"on_item_found\",\"cooldown\":20000},{\"eventPattern\":\"room_entered\",\"targetKnot\":\"on_room_entered\",\"cooldown\":45000,\"maxTriggers\":3},{\"eventPattern\":\"room_discovered\",\"targetKnot\":\"on_room_discovered\",\"cooldown\":15000,\"maxTriggers\":5},{\"eventPattern\":\"room_entered:ceo\",\"targetKnot\":\"on_ceo_office_entered\",\"onceOnly\":true}]}],\"objects\":[{\"type\":\"phone\",\"name\":\"Reception Phone\",\"takeable\":false,\"readable\":true,\"voice\":\"Hi, this is the IT Team. Security breach detected in server room. Changed access code to 4829.\",\"sender\":\"IT Team\",\"timestamp\":\"2:15 AM\",\"observations\":\"The reception phone's message light is blinking urgently\"},{\"type\":\"notes\",\"name\":\"Security Log\",\"takeable\":true,\"readable\":true,\"text\":\"Unusual after-hours access detected:\\n- CEO office: 11:30 PM\\n- Server room: 2:15 AM\\n- CEO office again: 3:45 AM\",\"observations\":\"A concerning security log from last night\"},{\"type\":\"pc\",\"name\":\"Reception Computer\",\"takeable\":false,\"lockType\":\"password\",\"passwordHint\":\"Optional hint text\",\"showHint\":true,\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"requires\":\"secret123\",\"observations\":\"The reception's computer, currently locked\",\"postitNote\":\"Password: secret123\",\"showPostit\":true,\"contents\":[{\"type\":\"text_file\",\"name\":\"Private\",\"takeable\":false,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\"}]},{\"type\":\"tablet\",\"name\":\"Tablet Device\",\"takeable\":true,\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"A locked tablet device that requires Bluetooth pairing\"},{\"type\":\"bluetooth_scanner\",\"name\":\"Bluetooth Scanner\",\"takeable\":true,\"observations\":\"A device for detecting nearby Bluetooth signals\",\"canScanBluetooth\":true},{\"type\":\"key\",\"name\":\"Office Key\",\"takeable\":true,\"key_id\":\"office1_key\",\"keyPins\":[65,25,65,25],\"observations\":\"A key to access the office areas\"},{\"type\":\"pin-cracker\",\"name\":\"PIN Cracker\",\"takeable\":true,\"observations\":\"A sophisticated device that can analyze PIN entry patterns and provide feedback on attempts\"},{\"type\":\"safe\",\"name\":\"Reception Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9573\",\"observations\":\"A small wall safe behind the reception desk. Looks like it needs a 4-digit code.\",\"contents\":[{\"type\":\"notes\",\"name\":\"IT Access Credentials\",\"takeable\":true,\"readable\":true,\"text\":\"Emergency IT Admin Credentials:\\nUsername: admin\\nPassword: ITsecure2024\\n\\nServer Room Backup Code: 4829\\nCEO Office Alarm Override: 1337\",\"observations\":\"Sensitive IT credentials that could be very useful\"}]}]},\"office1\":{\"type\":\"room_office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office1_key\",\"keyPins\":[65,25,65,25],\"difficulty\":\"easy\",\"door_sign\":\"4A Hot Desks\",\"connections\":{\"north\":[\"office2\",\"office3\"],\"south\":\"reception\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"requires\":\"password\",\"hasFingerprint\":true,\"fingerprintOwner\":\"ceo\",\"fingerprintDifficulty\":\"medium\",\"observations\":\"A computer with a cybersecurity alert on screen. There might be fingerprints on the keyboard.\"},{\"type\":\"notes\",\"name\":\"IT Memo\",\"takeable\":true,\"readable\":true,\"text\":\"URGENT: Multiple unauthorized access attempts detected from CEO's office IP address\",\"observations\":\"A concerning IT department memo\"},{\"type\":\"fingerprint_kit\",\"name\":\"Fingerprint Kit\",\"takeable\":true,\"observations\":\"A kit used for collecting fingerprints from surfaces\"}]},\"office2\":{\"type\":\"room_office\",\"connections\":{\"north\":\"ceo\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"office2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: office2024\",\"showPostit\":true,\"observations\":\"A standard office computer with a sticky note on the monitor\"},{\"type\":\"notes\",\"name\":\"Shredded Document\",\"takeable\":true,\"readable\":true,\"text\":\"Partially readable: '...offshore account...transfer complete...delete all traces...'\",\"observations\":\"A partially shredded document that someone failed to dispose of properly\"},{\"type\":\"key\",\"name\":\"CEO Office Key\",\"takeable\":true,\"key_id\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"observations\":\"A spare key to the CEO's office, carelessly left behind\"}]},\"office3\":{\"type\":\"room_office\",\"connections\":{\"north\":\"server1\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"IT Staff Computer\",\"takeable\":false,\"requires\":\"bluetooth\",\"lockType\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"An IT staff computer showing network security logs\"},{\"type\":\"notes\",\"name\":\"Network Logs\",\"takeable\":true,\"readable\":true,\"text\":\"Large data transfers detected to unknown external IPs - All originating from CEO's office\",\"observations\":\"Suspicious network activity logs\"}]},\"ceo\":{\"type\":\"room_ceo\",\"connections\":{\"north\":\"closet\",\"south\":\"office2\"},\"locked\":true,\"lockType\":\"key\",\"requires\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"difficulty\":\"easy\",\"objects\":[{\"type\":\"pc\",\"name\":\"CEO Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"ceo2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: ceo2024\",\"showPostit\":true,\"observations\":\"The CEO's laptop, still warm - recently used. A sticky note is attached to the screen.\"},{\"type\":\"suitcase\",\"name\":\"CEO Briefcase\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"difficulty\":\"medium\",\"observations\":\"An expensive leather briefcase with a sturdy lock\",\"contents\":[{\"type\":\"notes\",\"name\":\"Private Note\",\"takeable\":true,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\",\"observations\":\"A hastily written note on expensive paper\"},{\"type\":\"key\",\"name\":\"Safe Key\",\"takeable\":true,\"key_id\":\"safe_key\",\"keyPins\":[52,29,44,37],\"observations\":\"A heavy-duty safe key hidden behind server equipment\"}]},{\"type\":\"phone\",\"name\":\"CEO Phone\",\"takeable\":false,\"readable\":true,\"text\":\"Recent calls: 'Offshore Bank', 'Unknown', 'Data Buyer'\",\"sender\":\"Call Log\",\"timestamp\":\"Last 24 hours\",\"observations\":\"The CEO's phone shows suspicious recent calls\"}]},\"closet\":{\"type\":\"room_closet\",\"connections\":{\"south\":\"ceo\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"7391\",\"objects\":[{\"type\":\"safe\",\"name\":\"Hidden Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"safe_key\",\"keyPins\":[52,29,44,37],\"difficulty\":\"hard\",\"observations\":\"A well-hidden wall safe behind a painting\",\"contents\":[{\"type\":\"notes\",\"name\":\"Incriminating Documents\",\"takeable\":true,\"readable\":true,\"text\":\"Contract for sale of proprietary technology\\nBank transfers from competing companies\\nDetails of upcoming corporate espionage operations\",\"observations\":\"A folder containing damning evidence of corporate espionage. Congratulations! You've recovered the incriminating documents. flag{ceo_exfil_flag}\"}]}]},\"server1\":{\"type\":\"room_servers\",\"connections\":{\"south\":\"office3\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"4829\",\"objects\":[{\"type\":\"pc\",\"name\":\"Server Terminal\",\"takeable\":false,\"observations\":\"The main server terminal showing massive data exfiltration\"},{\"type\":\"key\",\"name\":\"Briefcase Key\",\"takeable\":true,\"key_id\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"observations\":\"A small key labeled 'Personal - Do Not Copy'\"}]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.051893"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.051180"], ["updated_at", "2025-11-29 00:39:43.051180"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Redirected to http://www.example.com/break_escape/games/1
+Completed 302 Found in 5ms (ActiveRecord: 0.3ms (4 queries, 0 cached) | GC: 2.5ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_create_game_and_redirect_when_showing_mission
+----------------------------------------------------------------------------------------------
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Count (0.0ms)[0m [1m[34mSELECT COUNT(*) FROM "break_escape_games"[0m
+Started GET "/break_escape/missions/418560898" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::MissionsController#show as HTML
+ Parameters: {"id"=>"418560898"}
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ [1m[36mBreakEscape::Game Load (0.0ms)[0m [1m[34mSELECT "break_escape_games".* FROM "break_escape_games" WHERE "break_escape_games"."player_type" = ? AND "break_escape_games"."player_id" = ? AND "break_escape_games"."mission_id" = ? LIMIT ?[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mSAVEPOINT active_record_1[0m
+ [1m[36mBreakEscape::Game Create (0.2ms)[0m [1m[32mINSERT INTO "break_escape_games" ("player_type", "player_id", "mission_id", "scenario_data", "player_state", "status", "started_at", "completed_at", "score", "created_at", "updated_at", "objectives_completed", "tasks_completed") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING "id"[0m [["player_type", "BreakEscape::DemoUser"], ["player_id", 149617800], ["mission_id", 418560898], ["scenario_data", "{\"scenario_brief\":\"Hi! You are a cyber investigator tasked with uncovering evidence of corporate espionage. Anonymous tips suggest the CEO has been selling company secrets, but you need proof.\",\"startRoom\":\"reception\",\"startItemsInInventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"rooms\":{\"reception\":{\"type\":\"room_reception\",\"connections\":{\"north\":\"office1\"},\"npcs\":[{\"id\":\"neye_eve\",\"displayName\":\"Neye Eve\",\"storyPath\":\"scenarios/ink/neye-eve.json\",\"avatar\":\"assets/npc/avatars/npc_adversary.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\"},{\"id\":\"gossip_girl\",\"displayName\":\"Gossip Girl\",\"storyPath\":\"scenarios/ink/gossip-girl.json\",\"avatar\":\"assets/npc/avatars/npc_neutral.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"timedMessages\":[{\"delay\":5000,\"message\":\"Hey! 👋 Got any juicy gossip for me today?\",\"type\":\"text\"}]},{\"id\":\"helper_npc\",\"displayName\":\"Helpful Contact\",\"storyPath\":\"scenarios/ink/helper-npc.json\",\"avatar\":\"assets/npc/avatars/npc_helper.png\",\"phoneId\":\"player_phone\",\"currentKnot\":\"start\",\"npcType\":\"phone\",\"unlockable\":[\"secure_vault\",\"ceo\"],\"eventMappings\":[{\"eventPattern\":\"item_picked_up:lockpick\",\"targetKnot\":\"on_lockpick_pickup\",\"onceOnly\":true,\"cooldown\":0},{\"eventPattern\":\"minigame_completed\",\"targetKnot\":\"on_lockpick_success\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":10000},{\"eventPattern\":\"minigame_failed\",\"targetKnot\":\"on_lockpick_failed\",\"condition\":\"data.minigameName \\u0026\\u0026 data.minigameName.includes('Lockpick')\",\"cooldown\":15000},{\"eventPattern\":\"door_unlocked\",\"targetKnot\":\"on_door_unlocked\",\"cooldown\":8000},{\"eventPattern\":\"door_unlock_attempt\",\"targetKnot\":\"on_door_attempt\",\"cooldown\":12000},{\"eventPattern\":\"object_interacted\",\"targetKnot\":\"on_ceo_desk_interact\",\"condition\":\"data.objectType === 'desk_ceo'\",\"cooldown\":10000},{\"eventPattern\":\"item_picked_up:*\",\"targetKnot\":\"on_item_found\",\"cooldown\":20000},{\"eventPattern\":\"room_entered\",\"targetKnot\":\"on_room_entered\",\"cooldown\":45000,\"maxTriggers\":3},{\"eventPattern\":\"room_discovered\",\"targetKnot\":\"on_room_discovered\",\"cooldown\":15000,\"maxTriggers\":5},{\"eventPattern\":\"room_entered:ceo\",\"targetKnot\":\"on_ceo_office_entered\",\"onceOnly\":true}]}],\"objects\":[{\"type\":\"phone\",\"name\":\"Reception Phone\",\"takeable\":false,\"readable\":true,\"voice\":\"Hi, this is the IT Team. Security breach detected in server room. Changed access code to 4829.\",\"sender\":\"IT Team\",\"timestamp\":\"2:15 AM\",\"observations\":\"The reception phone's message light is blinking urgently\"},{\"type\":\"notes\",\"name\":\"Security Log\",\"takeable\":true,\"readable\":true,\"text\":\"Unusual after-hours access detected:\\n- CEO office: 11:30 PM\\n- Server room: 2:15 AM\\n- CEO office again: 3:45 AM\",\"observations\":\"A concerning security log from last night\"},{\"type\":\"pc\",\"name\":\"Reception Computer\",\"takeable\":false,\"lockType\":\"password\",\"passwordHint\":\"Optional hint text\",\"showHint\":true,\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"requires\":\"secret123\",\"observations\":\"The reception's computer, currently locked\",\"postitNote\":\"Password: secret123\",\"showPostit\":true,\"contents\":[{\"type\":\"text_file\",\"name\":\"Private\",\"takeable\":false,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\"}]},{\"type\":\"tablet\",\"name\":\"Tablet Device\",\"takeable\":true,\"locked\":true,\"lockType\":\"bluetooth\",\"requires\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"A locked tablet device that requires Bluetooth pairing\"},{\"type\":\"bluetooth_scanner\",\"name\":\"Bluetooth Scanner\",\"takeable\":true,\"observations\":\"A device for detecting nearby Bluetooth signals\",\"canScanBluetooth\":true},{\"type\":\"key\",\"name\":\"Office Key\",\"takeable\":true,\"key_id\":\"office1_key\",\"keyPins\":[65,25,65,25],\"observations\":\"A key to access the office areas\"},{\"type\":\"pin-cracker\",\"name\":\"PIN Cracker\",\"takeable\":true,\"observations\":\"A sophisticated device that can analyze PIN entry patterns and provide feedback on attempts\"},{\"type\":\"safe\",\"name\":\"Reception Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"pin\",\"requires\":\"9573\",\"observations\":\"A small wall safe behind the reception desk. Looks like it needs a 4-digit code.\",\"contents\":[{\"type\":\"notes\",\"name\":\"IT Access Credentials\",\"takeable\":true,\"readable\":true,\"text\":\"Emergency IT Admin Credentials:\\nUsername: admin\\nPassword: ITsecure2024\\n\\nServer Room Backup Code: 4829\\nCEO Office Alarm Override: 1337\",\"observations\":\"Sensitive IT credentials that could be very useful\"}]}]},\"office1\":{\"type\":\"room_office\",\"locked\":true,\"lockType\":\"key\",\"requires\":\"office1_key\",\"keyPins\":[65,25,65,25],\"difficulty\":\"easy\",\"door_sign\":\"4A Hot Desks\",\"connections\":{\"north\":[\"office2\",\"office3\"],\"south\":\"reception\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"requires\":\"password\",\"hasFingerprint\":true,\"fingerprintOwner\":\"ceo\",\"fingerprintDifficulty\":\"medium\",\"observations\":\"A computer with a cybersecurity alert on screen. There might be fingerprints on the keyboard.\"},{\"type\":\"notes\",\"name\":\"IT Memo\",\"takeable\":true,\"readable\":true,\"text\":\"URGENT: Multiple unauthorized access attempts detected from CEO's office IP address\",\"observations\":\"A concerning IT department memo\"},{\"type\":\"fingerprint_kit\",\"name\":\"Fingerprint Kit\",\"takeable\":true,\"observations\":\"A kit used for collecting fingerprints from surfaces\"}]},\"office2\":{\"type\":\"room_office\",\"connections\":{\"north\":\"ceo\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"Office Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"office2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: office2024\",\"showPostit\":true,\"observations\":\"A standard office computer with a sticky note on the monitor\"},{\"type\":\"notes\",\"name\":\"Shredded Document\",\"takeable\":true,\"readable\":true,\"text\":\"Partially readable: '...offshore account...transfer complete...delete all traces...'\",\"observations\":\"A partially shredded document that someone failed to dispose of properly\"},{\"type\":\"key\",\"name\":\"CEO Office Key\",\"takeable\":true,\"key_id\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"observations\":\"A spare key to the CEO's office, carelessly left behind\"}]},\"office3\":{\"type\":\"room_office\",\"connections\":{\"north\":\"server1\",\"south\":\"office1\"},\"objects\":[{\"type\":\"pc\",\"name\":\"IT Staff Computer\",\"takeable\":false,\"requires\":\"bluetooth\",\"lockType\":\"bluetooth\",\"mac\":\"00:11:22:33:44:55\",\"observations\":\"An IT staff computer showing network security logs\"},{\"type\":\"notes\",\"name\":\"Network Logs\",\"takeable\":true,\"readable\":true,\"text\":\"Large data transfers detected to unknown external IPs - All originating from CEO's office\",\"observations\":\"Suspicious network activity logs\"}]},\"ceo\":{\"type\":\"room_ceo\",\"connections\":{\"north\":\"closet\",\"south\":\"office2\"},\"locked\":true,\"lockType\":\"key\",\"requires\":\"ceo_office_key\",\"keyPins\":[25,45,65,75],\"difficulty\":\"easy\",\"objects\":[{\"type\":\"pc\",\"name\":\"CEO Computer\",\"takeable\":false,\"lockType\":\"password\",\"requires\":\"ceo2024\",\"showKeyboard\":true,\"maxAttempts\":3,\"locked\":true,\"postitNote\":\"Password: ceo2024\",\"showPostit\":true,\"observations\":\"The CEO's laptop, still warm - recently used. A sticky note is attached to the screen.\"},{\"type\":\"suitcase\",\"name\":\"CEO Briefcase\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"difficulty\":\"medium\",\"observations\":\"An expensive leather briefcase with a sturdy lock\",\"contents\":[{\"type\":\"notes\",\"name\":\"Private Note\",\"takeable\":true,\"readable\":true,\"text\":\"Closet keypad code: 7391 - Must move evidence to safe before audit\",\"observations\":\"A hastily written note on expensive paper\"},{\"type\":\"key\",\"name\":\"Safe Key\",\"takeable\":true,\"key_id\":\"safe_key\",\"keyPins\":[52,29,44,37],\"observations\":\"A heavy-duty safe key hidden behind server equipment\"}]},{\"type\":\"phone\",\"name\":\"CEO Phone\",\"takeable\":false,\"readable\":true,\"text\":\"Recent calls: 'Offshore Bank', 'Unknown', 'Data Buyer'\",\"sender\":\"Call Log\",\"timestamp\":\"Last 24 hours\",\"observations\":\"The CEO's phone shows suspicious recent calls\"}]},\"closet\":{\"type\":\"room_closet\",\"connections\":{\"south\":\"ceo\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"7391\",\"objects\":[{\"type\":\"safe\",\"name\":\"Hidden Safe\",\"takeable\":false,\"locked\":true,\"lockType\":\"key\",\"requires\":\"safe_key\",\"keyPins\":[52,29,44,37],\"difficulty\":\"hard\",\"observations\":\"A well-hidden wall safe behind a painting\",\"contents\":[{\"type\":\"notes\",\"name\":\"Incriminating Documents\",\"takeable\":true,\"readable\":true,\"text\":\"Contract for sale of proprietary technology\\nBank transfers from competing companies\\nDetails of upcoming corporate espionage operations\",\"observations\":\"A folder containing damning evidence of corporate espionage. Congratulations! You've recovered the incriminating documents. flag{ceo_exfil_flag}\"}]}]},\"server1\":{\"type\":\"room_servers\",\"connections\":{\"south\":\"office3\"},\"locked\":true,\"lockType\":\"pin\",\"requires\":\"4829\",\"objects\":[{\"type\":\"pc\",\"name\":\"Server Terminal\",\"takeable\":false,\"observations\":\"The main server terminal showing massive data exfiltration\"},{\"type\":\"key\",\"name\":\"Briefcase Key\",\"takeable\":true,\"key_id\":\"briefcase_key\",\"keyPins\":[45,35,25,55],\"observations\":\"A small key labeled 'Personal - Do Not Copy'\"}]}}}"], ["player_state", "{\"currentRoom\":\"reception\",\"unlockedRooms\":[\"reception\"],\"unlockedObjects\":[],\"inventory\":[{\"type\":\"phone\",\"name\":\"Your Phone\",\"takeable\":true,\"phoneId\":\"player_phone\",\"npcIds\":[\"neye_eve\",\"gossip_girl\",\"helper_npc\"],\"observations\":\"Your personal phone with some interesting contacts\"},{\"type\":\"workstation\",\"name\":\"Crypto Analysis Station\",\"takeable\":true,\"observations\":\"A powerful workstation for cryptographic analysis\"},{\"type\":\"lockpick\",\"name\":\"Lock Pick Kit\",\"takeable\":true,\"observations\":\"A professional lock picking kit with various picks and tension wrenches\"}],\"encounteredNPCs\":[],\"globalVariables\":{},\"biometricSamples\":[],\"biometricUnlocks\":[],\"bluetoothDevices\":[],\"notes\":[],\"health\":100,\"submitted_flags\":[],\"flag_rewards_claimed\":[],\"pending_events\":[]}"], ["status", "in_progress"], ["started_at", "2025-11-29 00:39:43.058193"], ["completed_at", nil], ["score", 0], ["created_at", "2025-11-29 00:39:43.057394"], ["updated_at", "2025-11-29 00:39:43.057394"], ["objectives_completed", 0], ["tasks_completed", 0]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[35mRELEASE SAVEPOINT active_record_1[0m
+Redirected to http://www.example.com/break_escape/games/1
+Completed 302 Found in 3ms (ActiveRecord: 0.3ms (4 queries, 0 cached) | GC: 0.3ms)
+ [1m[36mBreakEscape::Game Count (0.0ms)[0m [1m[34mSELECT COUNT(*) FROM "break_escape_games"[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+----------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_should_get_index
+----------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.1ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.2ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 6.1ms | GC: 0.1ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 6.2ms | GC: 0.2ms)
+Completed 200 OK in 7ms (Views: 6.1ms | ActiveRecord: 0.3ms (5 queries, 1 cached) | GC: 0.2ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+------------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_index_should_return_HTML_with_mission_list
+------------------------------------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.0ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 0.8ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.9ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 1.0ms | ActiveRecord: 0.1ms (5 queries, 1 cached) | GC: 0.1ms)
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[36mbegin transaction[0m
+---------------------------------------------------------------------------------
+BreakEscape::MissionsControllerTest: test_index_should_display_published_missions
+---------------------------------------------------------------------------------
+Started GET "/break_escape/missions" for 127.0.0.1 at 2025-11-29 00:39:43 +0000
+Processing by BreakEscape::MissionsController#index as HTML
+ [1m[36mBreakEscape::DemoUser Load (0.0ms)[0m [1m[34mSELECT "break_escape_demo_users".* FROM "break_escape_demo_users" ORDER BY "break_escape_demo_users"."id" ASC LIMIT ?[0m [["LIMIT", 1]]
+ Rendering layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb
+ Rendering /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application
+ [1m[36mBreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mCACHE BreakEscape::Mission Pluck (0.0ms)[0m [1m[34mSELECT DISTINCT "break_escape_missions"."collection" FROM "break_escape_missions"[0m
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."published" = ?[0m [["published", 1]]
+ [1m[36mBreakEscape::Cybok Load (0.0ms)[0m [1m[34mSELECT "break_escape_cyboks".* FROM "break_escape_cyboks" WHERE "break_escape_cyboks"."cybokable_type" = ? AND "break_escape_cyboks"."cybokable_id" IN (?, ?)[0m [["cybokable_type", "BreakEscape::Mission"], ["cybokable_id", 418560898], ["cybokable_id", 899573729]]
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/shared/_cybok_label.html.erb (Duration: 0.0ms | GC: 0.0ms)
+ Rendered /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/break_escape/missions/index.html.erb within layouts/break_escape/application (Duration: 0.8ms | GC: 0.0ms)
+ Rendered layout /home/cliffe/Files/Projects/Code/BreakEscape/BreakEscape/app/views/layouts/break_escape/application.html.erb (Duration: 0.8ms | GC: 0.0ms)
+Completed 200 OK in 1ms (Views: 0.9ms | ActiveRecord: 0.1ms (5 queries, 1 cached) | GC: 0.0ms)
+ [1m[36mBreakEscape::Mission Load (0.0ms)[0m [1m[34mSELECT "break_escape_missions".* FROM "break_escape_missions" WHERE "break_escape_missions"."id" = ? LIMIT ?[0m [["id", 418560898], ["LIMIT", 1]]
+ [1m[36mTRANSACTION (0.0ms)[0m [1m[31mrollback transaction[0m
diff --git a/test/dummy/storage/test.sqlite3 b/test/dummy/storage/test.sqlite3
index 3b32bcf..2999b9b 100644
Binary files a/test/dummy/storage/test.sqlite3 and b/test/dummy/storage/test.sqlite3 differ