Files
BreakEscape/docs/LOCK_KEY_SYSTEM_ARCHITECTURE.md
Z. Cliffe Schreuders bd8b9a4f85 docs: Add comprehensive RFID keycard lock system planning documentation
Planning documentation for new RFID keycard lock system feature:

Documentation:
- Complete planning docs in planning_notes/rfid_keycard/
- 7 planning documents (~16,000 words)
- 90+ implementation tasks with estimates
- Technical architecture and code design
- Asset specifications and creation guides

Assets Created:
- 4 keycard sprite variants (CEO, Security, Maintenance, Generic)
- RFID cloner device sprite (Flipper Zero-inspired)
- 2 icon assets (RFID icon, NFC waves)
- Helper scripts for placeholder creation

System Documentation:
- Lock & key system architecture reference
- Lock & key quick start guide

Feature Overview:
- New lock type: "rfid"
- New items: keycard, rfid_cloner
- New minigame: Flipper Zero-style RFID reader/cloner
- Ink tag support: # clone_keycard:name|hex
- Inventory integration: Click cards to clone
- Two modes: Unlock (tap/emulate) and Clone (read/save)

Implementation:
- Estimated time: 91 hours (~11 working days)
- 11 new files, 5 modified files
- Full test plan included
- Ready for immediate implementation

All placeholder assets functional for development.
Documentation provides complete roadmap from planning to deployment.
2025-11-15 23:48:15 +00:00

17 KiB

BreakEscape Lock & Key System Architecture

Overview

The lock and key system in BreakEscape supports multiple lock types with a hierarchical architecture. Keys work with pintumbler locks through a scenario-based configuration system that ensures consistency between keys and locks.


1. Lock Types Definition

Supported Lock Types

Locks are defined in the scenario JSON with the following types:

Lock Type Definition Location Unlocking Method Key File
key Room lockType or Object lockType Physical key or lockpicking /js/systems/key-lock-system.js
pin Room/Object lockType: "pin" PIN code entry /js/systems/unlock-system.js (case: 'pin')
password Room/Object lockType: "password" Password text entry /js/systems/unlock-system.js (case: 'password')
biometric Room/Object lockType: "biometric" Fingerprint scanning /js/systems/unlock-system.js (case: 'biometric')
bluetooth Object lockType: "bluetooth" Bluetooth device connection /js/systems/unlock-system.js (case: 'bluetooth')

Lock Definition in Scenarios

Door Lock Example (scenario3.json):

{
  "room_reception": {
    "locked": true,
    "lockType": "key",
    "requires": "briefcase_key",  // Key ID that unlocks this
    "objects": [ ... ]
  }
}

Object Lock Example:

{
  "type": "safe",
  "name": "Safe1",
  "locked": true,
  "lockType": "password",
  "requires": "TimeIsMoney_123"  // Password required
}

2. Pin Tumbler Lock Minigame Implementation

Lockpicking Minigame Architecture

The pintumbler minigame simulates a realistic lock picking experience with interactive pin manipulation.

Main Implementation: /js/minigames/lockpicking/lockpicking-game-phaser.js

Key Components

2.1 Pin Management (pin-management.js)

  • createPins(): Creates pin objects with binding order
  • Pin Structure:
    pin = {
      index: 0-4,
      binding: randomized binding order,
      isSet: false,
      keyPinLength: 25-65 pixels,    // Lock pin height
      driverPinLength: 75 - keyPinLength,  // Spring pin height
      originalHeight: keyPinLength,
      currentHeight: 0,
      container: Phaser container,
      keyPin: graphics object,
      driverPin: graphics object,
      spring: graphics object
    }
    

2.2 Key Operations (key-operations.js)

  • Handles inserting and manipulating the key
  • Calculates how much each pin lifts based on key cut depth
  • Formula: keyPinHeight = (key.cuts[i] / maxCutDepth) * keyPinLength

2.3 Hook Mechanics (hook-mechanics.js)

  • Simulates applying tension to the lock
  • Implements binding order mechanics
  • When tension applied, pins in binding order become "biddable"
  • Player can only lift set pins when tension applied

2.4 Pin Visuals (pin-visuals.js)

  • Renders pins with visual feedback
  • Shows alignment with shear line (goal position)
  • Highlights when pins are set correctly

Pintumbler Lock State

Lock State Object:

lockState = {
  id: "lock_id",
  pinCount: 4,
  pinHeights: [32, 28, 35, 30],  // Predefined from scenario keyPins
  difficulty: "medium",
  pins: [ /* pin objects */ ],
  bindingOrder: [2, 0, 3, 1],     // Random order
  tensionApplied: false,
  success: false,
  progress: 0
}

Pin Height Configuration

From Scenario Data (keyPins):

{
  "room": {
    "locked": true,
    "lockType": "key",
    "requires": "office_key",
    "keyPins": [32, 28, 35, 30]   // Exact pin heights for this lock
  }
}

The keyPins array directly corresponds to the pintumbler lock's key pin lengths. These values are typically normalized to 25-65 pixel range during gameplay.


3. Key System

Key Definition in Scenarios

Key in Starting Inventory:

{
  "startItemsInInventory": [
    {
      "type": "key",
      "name": "Office Key",
      "key_id": "office_key",
      "keyPins": [32, 28, 35, 30],  // Matches lock's pin heights
      "observations": "A brass key with a distinctive cut"
    }
  ]
}

Key in Container:

{
  "type": "safe",
  "contents": [
    {
      "type": "key",
      "name": "Briefcase Key",
      "key_id": "briefcase_key",
      "keyPins": [40, 35, 38, 32, 36],  // 5-pin lock
      "observations": "Found inside the safe"
    }
  ]
}

Key-Lock Mapping System

File: /js/systems/key-lock-system.js

Global Key-Lock Mappings:

window.keyLockMappings = {
  "office_key": {
    lockId: "office_room_lock",
    lockConfig: { /* pin config */ },
    keyName: "Office Key",
    roomId: "office",
    lockName: "Office Door"
  },
  "briefcase_key": {
    lockId: "briefcase_lock",
    lockConfig: { /* pin config */ },
    keyName: "Briefcase Key",
    roomId: "ceo_office",
    objectIndex: 0,
    lockName: "Briefcase"
  }
}

Key Ring System

Inventory Key Ring (inventory.js):

window.inventory.keyRing = {
  keys: [keySprite1, keySprite2, ...],
  slot: DOM element,
  itemImg: DOM image
}

Features:

  • Multiple keys grouped into single "Key Ring" UI item
  • Key ring shows single key icon if 1 key, multiple key icon if 2+
  • Click key ring to see list of available keys
  • Tooltip shows key names

Key Cut Generation

File: /js/systems/key-lock-system.js - generateKeyCutsForLock()

Formula for Converting Lock Pin Heights to Key Cuts:

cutDepth = keyPinLength - gapFromKeyBladeTopToShearLine

// World coordinates:
keyBladeTop_world = 175      // Top surface of inserted key blade
shearLine_world = 155        // Where lock plug meets housing
gap = 20                      // Distance between them (175 - 155)

cutDepth = keyPinLength - 20  // So 65 pixel pin → 45 pixel cut
clampedCut = Math.max(0, Math.min(110, cutDepth))  // 0-110 range

4. Items System

Item Definition

File: /js/systems/inventory.js

Item Types Supported:

  • key - Physical key for locks
  • key_ring - Container for multiple keys
  • lockpick - Lockpicking tool
  • phone - Phone for chat conversations
  • notes - Written notes/clues
  • workstation - Crypto analysis tool
  • fingerprint_kit - Biometric collection
  • bluetooth_scanner - Bluetooth device scanner
  • pc, safe, tablet - Interactive objects
  • notepad - Inventory notepad

Item Inventory Structure

Inventory Object:

window.inventory = {
  items: [DOM img elements],
  container: HTMLElement,
  keyRing: {
    keys: [key items],
    slot: DOM element,
    itemImg: DOM image
  }
}

Item Data Structure:

inventoryItem = {
  scenarioData: {
    type: "key",
    name: "Office Key",
    key_id: "office_key",
    keyPins: [32, 28, 35, 30],
    locked: false,
    lockType: "key",
    observations: "A brass key"
  },
  name: "key",
  objectId: "inventory_key_office_key"
}

Item Interaction Flow

  1. Item Added to Inventory: addToInventory(sprite)

    • Creates DOM slot and image
    • Special handling for keys → key ring
    • Stores scenario data with item
    • Emits item_picked_up event
  2. Item Clicked: handleObjectInteraction(item)

    • Checks item type
    • Triggers appropriate minigame/action
  3. Key Ring Interaction: handleKeyRingInteraction(keyRingItem)

    • Single key: opens lock selection
    • Multiple keys: shows tooltip with key list

5. Minigame Triggering & Management

Minigame Framework

File: /js/minigames/framework/minigame-manager.js

Architecture:

window.MinigameFramework = {
  mainGameScene: null,
  currentMinigame: null,
  registeredScenes: {
    'lockpicking': LockpickingMinigamePhaser,
    'pin': PinMinigame,
    'password': PasswordMinigame,
    'person-chat': PersonChatMinigame,
    // ... more minigames
  },
  
  startMinigame(sceneType, container, params),
  endMinigame(success, result),
  registerScene(sceneType, SceneClass)
}

Registered Minigames

Minigame Scene Type File Purpose
Lockpicking lockpicking lockpicking/lockpicking-game-phaser.js Pin tumbler lock picking
PIN Entry pin pin/pin-minigame.js Numeric PIN code entry
Password password password/password-minigame.js Text password entry
Person Chat person-chat person-chat/person-chat-minigame.js In-person NPC conversations
Phone Chat phone-chat phone-chat/phone-chat-minigame.js Phone-based conversations
Container container container/container-minigame.js Open containers/safes
Notes notes notes/notes-minigame.js View notes/evidence
Biometrics biometrics biometrics/biometrics-minigame.js Fingerprint scanning
Bluetooth Scanner bluetooth-scanner bluetooth/bluetooth-scanner-minigame.js BLE device scanning

Minigame Triggering Flow

From Object Interaction:

Object clicked → handleObjectInteraction() 
  → Checks scenarioData.type
    → If key: Check if locked
      → If locked & requires key: startKeySelectionMinigame()
      → If locked & no key: Check for lockpick → startLockpickingMinigame()
    → If container: handleContainerInteraction()
    → If phone/notes/etc: startMinigame('type')

Lock Unlocking Flow:

handleUnlock(lockable, type='door'|'item')
  → getLockRequirements()
  → Check lockType:
    case 'key':
      → if keys available: startKeySelectionMinigame()
      → else if lockpick: startLockpickingMinigame()
    case 'pin':
      → startPinMinigame()
    case 'password':
      → startPasswordMinigame()
    case 'biometric':
      → Check biometric samples, unlock if match
    case 'bluetooth':
      → Check BLE devices, unlock if found & signal strong enough

Minigame Lifecycle

1. startMinigame(sceneType, container, params)
   ↓
2. Minigame construction: new MinigameClass(container, params)
   ↓
3. init() - Setup UI structure
   ↓
4. start() - Begin minigame logic
   ↓
5. User interaction/gameplay
   ↓
6. onComplete callback(success, result)
   ↓
7. cleanup() - Remove DOM elements
   ↓
8. Re-enable main game input

6. Ink Conversations Integration

Ink Engine

File: /js/systems/ink/ink-engine.js

InkEngine Wrapper:

export default class InkEngine {
  loadStory(storyJson)          // Load compiled Ink JSON
  continue()                     // Get next dialogue + choices
  goToKnot(knotName)            // Jump to specific knot
  choose(index)                 // Select a choice
  getVariable(name)             // Read Ink variable
  setVariable(name, value)      // Set Ink variable
}

Ink Tag System

Tags in Ink Stories (equipment-officer.ink):

=== start ===
# speaker:npc
Welcome to equipment supply!
-> hub

=== show_inventory ===
# speaker:npc
Here's everything we have!
# give_npc_inventory_items
What else can I help with?
-> hub

Tag Processing:

File: /js/minigames/helpers/chat-helpers.js

Supported Action Tags:

Tag Format Effect
unlock_door # unlock_door:room_id Unlocks specified door
give_item # give_item:item_type Gives item to player
give_npc_inventory_items # give_npc_inventory_items:type1,type2 Opens container UI
set_objective # set_objective:text Updates mission objective
add_note # add_note:title|content Adds note to collection
reveal_secret # reveal_secret:id|data Reveals game secret
trigger_minigame # trigger_minigame:name Triggers a minigame
influence_increased # influence_increased Increases NPC influence
influence_decreased # influence_decreased Decreases NPC influence
speaker:player # speaker:player Sets speaker to player
speaker:npc # speaker:npc Sets speaker to NPC

Conversation Minigame Handoff

Person Chat Flow:

PersonChatMinigame.advance()
  → call inkEngine.continue()
  → get currentText + currentChoices + currentTags
  → processGameActionTags(tags, ui)
    → For each action tag, call NPCGameBridge methods
    → Show notifications
  → Display dialogue + choices to player

Tag Execution Example:

// In equipment-officer.ink: "# give_npc_inventory_items:lockpick,workstation"
// becomes:
window.NPCGameBridge.showNPCInventory(npcId, ['lockpick', 'workstation'])
   Triggers container minigame
   Shows items filtered by type
   Player can take items

7. RFID & Keycard System

Current Status

RFID/Keycard is mentioned in documentation but NOT YET IMPLEMENTED in game logic.

References:

  • equipment-officer.ink mentions "keycards for security"
  • No corresponding lock type or unlock logic currently

Architecture for Implementation

Proposed Keycard Lock Type:

Scenario Definition:

{
  "room": {
    "locked": true,
    "lockType": "keycard",
    "requires": "ceo_keycard",      // Keycard ID
    "requiredAccessLevel": 3        // Optional: access level check
  },
  "objects": [
    {
      "type": "keycard",
      "name": "CEO Keycard",
      "key_id": "ceo_keycard",
      "accessLevel": 3,
      "accessRooms": ["ceo_office", "server_room"],
      "observations": "A magnetic keycard for building access"
    }
  ]
}

Unlock Logic (to be added):

case 'keycard':
  const requiredCardId = lockRequirements.requires;
  const requiredLevel = lockRequirements.requiredAccessLevel || 1;
  
  // Check inventory for matching keycard
  const keycard = window.inventory.items.find(item =>
    item.scenarioData?.type === 'keycard' &&
    item.scenarioData?.key_id === requiredCardId &&
    (item.scenarioData?.accessLevel || 1) >= requiredLevel
  );
  
  if (keycard) {
    window.gameAlert(`Keycard accepted!`, 'success');
    unlockTarget(lockable, type);
  } else {
    window.gameAlert(`Access denied - keycard required`, 'error');
  }
  break;

Key Files Reference

Lock/Key System Files

File Purpose
/js/systems/key-lock-system.js Key-lock mapping, cut generation
/js/systems/unlock-system.js Main unlock logic for all lock types
/js/systems/inventory.js Inventory management, key ring
/js/systems/interactions.js Object interaction detection

Lockpicking Minigame

File Purpose
/js/minigames/lockpicking/lockpicking-game-phaser.js Main minigame controller
/js/minigames/lockpicking/pin-management.js Pin creation and state
/js/minigames/lockpicking/key-operations.js Key insertion and manipulation
/js/minigames/lockpicking/hook-mechanics.js Tension and binding order
/js/minigames/lockpicking/pin-visuals.js Pin rendering
/js/minigames/lockpicking/key-geometry.js Key blade mathematics
/js/minigames/lockpicking/lock-configuration.js Lock state management

Minigame Framework

File Purpose
/js/minigames/framework/minigame-manager.js Framework lifecycle
/js/minigames/framework/base-minigame.js Base class for all minigames
/js/minigames/index.js Minigame registration

Conversation System

File Purpose
/js/minigames/person-chat/person-chat-minigame.js Person conversation controller
/js/minigames/person-chat/person-chat-conversation.js Conversation flow
/js/minigames/helpers/chat-helpers.js Tag processing
/js/systems/ink/ink-engine.js Ink story wrapper

Scenario Examples

File Content
/scenarios/scenario3.json Key locks example
/scenarios/scenario1.json Biometric/Bluetooth/Password locks
/scenarios/scenario2.json PIN locks

Summary: Lock Unlock Flow Diagram

Player clicks locked door/object
    ↓
handleObjectInteraction() / handleUnlock()
    ↓
getLockRequirements() - Extract lock type & requirements
    ↓
Switch on lockType:
  ├─ 'key' →
  │   ├─ Has keys? → startKeySelectionMinigame()
  │   │   └─ Player selects key
  │   │     └─ Key cuts checked against lock pins
  │   │       └─ If match: unlock, show success
  │   │
  │   └─ Has lockpick? → startLockpickingMinigame()
  │       └─ Interactive pin tumbler UI
  │         └─ Player manipulates pins with hook
  │           └─ All pins set → shear line alignment
  │             └─ Lock opens
  │
  ├─ 'pin' → startPinMinigame()
  │   └─ Numeric keypad UI
  │     └─ Enter PIN code
  │       └─ If correct: unlock
  │
  ├─ 'password' → startPasswordMinigame()
  │   └─ Text input dialog
  │     └─ Enter password
  │       └─ If correct: unlock
  │
  ├─ 'biometric' →
  │   └─ Check gameState.biometricSamples
  │     └─ If fingerprint quality ≥ threshold: unlock
  │
  └─ 'bluetooth' →
      └─ Check gameState.bluetoothDevices
        └─ If device found & signal strong: unlock
    ↓
unlockTarget(lockable, type)
    ├─ If type='door': unlockDoor()
    └─ If type='item': set locked=false, show container if contents
    ↓
Emit 'door_unlocked'/'item_unlocked' event