docs(rfid): Add corrected scenario patterns and multi-protocol test

Created proper RFID scenario examples following project patterns
(hub structure, #exit_conversation tags, card_id format).

## Issues Found in Existing Files:

### test-rfid.json (Not modified - reference only):
 Uses legacy format (rfid_hex, rfid_facility, key_id)
 All EM4100 - no protocol variety
 Door requires not array format
 No protocol testing variety

### rfid-security-guard.ink (Not modified - reference only):
 Uses -> END instead of #exit_conversation
 Doesn't return to hub after exits
 No proper hub pattern
 Clone tag uses hex instead of card_id

## New Corrected Files:

### 1. scenarios/RFID_SCENARIO_PATTERNS.md (NEW)
Comprehensive guide showing:
-  Correct hub pattern with #exit_conversation
-  Proper card_id JSON format
-  Protocol-specific examples
-  Common mistakes to avoid
- Complete working examples

### 2. scenarios/test-rfid-multiprotocol.json (NEW)
Test scenario with ALL 4 protocols:
- EM4100 (instant clone)
- MIFARE_Classic_Weak_Defaults (dictionary)
- MIFARE_Classic_Custom_Keys (Darkside attack)
- MIFARE_DESFire (UID only)

Features:
- 4 NPCs, each with different protocol
- 4 test rooms demonstrating each security level
- Proper card_id format throughout
- Array-based door requirements
- acceptsUIDOnly flag demonstrated

### 3. scenarios/ink/rfid-security-guard-fixed.ink (NEW)
Fixed version showing:
- Proper hub structure
- #exit_conversation on choice lines
- All paths return to hub
- Card protocol variables declared
- Uses {card_card_id} in clone tags

### 4. scenarios/ink/rfid-guard-low.ink (NEW)
Simple EM4100 example:
- Instant clone pattern
- Proper exit handling
- Minimal complexity for learning

### 5. scenarios/ink/rfid-guard-custom.ink (NEW)
MIFARE Custom Keys example:
- Shows attack requirement
- Player feedback for encrypted cards
- Proper state management

## Key Patterns Documented:

### Correct Ink Pattern:
```ink
+ [Leave] #exit_conversation
  # speaker:npc
  Goodbye!
  -> hub  // Return to hub, NOT END
```

### Correct JSON Pattern:
```json
{
  "type": "keycard",
  "card_id": "employee_badge",
  "rfid_protocol": "EM4100",
  "name": "Employee Badge"
}
```

### Door Configuration:
```json
{
  "lockType": "rfid",
  "requires": ["card1", "card2"],
  "acceptsUIDOnly": false
}
```

## Testing Checklist Added:
- [ ] Uses #exit_conversation tag
- [ ] All knots return to hub
- [ ] Card variables declared
- [ ] Uses card_id format
- [ ] Door requires is array
- [ ] No manual hex entry

## Files Added:
- scenarios/RFID_SCENARIO_PATTERNS.md
- scenarios/test-rfid-multiprotocol.json
- scenarios/ink/rfid-security-guard-fixed.ink
- scenarios/ink/rfid-guard-low.ink
- scenarios/ink/rfid-guard-custom.ink

These serve as reference implementations for scenario designers.
Original files (test-rfid.json, rfid-security-guard.ink) left
unmodified as they may be in use.
This commit is contained in:
Z. Cliffe Schreuders
2025-11-15 23:48:15 +00:00
parent 2a4ee59e4f
commit bff4a6a31a
5 changed files with 845 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
# RFID Scenario Patterns - Correct Usage
This document shows the **correct patterns** for creating RFID scenarios and Ink scripts in this project.
## ✅ Correct Ink Pattern
### Hub Structure with `#exit_conversation`
```ink
VAR has_keycard = false
VAR has_rfid_cloner = false
// Card protocol variables (auto-synced from NPC itemsHeld)
VAR card_protocol = ""
VAR card_instant_clone = false
VAR card_card_id = ""
=== start ===
# speaker:npc
Hello! I'm a guard.
-> hub
=== hub ===
// Main conversation hub
+ [Option 1]
-> some_knot
+ [Leave] #exit_conversation
# speaker:npc
Goodbye!
-> hub // Return to hub, not END
=== some_knot ===
# speaker:npc
Some dialogue here.
-> hub // Always return to hub
```
### Key Rules:
1. **Use `#exit_conversation` tag** - NOT `-> END`
2. **Tag goes on the choice line** - `+ [Leave] #exit_conversation`
3. **After exit, return to hub** - `-> hub` (not `-> END`)
4. **Always have a hub knot** - Central return point for all conversations
5. **All paths return to hub** - Enables conversation to resume
## ✅ Correct Scenario JSON Pattern
### Use `card_id` Format (New)
```json
{
"npcId": "security_guard",
"itemsHeld": [
{
"type": "keycard",
"card_id": "employee_badge",
"rfid_protocol": "EM4100",
"name": "Employee Badge"
}
]
}
```
**Benefits:**
- No manual hex/UID entry needed
- Deterministic generation from card_id
- Simpler for scenario designers
- Supports all 4 protocols
### Door Configuration
```json
{
"lockType": "rfid",
"requires": ["employee_badge", "master_card"],
"acceptsUIDOnly": false
}
```
**Key Points:**
- `requires` is an **array** of card_ids
- `acceptsUIDOnly` for DESFire UID emulation
- Use card_id, not key_id
## ❌ Incorrect Patterns (DO NOT USE)
### ❌ Wrong Ink Pattern
```ink
=== hub ===
+ [Leave]
# speaker:npc
Goodbye!
-> END // ❌ WRONG - breaks conversation state
```
**Problems:**
- Uses `-> END` - conversation can't be resumed
- Doesn't return to hub
- State isn't saved properly
### ❌ Wrong Scenario Pattern (Legacy)
```json
{
"type": "keycard",
"rfid_hex": "FF4A7B9C21",
"rfid_facility": 255,
"rfid_card_number": 18811,
"key_id": "master_keycard"
}
```
**Problems:**
- Manual hex entry required
- Uses old `key_id` instead of `card_id`
- Doesn't work with new protocol system
- More complex for scenario designers
## Protocol-Specific Examples
### EM4100 (Instant Clone)
```ink
{has_keycard and card_instant_clone and card_protocol == "EM4100":
+ [Scan badge] #clone_keycard:{card_card_id}
# speaker:player
Quick scan - card cloned!
-> success
}
```
### MIFARE Classic - Weak Defaults (Dictionary Attack)
```ink
{card_instant_clone and card_protocol == "MIFARE_Classic_Weak_Defaults":
+ [Scan badge] #clone_keycard:{card_card_id}
# speaker:player
Dictionary attack succeeded instantly!
-> success
}
```
### MIFARE Classic - Custom Keys (Needs Attack)
```ink
{card_needs_attack:
+ [Try to scan]
# speaker:player
Encrypted - need Darkside attack (~30 sec)
-> needs_time
}
```
### MIFARE DESFire (UID Only)
```ink
{card_uid_only:
+ [Save UID only]
# speaker:player
Saved UID - only works on weak readers
-> uid_saved
}
```
## Complete Working Example
See these files for complete examples:
- `scenarios/test-rfid-multiprotocol.json` - All 4 protocols
- `scenarios/ink/rfid-guard-low.ink` - EM4100 example
- `scenarios/ink/rfid-guard-custom.ink` - Custom keys example
- `scenarios/ink/rfid-security-guard-fixed.ink` - Full featured example
## Common Mistakes
### ❌ Mistake 1: Using `-> END` for exits
```ink
+ [Leave]
Goodbye!
-> END // ❌ WRONG
```
**✅ Correct:**
```ink
+ [Leave] #exit_conversation
# speaker:npc
Goodbye!
-> hub // ✅ RIGHT
```
### ❌ Mistake 2: Not returning to hub
```ink
=== some_knot ===
Dialog here.
// ❌ Falls off end of knot
```
**✅ Correct:**
```ink
=== some_knot ===
Dialog here.
-> hub // ✅ Always return to hub
```
### ❌ Mistake 3: Using legacy card format
```json
{
"rfid_hex": "01AB34CD56",
"key_id": "employee_badge"
}
```
**✅ Correct:**
```json
{
"card_id": "employee_badge",
"rfid_protocol": "EM4100"
}
```
### ❌ Mistake 4: Single card_id instead of array
```json
{
"requires": "employee_badge" // ❌ Works but not preferred
}
```
**✅ Correct:**
```json
{
"requires": ["employee_badge", "master_card"] // ✅ Array format
}
```
## Protocol Names (Use These Exact Strings)
```
"EM4100" // Low security, instant
"MIFARE_Classic_Weak_Defaults" // Low security, instant dictionary
"MIFARE_Classic_Custom_Keys" // Medium security, 30sec Darkside
"MIFARE_DESFire" // High security, UID only
```
## Testing Checklist
When creating RFID scenarios, verify:
- [ ] Ink file uses `#exit_conversation` tag
- [ ] All knots return to hub
- [ ] Card variables declared at top
- [ ] Uses `card_id` format in JSON
- [ ] Uses `rfid_protocol` with correct protocol name
- [ ] Door `requires` is an array
- [ ] No manual hex/UID entry
- [ ] Hub pattern implemented correctly
- [ ] #clone_keycard tag uses `{card_card_id}` variable
## Reference Documentation
For more details see:
- `scenarios/ink/README_RFID_VARIABLES.md` - Complete Ink variable reference
- `planning_notes/rfid_keycard/protocols_and_interactions/00_IMPLEMENTATION_SUMMARY.md` - Technical implementation details

View File

@@ -0,0 +1,105 @@
// rfid-guard-custom.ink
// Guard with MIFARE Classic (Custom Keys) - requires Darkside attack
// Demonstrates attack requirement pattern
VAR has_keycard = false
VAR has_rfid_cloner = false
// Card protocol variables (auto-synced)
VAR card_protocol = ""
VAR card_name = ""
VAR card_card_id = ""
VAR card_needs_attack = false
VAR card_uid = ""
=== start ===
# speaker:npc
Hey. I'm in charge of corporate security.
{has_keycard:
This badge uses MIFARE Classic with custom encryption keys.
Much more secure than those old EM4100 cards.
}
-> hub
=== hub ===
{has_keycard:
+ [Ask about badge security]
-> ask_security
}
{has_keycard and has_rfid_cloner and card_needs_attack:
+ [Try to scan their badge]
# speaker:player
You try to scan, but it's encrypted...
-> needs_attack
}
+ [Chat about security]
-> chat_security
+ [Leave] #exit_conversation
# speaker:npc
Stay safe.
-> hub
=== ask_security ===
# speaker:npc
This badge? It's a MIFARE Classic 1K with custom encryption keys.
Much better than the factory defaults some companies use.
Can't just clone these with a quick scan. The crypto is... well, it's broken technically, but it takes time to crack.
+ [How long to crack?]
# speaker:npc
With the right tools? Maybe 30 seconds using a Darkside attack.
But most people don't have those tools.
-> hub
+ [Interesting...]
-> hub
=== chat_security ===
# speaker:npc
Corporate security is no joke. We take access control seriously.
All our badges use custom keys. Random generation, changed quarterly.
The CEO even has a DESFire card - that's military-grade encryption.
-> hub
=== needs_attack ===
# speaker:npc
What are you doing?
# speaker:player
Oh, just... checking my phone!
# speaker:npc
That looked like you were trying to scan my badge.
You'd need to run a proper attack to get this one. Can't just quick-clone it.
* [Play it cool]
# speaker:player
Sorry, my device sometimes picks up NFC signals by accident.
# speaker:npc
Uh huh. Sure.
-> hub
* [Tell the truth]
# speaker:player
You're right - I was trying to clone it. But it's encrypted.
# speaker:npc
Yeah, that's the point of custom keys.
You'd need to be close for about 30 seconds to run a Darkside attack.
Good luck with that while I'm watching!
-> hub

View File

@@ -0,0 +1,76 @@
// rfid-guard-low.ink
// Guard with EM4100 card (instant clone)
// Demonstrates proper hub pattern with #exit_conversation
VAR has_keycard = false
VAR has_rfid_cloner = false
// Card protocol variables (auto-synced)
VAR card_protocol = ""
VAR card_name = ""
VAR card_card_id = ""
VAR card_instant_clone = false
=== start ===
# speaker:npc
Hi! I work security here at the building.
{has_keycard:
This badge on my belt? Just a basic EM4100 card. Nothing fancy.
}
-> hub
=== hub ===
{has_keycard:
+ [Ask about the badge]
-> ask_badge
}
{has_keycard and has_rfid_cloner and card_instant_clone:
+ [Casually scan their badge] #clone_keycard:{card_card_id}
# speaker:player
You position your Flipper Zero near their badge while chatting...
# speaker:npc
...and that's when I realized I'd left my lunch at home!
-> cloned
}
+ [Chat about the job]
-> chat_job
+ [Leave] #exit_conversation
# speaker:npc
See you around!
-> hub
=== ask_badge ===
# speaker:npc
Oh, this old thing? Yeah, it's one of those 125kHz proximity cards.
Pretty basic technology. I just wave it at the reader and it opens.
No PIN or anything - just the card itself.
-> hub
=== chat_job ===
# speaker:npc
The job's not bad. Mostly just sitting at the desk and checking people in.
I get to read a lot during my shifts. The night shift especially is pretty quiet.
-> hub
=== cloned ===
# speaker:player
[You've successfully cloned the {card_name}!]
# speaker:npc
Anyway, I should probably get back to my post.
+ [Thanks for chatting] #exit_conversation
# speaker:npc
No problem! Have a good day!
-> hub

View File

@@ -0,0 +1,133 @@
// rfid-security-guard.ink (FIXED)
// Security Guard NPC for RFID test scenario
// Demonstrates proper hub pattern and #exit_conversation usage
VAR has_keycard = false
VAR has_rfid_cloner = false
VAR conversation_count = 0
// Card protocol variables (auto-synced from NPC itemsHeld)
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 card_hex = ""
=== start ===
~ conversation_count += 1
# speaker:npc
Hey there. I'm the security guard for this facility.
{has_keycard:
I've got the master keycard that opens the secure room.
{card_protocol == "EM4100":
It's a basic EM4100 card - nothing fancy.
}
{card_security == "medium":
This one's got proper encryption. Corporate security.
}
}
-> hub
=== hub ===
// Main conversation hub
{has_keycard and not card_instant_clone:
+ [Ask about the keycard security]
-> ask_security
}
{has_keycard:
+ [Ask about the keycard]
-> ask_keycard
}
{has_keycard and has_rfid_cloner and card_instant_clone:
+ [Subtly scan their badge] #clone_keycard:{card_card_id}
# speaker:player
You casually position your Flipper Zero near their badge...
-> cloned_success
}
{has_keycard and has_rfid_cloner and card_needs_attack:
+ [Scan badge (requires attack)]
# speaker:player
You try to scan their badge, but it's encrypted.
# speaker:player
You'll need to run a Darkside attack - this will take about 30 seconds.
-> needs_attack
}
+ [Just browsing] #exit_conversation
# speaker:npc
Alright, let me know if you need anything.
-> hub
=== ask_security ===
# speaker:npc
{card_security == "low":
Honestly? It's just a basic proximity card. Nothing special.
The company's been meaning to upgrade for years...
- else:
This card uses {card_protocol} with custom encryption.
Pretty secure stuff. Can't just clone these easily.
}
-> hub
=== ask_keycard ===
# speaker:npc
This keycard? Yeah, it's the master access card. Opens everything in the building.
I can't just hand it to you though - security policy and all that.
+ [Offer to buy it]
# speaker:npc
Ha! Nice try, but I can't sell company property. I'd lose my job.
-> hub
+ [Ask if you can borrow it]
# speaker:npc
Sorry, no can do. This thing never leaves my person.
-> hub
+ [Back]
-> hub
=== cloned_success ===
# speaker:npc
...So anyway, that's why I love working nights. Much quieter, you know?
The pay's better too. Plus I get to catch up on my podcasts.
+ [Thanks for the chat!] #exit_conversation
# speaker:npc
No problem! Stay safe out there.
-> hub
+ [Any other secure areas?]
# speaker:npc
Well, there's the CEO's office, but that's on a different floor entirely.
This master card works for most areas on this level though.
-> hub
=== needs_attack ===
# speaker:npc
Hey, what are you doing with that device?
# speaker:player
Oh, just... checking the time!
# speaker:npc
That didn't look like checking the time...
You'll need to be more subtle. Or find a way to get the card when they're not looking.
-> hub

View File

@@ -0,0 +1,267 @@
{
"name": "RFID Multi-Protocol Test",
"description": "Comprehensive test for all 4 RFID protocols with attacks",
"scenario_brief": "Test all RFID protocols: EM4100, MIFARE Classic (weak/custom), and DESFire",
"startRoom": "lobby",
"globalVariables": {},
"player": {
"id": "player",
"displayName": "Agent 0x00",
"spriteSheet": "hacker",
"startX": 200,
"startY": 200
},
"startItemsInInventory": [
{
"type": "rfid_cloner",
"name": "Flipper Zero",
"saved_cards": []
}
],
"rooms": {
"lobby": {
"name": "Test Lobby",
"type": "room_reception",
"connections": {
"north": "low_security",
"east": "medium_security",
"south": "high_security"
},
"npcs": [
{
"id": "guard_low",
"displayName": "Guard (Low Security)",
"npcType": "person",
"position": { "x": 3, "y": 3 },
"spriteSheet": "hacker-red",
"spriteConfig": {
"idleFrameStart": 20,
"idleFrameEnd": 23
},
"storyPath": "scenarios/ink/rfid-guard-low.json",
"currentKnot": "start",
"itemsHeld": [
{
"type": "keycard",
"card_id": "employee_badge",
"rfid_protocol": "EM4100",
"name": "Employee Badge"
}
]
},
{
"id": "guard_weak",
"displayName": "Guard (Weak MIFARE)",
"npcType": "person",
"position": { "x": 6, "y": 3 },
"spriteSheet": "hacker-blue",
"spriteConfig": {
"idleFrameStart": 20,
"idleFrameEnd": 23
},
"storyPath": "scenarios/ink/rfid-guard-weak.json",
"currentKnot": "start",
"itemsHeld": [
{
"type": "keycard",
"card_id": "hotel_keycard",
"rfid_protocol": "MIFARE_Classic_Weak_Defaults",
"name": "Hotel Keycard"
}
]
},
{
"id": "guard_custom",
"displayName": "Guard (Custom Keys)",
"npcType": "person",
"position": { "x": 9, "y": 3 },
"spriteSheet": "hacker-green",
"spriteConfig": {
"idleFrameStart": 20,
"idleFrameEnd": 23
},
"storyPath": "scenarios/ink/rfid-guard-custom.json",
"currentKnot": "start",
"itemsHeld": [
{
"type": "keycard",
"card_id": "corporate_badge",
"rfid_protocol": "MIFARE_Classic_Custom_Keys",
"name": "Corporate Badge"
}
]
},
{
"id": "guard_high",
"displayName": "Guard (DESFire)",
"npcType": "person",
"position": { "x": 12, "y": 3 },
"spriteSheet": "hacker-yellow",
"spriteConfig": {
"idleFrameStart": 20,
"idleFrameEnd": 23
},
"storyPath": "scenarios/ink/rfid-guard-desfire.json",
"currentKnot": "start",
"itemsHeld": [
{
"type": "keycard",
"card_id": "executive_card",
"rfid_protocol": "MIFARE_DESFire",
"name": "Executive Card"
}
]
}
],
"objects": [
{
"type": "notes",
"name": "Protocol Guide",
"x": 250,
"y": 250,
"takeable": true,
"readable": true,
"note_title": "RFID Protocol Security Levels",
"note_content": "🟥 LOW SECURITY (Instant Clone):\n • EM4100 (125kHz) - Old tech, no encryption\n • MIFARE Classic (Default Keys) - Factory defaults\n\n🟦 MEDIUM SECURITY (Requires Attack):\n • MIFARE Classic (Custom Keys) - 30 sec Darkside attack\n\n🟩 HIGH SECURITY (UID Only):\n • MIFARE DESFire - Strong encryption, can't crack\n • Only UID emulation works on weak readers",
"observations": "Guide to the 4 RFID protocols"
}
],
"doors": [
{
"roomId": "lobby",
"connectedRoom": "low_security",
"direction": "north",
"x": 300,
"y": 100,
"locked": true,
"lockType": "rfid",
"requires": ["employee_badge"]
},
{
"roomId": "lobby",
"connectedRoom": "medium_security",
"direction": "east",
"x": 600,
"y": 300,
"locked": true,
"lockType": "rfid",
"requires": ["hotel_keycard"]
},
{
"roomId": "lobby",
"connectedRoom": "high_security",
"direction": "south",
"x": 300,
"y": 500,
"locked": true,
"lockType": "rfid",
"requires": ["corporate_badge", "executive_card"],
"acceptsUIDOnly": false
}
]
},
"low_security": {
"name": "Low Security Room (EM4100)",
"type": "room_office",
"connections": {
"south": "lobby"
},
"npcs": [],
"objects": [
{
"type": "notes",
"name": "Success - EM4100",
"x": 300,
"y": 300,
"takeable": true,
"readable": true,
"note_title": "✓ EM4100 Test Passed",
"note_content": "You successfully cloned an EM4100 card!\n\nThis protocol:\n• 125kHz frequency\n• No encryption\n• Instant clone\n• Used in: Old hotels, parking garages",
"observations": "EM4100 test passed"
}
],
"doors": [
{
"roomId": "low_security",
"connectedRoom": "lobby",
"direction": "south",
"x": 300,
"y": 500,
"locked": false
}
]
},
"medium_security": {
"name": "Medium Security Room (Weak MIFARE)",
"type": "room_office",
"connections": {
"west": "lobby"
},
"npcs": [],
"objects": [
{
"type": "notes",
"name": "Success - MIFARE Weak",
"x": 300,
"y": 300,
"takeable": true,
"readable": true,
"note_title": "✓ MIFARE Weak Defaults Test Passed",
"note_content": "You cloned a MIFARE Classic with default keys!\n\nThis protocol:\n• 13.56MHz NFC\n• Encrypted but uses FFFFFFFFFFFF keys\n• Dictionary attack succeeds instantly (~95%)\n• Used in: Cheap hotels, old transit cards",
"observations": "MIFARE weak defaults test passed"
}
],
"doors": [
{
"roomId": "medium_security",
"connectedRoom": "lobby",
"direction": "west",
"x": 100,
"y": 300,
"locked": false
}
]
},
"high_security": {
"name": "High Security Room (Custom MIFARE / DESFire)",
"type": "room_server",
"connections": {
"north": "lobby"
},
"npcs": [],
"objects": [
{
"type": "notes",
"name": "Success - High Security",
"x": 300,
"y": 300,
"takeable": true,
"readable": true,
"note_title": "✓ High Security Protocols Test Passed",
"note_content": "You accessed high security!\n\nMIFARE Classic (Custom Keys):\n• Requires 30-second Darkside attack\n• Used in: Corporate badges, banks\n\nMIFARE DESFire:\n• Can't be cracked - UID only\n• Only works on poorly-configured readers\n• Used in: Government, military",
"observations": "High security test passed"
},
{
"type": "keycard",
"card_id": "master_override",
"rfid_protocol": "EM4100",
"name": "Master Override Card",
"x": 350,
"y": 300,
"takeable": true,
"observations": "A master override card that opens all doors"
}
],
"doors": [
{
"roomId": "high_security",
"connectedRoom": "lobby",
"direction": "north",
"x": 300,
"y": 100,
"locked": false
}
]
}
}
}