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.
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 lockskey_ring- Container for multiple keyslockpick- Lockpicking toolphone- Phone for chat conversationsnotes- Written notes/cluesworkstation- Crypto analysis toolfingerprint_kit- Biometric collectionbluetooth_scanner- Bluetooth device scannerpc,safe,tablet- Interactive objectsnotepad- 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
-
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_upevent
-
Item Clicked:
handleObjectInteraction(item)- Checks item type
- Triggers appropriate minigame/action
-
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.inkmentions "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