docs(rfid): Add Ink integration and comprehensive variable documentation

Add Ink integration for RFID card protocols, allowing conversation scripts
to detect and respond to different card security levels and protocols.

## Ink Integration (Phase 5):

### 1. Added syncCardProtocolsToInk() Method
- Location: js/minigames/person-chat/person-chat-conversation.js
- Auto-syncs when syncItemsToInk() is called
- Generates rfid_data if card uses card_id pattern
- Syncs variables for each keycard NPC holds

### 2. Per-Card Variables Synced:
- {prefix}_protocol - Protocol name
- {prefix}_name - Card display name
- {prefix}_card_id - Logical identifier
- {prefix}_security - "low", "medium", "high"
- {prefix}_instant_clone - Boolean (EM4100, weak MIFARE)
- {prefix}_needs_attack - Boolean (custom key MIFARE)
- {prefix}_uid_only - Boolean (DESFire)
- {prefix}_uid - Card UID (MIFARE)
- {prefix}_hex - Card hex ID (EM4100)

### 3. Prefix Pattern:
- First card: card_protocol, card_name, card_card_id, etc.
- Second card: card2_protocol, card2_name, card2_card_id, etc.

## Documentation:

### scenarios/ink/README_RFID_VARIABLES.md (NEW)
Comprehensive guide for scenario designers covering:

1. **Variable Declarations** - Required Ink variable setup
2. **Variable Reference** - Complete table of all variables
3. **Protocol Characteristics** - Details for each of 4 protocols
4. **Usage Examples**:
   - Simple EM4100 clone
   - Multi-protocol detection
   - Conditional dialogue based on protocol
5. **Ink Tags** - Suggested tag patterns for RFID actions
6. **Scenario JSON Format** - How to define keycards
7. **Tips for Scenario Designers** - Best practices
8. **Complete Example Scenario** - Full working Ink script
9. **Troubleshooting** - Common issues and solutions

## Example Ink Usage:

```ink
VAR card_protocol = ""
VAR card_security = ""
VAR card_instant_clone = false

{card_security == "low":
  "This is a low-security card. Easy to clone!"
  # clone_keycard:{card_card_id}
  -> cloned
}

{card_needs_attack:
  "Need to run Darkside attack..."
  # save_uid_and_start_attack:{card_card_id}|{card_uid}
  -> wait_for_attack
}
```

## Benefits:

1. **Scenario designers** can write protocol-aware dialogue
2. **NPCs** can react realistically to card security levels
3. **Players** get different experiences based on card type
4. **Automatic** - no manual variable management needed

## Files Modified:
- js/minigames/person-chat/person-chat-conversation.js
- scenarios/ink/README_RFID_VARIABLES.md (NEW)

## Next Steps:
- Create test scenarios for each protocol
- Add Ink tag handlers for suggested patterns
- Test with various card combinations

Phase 5 (Ink Integration) complete!
This commit is contained in:
Z. Cliffe Schreuders
2025-11-15 23:48:15 +00:00
parent 7ecda9d39d
commit 2a4ee59e4f
2 changed files with 423 additions and 6 deletions

View File

@@ -120,29 +120,29 @@ export default class PersonChatConversation {
*/
syncItemsToInk() {
if (!this.inkEngine || !this.inkEngine.story) return;
const npc = this.npc;
if (!npc || !npc.itemsHeld) return;
const varState = this.inkEngine.story.variablesState;
if (!varState._defaultGlobalVariables) return;
// Count items by type
const itemCounts = {};
npc.itemsHeld.forEach(item => {
itemCounts[item.type] = (itemCounts[item.type] || 0) + 1;
});
// Get all declared has_* variables from the story
const declaredVars = Array.from(varState._defaultGlobalVariables.keys());
const hasItemVars = declaredVars.filter(varName => varName.startsWith('has_'));
// Sync all has_* variables - set to true if NPC has item, false if not
hasItemVars.forEach(varName => {
// Extract item type from variable name (e.g., "has_lockpick" -> "lockpick")
const itemType = varName.replace(/^has_/, '');
const hasItem = (itemCounts[itemType] || 0) > 0;
try {
this.inkEngine.setVariable(varName, hasItem);
console.log(`✅ Synced ${varName} = ${hasItem} for NPC ${npc.id} (${itemCounts[itemType] || 0} items)`);
@@ -150,6 +150,76 @@ export default class PersonChatConversation {
console.warn(`⚠️ Could not sync ${varName}:`, err.message);
}
});
// Also sync card protocol information
this.syncCardProtocolsToInk();
}
/**
* Sync RFID card protocol information to Ink variables
* Allows Ink scripts to detect and respond to different card protocols
*/
syncCardProtocolsToInk() {
if (!this.inkEngine || !this.npc || !this.npc.itemsHeld) return;
// Filter for keycards
const keycards = this.npc.itemsHeld.filter(item => item.type === 'keycard');
// Get RFID data manager if available
const dataManager = window.rfidDataManager || (window.RFIDDataManager ? new window.RFIDDataManager() : null);
keycards.forEach((card, index) => {
const protocol = card.rfid_protocol || 'EM4100';
const prefix = index === 0 ? 'card' : `card${index + 1}`;
// Ensure rfid_data exists (generate if using card_id)
if (!card.rfid_data && card.card_id && dataManager) {
card.rfid_data = dataManager.generateRFIDDataFromCardId(card.card_id, protocol);
}
try {
// Basic card info
this.inkEngine.setVariable(`${prefix}_protocol`, protocol);
this.inkEngine.setVariable(`${prefix}_name`, card.name || 'Card');
this.inkEngine.setVariable(`${prefix}_card_id`, card.card_id || card.key_id || '');
// Security level (low, medium, high)
let security = 'low';
if (protocol === 'MIFARE_Classic_Custom_Keys') {
security = 'medium';
} else if (protocol === 'MIFARE_DESFire') {
security = 'high';
}
this.inkEngine.setVariable(`${prefix}_security`, security);
// Simplified booleans for common checks
const isInstantClone = protocol === 'EM4100' || protocol === 'MIFARE_Classic_Weak_Defaults';
this.inkEngine.setVariable(`${prefix}_instant_clone`, isInstantClone);
const needsAttack = protocol === 'MIFARE_Classic_Custom_Keys';
this.inkEngine.setVariable(`${prefix}_needs_attack`, needsAttack);
const isUIDOnly = protocol === 'MIFARE_DESFire';
this.inkEngine.setVariable(`${prefix}_uid_only`, isUIDOnly);
// Set UID or hex based on protocol
if (card.rfid_data?.uid) {
this.inkEngine.setVariable(`${prefix}_uid`, card.rfid_data.uid);
} else {
this.inkEngine.setVariable(`${prefix}_uid`, '');
}
if (card.rfid_data?.hex) {
this.inkEngine.setVariable(`${prefix}_hex`, card.rfid_data.hex);
} else {
this.inkEngine.setVariable(`${prefix}_hex`, '');
}
console.log(`✅ Synced ${prefix}: ${protocol} (card_id: ${card.card_id || card.key_id})`);
} catch (err) {
console.warn(`⚠️ Could not sync card protocol for ${prefix}:`, err.message);
}
});
}
/**

View File

@@ -0,0 +1,347 @@
# RFID Protocol Variables for Ink
This document describes the RFID card protocol variables that are automatically synced to Ink scripts when an NPC holds keycards.
## Overview
When an NPC has keycards in their `itemsHeld` array, the conversation system automatically syncs protocol-specific information to Ink variables. This allows your Ink scripts to react differently based on the card's security level and protocol.
## Required Variable Declarations
Add these to the top of your Ink script (adjust based on how many cards the NPC might have):
```ink
// Card protocol info (auto-synced from NPC itemsHeld)
VAR card_protocol = "" // Protocol name
VAR card_name = "" // Display name
VAR card_card_id = "" // Logical card ID
VAR card_uid = "" // UID (for MIFARE cards)
VAR card_hex = "" // Hex ID (for EM4100 cards)
VAR card_security = "" // "low", "medium", "high"
VAR card_instant_clone = false // true for EM4100 and weak MIFARE
VAR card_needs_attack = false // true for custom key MIFARE
VAR card_uid_only = false // true for DESFire
// For second card (if NPC has multiple keycards)
VAR card2_protocol = ""
VAR card2_name = ""
// ... (same pattern as above)
```
## Variable Reference
### Per-Card Variables
Each card gets a prefix: `card`, `card2`, `card3`, etc.
| Variable | Type | Description | Example Values |
|----------|------|-------------|----------------|
| `{prefix}_protocol` | string | RFID protocol name | `"EM4100"`, `"MIFARE_Classic_Weak_Defaults"`, `"MIFARE_Classic_Custom_Keys"`, `"MIFARE_DESFire"` |
| `{prefix}_name` | string | Card display name | `"Employee Badge"`, `"Security Card"` |
| `{prefix}_card_id` | string | Logical card identifier | `"employee_badge"`, `"master_card"` |
| `{prefix}_security` | string | Security level | `"low"`, `"medium"`, `"high"` |
| `{prefix}_instant_clone` | boolean | Can clone instantly? | `true` for EM4100 and weak MIFARE |
| `{prefix}_needs_attack` | boolean | Needs key attack? | `true` for custom key MIFARE |
| `{prefix}_uid_only` | boolean | UID-only emulation? | `true` for DESFire |
| `{prefix}_uid` | string | Card UID (if MIFARE) | `"A1B2C3D4"` |
| `{prefix}_hex` | string | Card hex ID (if EM4100) | `"01AB34CD56"` |
## Protocol Characteristics
### EM4100 (Low Security)
- **Instant clone**: Yes
- **Attack required**: No
- **Full emulation**: Yes
- **Use case**: Entry-level cards, parking garage, old hotel keys
```ink
{card_protocol == "EM4100":
+ [Scan badge]
# clone_keycard:{card_card_id}
Easy! This old 125kHz card clones instantly.
-> cloned
}
```
### MIFARE Classic - Weak Defaults (Low Security)
- **Instant clone**: Dictionary attack succeeds (~95%)
- **Attack required**: Dictionary (instant)
- **Full emulation**: Yes
- **Use case**: Cheap hotels, old transit cards, poorly maintained systems
```ink
{card_instant_clone && card_protocol == "MIFARE_Classic_Weak_Defaults":
+ [Scan badge]
# clone_keycard:{card_card_id}
This card uses factory default keys - dictionary attack works!
-> cloned
}
```
### MIFARE Classic - Custom Keys (Medium Security)
- **Instant clone**: No
- **Attack required**: Darkside (~30 seconds)
- **Full emulation**: Yes (after cracking keys)
- **Use case**: Corporate badges, banks, government facilities
```ink
{card_needs_attack:
+ [Scan badge]
# save_uid_and_start_attack:{card_card_id}|{card_uid}
Custom keys detected. Need to run Darkside attack...
This will take about 30 seconds.
-> wait_for_attack
}
```
### MIFARE DESFire (High Security)
- **Instant clone**: No
- **Attack required**: N/A (impossible to crack)
- **Full emulation**: UID only (works on poorly-configured readers)
- **Use case**: High-security government, military, modern banking
```ink
{card_uid_only:
+ [Try to scan]
# save_uid_only:{card_card_id}|{card_uid}
High security card - I can only save the UID.
It might work on poorly-configured readers.
-> uid_saved
}
```
## Usage Examples
### Example 1: Simple EM4100 Clone
```ink
=== meet_security_guard ===
{has_keycard:
+ [Ask about the badge]
You see a badge clipped to their belt.
{card_protocol == "EM4100":
"It's just a basic proximity card. Nothing special."
-> offer_to_scan
}
}
=== offer_to_scan ===
+ [Offer to "check" their badge]
You offer to scan their badge with your Flipper Zero.
{card_instant_clone:
# clone_keycard:{card_card_id}
"Sure, go ahead!" They hold it out to you.
Your device quickly reads and clones the card.
-> cloned_success
}
```
### Example 2: Multi-Protocol Detection
```ink
=== analyze_card ===
{card_security == "low":
"This is a low-security card. Easy to clone!"
-> easy_clone
}
{card_security == "medium":
"Medium security. I'll need to run an attack."
-> needs_attack
}
{card_security == "high":
"High security DESFire. I can only get the UID."
-> uid_only
}
=== easy_clone ===
+ [Clone it]
# clone_keycard:{card_card_id}
Done! Cloned instantly.
-> END
=== needs_attack ===
+ [Run Darkside attack]
# save_uid_and_start_attack:{card_card_id}|{card_uid}
Starting attack... this will take about 30 seconds.
-> wait_for_crack
=== uid_only ===
+ [Save UID]
# save_uid_only:{card_card_id}|{card_uid}
UID saved. Might work on weak readers.
-> END
```
### Example 3: Conditional Dialogue Based on Protocol
```ink
=== guard_conversation ===
Guard: "This is my access badge."
+ [Ask about security]
You: "What kind of badge is it?"
{card_protocol == "EM4100":
Guard: "Just a basic prox card. Works fine."
// Easy target
}
{card_protocol == "MIFARE_Classic_Weak_Defaults":
Guard: "It's a MIFARE card. Standard issue."
// Still easy, but sounds more secure
}
{card_protocol == "MIFARE_Classic_Custom_Keys":
Guard: "MIFARE Classic with custom encryption."
// They know a bit about security
}
{card_protocol == "MIFARE_DESFire":
Guard: "DESFire EV2. Military-grade security."
// High-security environment
}
-> guard_conversation
+ {has_rfid_cloner} [Ask to scan it]
{card_instant_clone:
// Easy clone
Guard: "Sure, go ahead."
# clone_keycard:{card_card_id}
-> cloned
}
{card_needs_attack:
// Need attack
Guard: "I don't know... this is secure."
-> need_persuasion
}
{card_uid_only:
// UID only
Guard: "No way. This is a DESFire card."
-> refused
}
```
## Ink Tags for RFID Actions
Use these tags to trigger RFID operations from Ink:
| Tag | Description | Example |
|-----|-------------|---------|
| `# clone_keycard:{card_id}` | Clone a card instantly | `# clone_keycard:employee_badge` |
| `# save_uid_only:{card_id}\|{uid}` | Save UID only (DESFire) | `# save_uid_only:exec_card\|A1B2C3D4E5F6` |
| `# save_uid_and_start_attack:{card_id}\|{uid}` | Start Darkside attack | `# save_uid_and_start_attack:secure_badge\|12345678` |
Note: The actual implementation of these tags depends on your tag handler. These are suggested patterns.
## Scenario JSON Format
Define keycards in your scenario using the simplified `card_id` format:
```json
{
"npcId": "security_guard",
"itemsHeld": [
{
"type": "keycard",
"card_id": "employee_badge",
"rfid_protocol": "EM4100",
"name": "Employee Badge"
}
]
}
```
The technical RFID data (hex, UID, etc.) is generated automatically from `card_id`.
## Tips for Scenario Designers
1. **Check security level first**: Use `card_security` for quick branching
2. **Use boolean helpers**: `card_instant_clone`, `card_needs_attack`, `card_uid_only` are easier than checking protocol names
3. **Provide context**: NPCs with high-security cards should acknowledge the security ("This is a DESFire card")
4. **Realistic behavior**: Security-conscious NPCs shouldn't casually hand over DESFire cards
5. **Time pressure**: Custom key attacks take ~30 seconds - use this for tension
## Complete Example Scenario
```ink
VAR card_protocol = ""
VAR card_name = ""
VAR card_card_id = ""
VAR card_security = ""
VAR card_instant_clone = false
VAR card_needs_attack = false
VAR card_uid_only = false
VAR card_uid = ""
VAR has_rfid_cloner = false
-> hotel_reception
=== hotel_reception ===
You approach the hotel reception desk.
{has_keycard:
The receptionist has a {card_name} on a lanyard.
+ [Ask about room access]
"All our rooms use RFID cards for security."
{card_security == "low":
"Basic prox cards. Nothing fancy."
}
{card_security == "medium":
"We use MIFARE Classic with custom keys."
}
{card_security == "high":
"DESFire cards. Top of the line security."
}
-> reception_menu
}
-> reception_menu
=== reception_menu ===
+ [Ask to see their card]
{card_instant_clone:
"Sure!" They hold it out carelessly.
# clone_keycard:{card_card_id}
Your Flipper Zero quickly clones it.
-> cloned_success
}
{card_needs_attack:
"I... suppose?" They seem hesitant.
You'll need to run an attack.
-> attempt_attack
}
{card_uid_only:
"No way. These are secure."
You can only get the UID if you steal it.
-> refused
}
+ [Leave]
-> END
=== cloned_success ===
Success! You now have a copy of {card_name}.
-> END
=== attempt_attack ===
+ [Run Darkside attack]
# save_uid_and_start_attack:{card_card_id}|{card_uid}
Starting attack...
[30 seconds pass]
Success! All keys cracked.
-> END
=== refused ===
You'll need to find another way.
-> END
```
## Troubleshooting
**Variables not syncing?**
- Ensure variables are declared at the top of your Ink script
- Check console for sync messages: `✅ Synced card: ...`
- Verify NPC has keycards in `itemsHeld` array
**Protocol always shows EM4100?**
- Check that `rfid_protocol` is set in scenario JSON
- Default protocol is EM4100 if not specified
**Boolean helpers not working?**
- Make sure to declare boolean variables: `VAR card_instant_clone = false`
- Don't use string comparisons for booleans