diff --git a/db/migrate/20251128000001_remove_unique_game_constraint.rb b/db/migrate/20251128000001_remove_unique_game_constraint.rb index a4715d9..f370592 100644 --- a/db/migrate/20251128000001_remove_unique_game_constraint.rb +++ b/db/migrate/20251128000001_remove_unique_game_constraint.rb @@ -16,3 +16,5 @@ class RemoveUniqueGameConstraint < ActiveRecord::Migration[7.0] name: 'index_games_on_player_and_mission_non_unique' end end + + diff --git a/public/break_escape/css/flag-station-minigame.css b/public/break_escape/css/flag-station-minigame.css index 7665636..d1018af 100644 --- a/public/break_escape/css/flag-station-minigame.css +++ b/public/break_escape/css/flag-station-minigame.css @@ -182,3 +182,5 @@ font-size: 13px; } + + diff --git a/public/break_escape/css/vm-launcher-minigame.css b/public/break_escape/css/vm-launcher-minigame.css index 619295d..92d12cf 100644 --- a/public/break_escape/css/vm-launcher-minigame.css +++ b/public/break_escape/css/vm-launcher-minigame.css @@ -188,3 +188,5 @@ color: #ccc; } + + diff --git a/public/break_escape/js/systems/hacktivity-cable.js b/public/break_escape/js/systems/hacktivity-cable.js index d9a0515..cb894f2 100644 --- a/public/break_escape/js/systems/hacktivity-cable.js +++ b/public/break_escape/js/systems/hacktivity-cable.js @@ -219,3 +219,5 @@ window.hacktivityCable = new HacktivityCable(); // Export for module usage export default window.hacktivityCable; + + diff --git a/scenarios/m01_first_contact/FIXES_APPLIED.md b/scenarios/m01_first_contact/FIXES_APPLIED.md index a96dfb7..2f70baf 100644 --- a/scenarios/m01_first_contact/FIXES_APPLIED.md +++ b/scenarios/m01_first_contact/FIXES_APPLIED.md @@ -2,12 +2,237 @@ **Date:** 2025-12-01 **Status:** ✅ All Critical Issues Resolved -**Latest Fix:** 2025-12-01 - Fixed item types to match #give_item tags (removed id fields, corrected type values) +**Latest Fix:** 2025-12-01 - Added lock type variety by converting safes to PIN codes --- ## Issues Found and Fixed +### Issue #10: Lack of Lock Type Variety + +**Problem:** All locks used keys/lockpick - gameplay became repetitive and "same-y" + +**User Feedback:** "The player is given a lock pick quite early so never experiences doors that they can't get through soon. All the doors and safes etc use keys -- change the safes to use PINs that the player needs to discover/reveal." + +**Root Cause:** All 3 safes used `lockType: "key"` with lockpick - no variety in puzzle types + +**Fix Applied:** +- **Storage Safe** → PIN code `1337` with hint in maintenance checklist +- **Main Office Filing Cabinet** → PIN code `2024` with hint on sticky note +- **Derek's Filing Cabinet** → PIN code `0419` discovered by decoding Base64 message + +**Lock Type Variety Now:** +1. **Storage closet door** - lockpick (tutorial) +2. **Storage safe** - PIN `1337` (easy puzzle - hint nearby) +3. **Main office filing cabinet** - PIN `2024` (medium puzzle - requires reading sticky note) +4. **Derek's office door** - key (from storage safe) +5. **Derek's filing cabinet** - PIN `0419` (harder puzzle - requires CyberChef to decode Base64) +6. **Server room door** - RFID keycard (different lock type) + +**Files Changed:** +- `scenarios/m01_first_contact/scenario.json.erb` - Converted 3 safes to PIN locks, added hint documents +- Updated `client_list_message` variable to include PIN hint for Derek's safe + +**Progression Design:** +- Player must use lockpick tutorial first (storage closet) +- Find PIN hints through exploration and reading documents +- Use CyberChef terminal to decode Base64 for final safe PIN +- Creates varied puzzle-solving experience instead of just lockpicking everything + +**FURTHER UPDATE - Lock Progression Enforcement:** + +**User Feedback:** "Once the player has access to a lockpick, then they don't need any keys, so if we want to include traditional key based access, then those need to happen before they get the lockpick. Keys shouldn't be in same room as door. Must ensure valid path to completion and logical puzzle ordering." + +**Additional Fixes:** +- Storage closet door changed from `locked: true, requires: lockpick` → `locked: false` +- Moved maintenance checklist from storage closet to main office (hint accessible first) +- Kevin's lockpick dialogue updated: requires `influence >= 8` (was: immediate after meeting) +- Ensures key-based puzzles MUST be solved before lockpick is obtained + +**Final Progression Flow:** +1. Main office → find hints (sticky note PIN 2024, maintenance checklist PIN 1337) +2. Use PIN 2024 on main office filing cabinet → The Architect's Letter (LORE, optional) +3. Use PIN 1337 on storage safe → Derek's office key +4. Use key on Derek's office door → enter Derek's office ✅ **KEY USED BEFORE LOCKPICK** +5. Decode Base64 message with CyberChef → PIN 0419 +6. Use PIN 0419 on Derek's filing cabinet → campaign evidence +7. Build rapport with Kevin (influence >= 8) → NOW get lockpick +8. Get RFID keycard from Kevin → access server room + +**Progression Enforcement:** +- Keys used for critical path BEFORE lockpick obtainable ✅ +- Keys not in same room as locks (storage safe → Derek's office) ✅ +- Logical puzzle ordering (easy → medium → hard) ✅ +- Valid completion path ensured ✅ + +--- + +### Issue #9: Derek's Ink File Path Incorrect + +**Problem:** Derek NPC's storyPath pointed to non-existent file `m01_npc_derek.json` + +**Error:** `GET /break_escape/games/112/ink?npc=derek_lawson 404 (Not Found)` + +**Root Cause:** Filename mismatch - actual file is `m01_derek_confrontation.json` + +**Fix Applied:** +- Changed storyPath from `m01_npc_derek.json` to `m01_derek_confrontation.json` +- Now correctly points to the compiled Derek confrontation Ink script + +**Files Changed:** +- `scenarios/m01_first_contact/scenario.json.erb` - Updated Derek NPC storyPath + +**Correct Configuration:** +```json +{ + "id": "derek_lawson", + "displayName": "Derek Lawson", + "npcType": "person", + "storyPath": "scenarios/m01_first_contact/ink/m01_derek_confrontation.json", + "currentKnot": "start" +} +``` + +--- + +### Issue #8: Phone NPCs in Separate Array + +**Problem:** Phone NPCs were in separate `phoneNPCs` array instead of in room `npcs` arrays + +**Validation Error:** "Phone NPCs should be defined in 'rooms/{room_id}/npcs[]' arrays, NOT in a separate 'phoneNPCs' section" + +**Root Cause:** Misunderstood NPC placement - all NPCs (including phone NPCs) should be in room arrays + +**Fix Applied:** +- Moved `agent_0x99` and `closing_debrief_trigger` from `phoneNPCs` array to `reception_area.npcs` array +- Removed separate `phoneNPCs` section entirely +- Phone NPCs now properly defined alongside person NPCs in starting room + +**Files Changed:** +- `scenarios/m01_first_contact/scenario.json.erb` - Moved phone NPCs to room arrays + +**Correct Format:** +```json +"rooms": { + "reception_area": { + "npcs": [ + // Person NPCs + { "id": "sarah_martinez", "npcType": "person", ... }, + + // Phone NPCs (same array!) + { "id": "agent_0x99", "npcType": "phone", "phoneId": "player_phone", ... } + ] + } +} +``` + +**Validation Tool:** Caught by `ruby scripts/validate_scenario.rb` + +--- + +### Issue #7: Invalid Room Connections (Diagonal Directions) + +**Problem:** Rooms used invalid diagonal directions (southeast, northwest) making some rooms inaccessible + +**User Feedback:** "Some of the rooms aren't accessible -- the break_room and storage_closet aren't connected to the other rooms" + +**Root Cause:** +- Used invalid diagonal directions like "southeast" and "northwest" +- Only north, south, east, west are valid directions +- Reverse connections weren't using valid cardinal directions + +**Fix Applied:** +- Removed diagonal directions (southeast, northwest) from main_office_area +- Multiple rooms already in array format: `"south": ["reception_area", "break_room"]` +- Fixed break_room reverse connection from "northwest" to "north" +- storage_closet already had correct "south" connection + +**Files Changed:** +- `scenarios/m01_first_contact/scenario.json.erb` - Fixed room connections + +**Valid Directions:** Only **north, south, east, west** + +**Valid Format Examples:** +```json +// Single room +"connections": { "north": "room_id" } + +// Multiple rooms in same direction (use array) +"connections": { "south": ["room_a", "room_b"] } + +// ❌ INVALID - diagonal directions +"connections": { "southeast": "room_id" } // NOT VALID +``` + +**Bidirectional Connections Required:** +```json +// main_office_area connects north to storage_closet +"connections": { "north": ["derek_office", "storage_closet"] } + +// storage_closet must connect south back to main_office_area +"connections": { "south": "main_office_area" } // NOT "southwest"! +``` + +--- + +### Issue #6: Incorrect VM Launcher and Flag Station Configuration + +**Problem:** VM terminal used wrong object type (`type: "pc"` with `vmAccess`) instead of proper `vm-launcher` type + +**User Feedback:** "The scenario seems to be missing the vm launchers and flag drop sites, this should have been incorporated into the scenario" + +**Root Cause:** Used incorrect configuration format - should reference `scenarios/secgen_vm_lab` example + +**Fix Applied:** +- Changed VM Access Terminal from `type: "pc"` to `type: "vm-launcher"` +- Added proper `hacktivityMode` and `vm` object using ERB helper `vm_object()` +- Changed Drop-Site Terminal from Ink dialogue to `type: "flag-station"` +- Added `acceptsVms`, `flags`, and `flagRewards` arrays +- Uses ERB helper `flags_for_vm()` to configure accepted flags +- Removed manual Ink dialogue script for flag submission + +**Files Changed:** +- `scenarios/m01_first_contact/scenario.json.erb` - Updated server_room terminals + +**Before (Incorrect):** +```json +{ + "type": "pc", + "name": "VM Access Terminal", + "vmAccess": true, + "vmScenario": "intro_to_linux_security_lab" +} +``` + +**After (Correct):** +```json +{ + "type": "vm-launcher", + "id": "vm_launcher_intro_linux", + "name": "VM Access Terminal", + "hacktivityMode": <%= vm_context && vm_context['hacktivity_mode'] ? 'true' : 'false' %>, + "vm": <%= vm_object('intro_to_linux_security_lab', {...}) %> +} +``` + +**Flag Station Configuration:** +```json +{ + "type": "flag-station", + "id": "flag_station_dropsite", + "name": "SAFETYNET Drop-Site Terminal", + "acceptsVms": ["intro_to_linux_security_lab"], + "flags": <%= flags_for_vm('intro_to_linux_security_lab', [...]) %>, + "flagRewards": [ + {"type": "emit_event", "event_name": "ssh_flag_submitted"}, + {"type": "emit_event", "event_name": "navigation_flag_submitted"}, + {"type": "emit_event", "event_name": "sudo_flag_submitted"} + ] +} +``` + +--- + ### Issue #1: Missing Opening Briefing Cutscene **Problem:** Opening briefing Ink script existed but wasn't configured to auto-start @@ -216,7 +441,25 @@ ## Prompts Updated -### 1. Stage 1: Narrative Structure +### 1. Stage 5: Room Layout Design + +**File:** `story_design/story_dev_prompts/05_room_layout_design.md` + +**Added Section:** "CRITICAL: Lock Type Variety and Progression" +- Lock Type Ordering Rules (keys BEFORE lockpick, vary lock types) +- Rule: Keys not in same room as locks +- Rule: Progressive difficulty (easy → medium → hard) +- Lock Progression Template with validation checklist +- Examples of good vs bad progression +- 4 critical rules for lock design + +**Why Added:** +- Prevents "same-y" gameplay from using only one lock type +- Ensures keys matter by using them before lockpick is obtained +- Provides clear template for designing lock progression +- Helps future scenarios avoid lock progression mistakes + +### 2. Stage 1: Narrative Structure **File:** `story_design/story_dev_prompts/01_narrative_structure.md` @@ -229,6 +472,12 @@ **File:** `story_design/story_dev_prompts/07_ink_scripting.md` +**Added Section:** "CRITICAL: Dialogue Pacing Rule" +- Maximum 3 lines from single character before presenting player choices +- Keeps dialogue snappy and interactive +- Prevents dialogue fatigue and maintains pacing +- Includes good/bad examples and exceptions + **Added Section:** "CRITICAL: Compile Ink Scripts Before Proceeding" - Compile scripts after writing them: `./scripts/compile-ink.sh [scenario_name]` - Validates syntax and catches errors early @@ -240,14 +489,15 @@ **File:** `story_design/story_dev_prompts/09_scenario_assembly.md` **Added Section:** "Pre-Assembly Required Steps" -- Compile all Ink scripts before assembly -- Verify successful compilation -- Fix any errors before proceeding +- Compile all Ink scripts before assembly: `./scripts/compile-ink.sh [scenario_name]` +- Validate scenario structure: `ruby scripts/validate_scenario.rb scenarios/[scenario_name]/scenario.json.erb` +- Verify successful compilation and validation +- Fix all INVALID errors before proceeding (suggestions are optional) **Updated Section:** "Required Reading" - Added CRITICAL reference to SCENARIO_JSON_FORMAT_GUIDE.md - Updated documentation references to match actual files -- Added reference to working examples (ceo_exfil, npc-sprite-test3) +- Added reference to working examples (ceo_exfil, npc-sprite-test3, secgen_vm_lab) --- @@ -292,7 +542,9 @@ 5. ✅ Set item `type` to match #give_item tag parameter (DON'T use `id` field) 6. ✅ Test Ink scripts compile before scenario assembly 7. ✅ Use object format for rooms, simple format for connections -8. ✅ Reference working examples (ceo_exfil, npc-sprite-test3) +8. ✅ Reference working examples (ceo_exfil, npc-sprite-test3, secgen_vm_lab) +9. ✅ Use `type: "vm-launcher"` for VM terminals with proper ERB helpers +10. ✅ Use `type: "flag-station"` for flag submission terminals **Never Do:** 1. ❌ Use EXTERNAL for regular variables @@ -302,6 +554,9 @@ 5. ❌ Add `id` fields to items in itemsHeld (use `type` field instead) 6. ❌ Forget to set mission completion variables 7. ❌ Skip event mappings for automatic triggers +8. ❌ Use `type: "pc"` with `vmAccess` for VM launchers +9. ❌ Create Ink dialogue scripts for flag submission +10. ❌ Use diagonal directions (northeast, southeast, etc.) for room connections --- diff --git a/scenarios/m01_first_contact/OBJECTIVES_INTEGRATION.md b/scenarios/m01_first_contact/OBJECTIVES_INTEGRATION.md new file mode 100644 index 0000000..a39a968 --- /dev/null +++ b/scenarios/m01_first_contact/OBJECTIVES_INTEGRATION.md @@ -0,0 +1,169 @@ +# Mission 1: First Contact - Objectives System Integration + +## Overview + +Integrated comprehensive objectives system to track player progress through the three primary aims specified in Agent 0x99's briefing. + +## Objectives Structure + +### Aim 1: Identify ENTROPY Operatives (order: 0) +**Status:** Active from mission start + +**Tasks:** +1. ✓ `meet_reception` - Check in at reception (NPC conversation with Sarah) +2. ✓ `meet_kevin` - Meet the IT manager (NPC conversation with Kevin) +3. 🔒 `investigate_derek` - Investigate Derek Lawson's office (Enter derek_office) +4. 🔒 `confront_derek` - Confront the ENTROPY operative (NPC conversation with Derek) + +### Aim 2: Gather Evidence (order: 1) +**Status:** Active from mission start + +**Tasks:** +1. 🔒 `find_campaign_materials` - Find campaign materials (Collect notes from Derek's filing cabinet) +2. 🔒 `discover_manifesto` - Discover ENTROPY manifesto (Collect notes from Derek's filing cabinet) +3. 🔒 `decode_communications` - Decode encrypted communications (Use CyberChef workstation) + +### Aim 3: Intercept Communications (order: 2) +**Status:** Active from mission start + +**Tasks:** +1. 🔒 `access_server_room` - Access the server room (Enter server_room) +2. 🔒 `access_vm` - Access compromised systems (Interact with VM launcher) +3. 🔒 `submit_ssh_flag` - Submit SSH access evidence (Flag submission) +4. 🔒 `submit_linux_flag` - Submit Linux navigation evidence (Flag submission) +5. 🔒 `submit_sudo_flag` - Submit privilege escalation evidence (Flag submission) + +## Task Unlock/Complete Flow + +### Initial State (Mission Start) +- `meet_reception` - Active +- `meet_kevin` - Active +- All other tasks - Locked + +### Progression Chain + +**1. Meet Sarah (Reception)** +- **Trigger:** Talk to Sarah Martinez +- **Completes:** `meet_reception` (#complete_task in m01_npc_sarah.ink) +- **Unlocks:** `investigate_derek` when Sarah reveals Derek's suspicious behavior + +**2. Meet Kevin (IT Manager)** +- **Trigger:** Talk to Kevin Park +- **Completes:** `meet_kevin` (#complete_task in m01_npc_kevin.ink) +- **Unlocks:** `access_server_room` when Kevin discusses server room + +**3. Enter Derek's Office** +- **Trigger:** Player enters derek_office room +- **Completes:** `investigate_derek` (automatic room entry detection) +- **Unlocks via Agent 0x99 event handler:** + - `find_campaign_materials` + - `discover_manifesto` + - `decode_communications` + +**4. Gather Evidence** +- **Campaign Materials:** Automatically completes when player picks up item from Derek's filing cabinet +- **Manifesto:** Automatically completes when player picks up item from Derek's filing cabinet +- **Decode Communications:** Completes when player successfully decodes Base64 message (#complete_task in m01_terminal_cyberchef.ink) + +**5. Access Server Room** +- **Trigger:** Player enters server_room +- **Completes:** `access_server_room` (#complete_task in m01_phone_agent0x99.ink event handler) +- **Unlocks:** `access_vm` + +**6. Access VM Systems** +- **Trigger:** Player interacts with VM launcher terminal +- **Completes:** `access_vm` (via Ink or game system) +- **Unlocks via m01_terminal_dropsite.ink first_access:** + - `submit_ssh_flag` + - `submit_linux_flag` + - `submit_sudo_flag` + +**7. Submit VM Flags** +- **SSH Flag:** Completes when player submits correct SSH flag (#complete_task in m01_terminal_dropsite.ink) +- **Linux Flag:** Completes when player submits correct navigation flag (#complete_task in m01_terminal_dropsite.ink) +- **Sudo Flag:** Completes when player submits correct privilege escalation flag (#complete_task in m01_terminal_dropsite.ink) + - **Also unlocks:** `confront_derek` (player now has sufficient evidence) + +**8. Confront Derek** +- **Trigger:** Player talks to Derek Lawson in his office +- **Completes:** `confront_derek` (#complete_task at start of m01_derek_confrontation.ink) +- **Mission Resolution:** Player chooses final outcome (arrest/recruit/expose) + +## Ink Script Changes + +### m01_npc_sarah.ink +```ink +=== derek_suspicion === ++ [That does seem odd] + #unlock_task:investigate_derek // Unlocks investigation task + Sarah: Right? But I'm just the receptionist. What do I know? +``` + +### m01_npc_kevin.ink +```ink +=== ask_server_room === +~ discussed_server_room = true +~ influence += 1 +#unlock_task:access_server_room // Unlocks server access task +``` + +### m01_phone_agent0x99.ink +```ink +=== event_derek_office_entered === +#unlock_task:find_campaign_materials +#unlock_task:discover_manifesto +#unlock_task:decode_communications +Agent 0x99: You're in Derek's office. Good. + +=== event_server_room_entered === +#complete_task:access_server_room +#unlock_task:access_vm +Agent 0x99: You're in the server room. Good work getting access. +``` + +### m01_terminal_cyberchef.ink +```ink +=== whiteboard_decoded === +~ decoded_whiteboard = true +#complete_task:decode_communications // Changed from decode_whiteboard +``` + +### m01_terminal_dropsite.ink +```ink +=== first_access === +#unlock_task:submit_ssh_flag +#unlock_task:submit_linux_flag +#unlock_task:submit_sudo_flag + +=== sudo_success === +#complete_task:submit_sudo_flag +#unlock_task:confront_derek // Sufficient evidence gathered +``` + +### m01_derek_confrontation.ink +```ink +=== start === +#complete_task:confront_derek // Final task completion +Derek: Working late on the security audit? +``` + +## Validation + +✅ All Ink scripts compile successfully +✅ Scenario structure validates against schema +✅ All task IDs match between objectives and Ink tags +✅ Proper task progression from locked → active → completed + +## Task Type Mapping + +- **npc_conversation:** Direct NPC dialogue tasks (Sarah, Kevin, Derek) +- **enter_room:** Room entry tasks (Derek's office, server room) +- **collect_items:** Item collection tasks (campaign materials, manifesto) +- **unlock_object:** Terminal/object interaction tasks (CyberChef, VM launcher, flag station) + +## Notes + +- Most task completion is handled via Ink tags (#complete_task, #unlock_task) +- Item collection tasks are automatically tracked by game engine +- Room entry tasks can be completed automatically or via Ink event handlers +- The objectives system provides clear player guidance matching the briefing's three-pronged approach diff --git a/scenarios/m01_first_contact/ink/m01_derek_confrontation.ink b/scenarios/m01_first_contact/ink/m01_derek_confrontation.ink index 26d189d..6f9348e 100644 --- a/scenarios/m01_first_contact/ink/m01_derek_confrontation.ink +++ b/scenarios/m01_first_contact/ink/m01_derek_confrontation.ink @@ -196,15 +196,12 @@ Derek: What happens next is up to you. + [I'm calling in SAFETYNET. You're under arrest] ~ final_choice = "arrest" - #complete_task:final_resolution -> choice_arrest + [I have a proposition—work for us instead] ~ final_choice = "recruit" - #complete_task:final_resolution -> choice_recruit + [I'm exposing everything publicly] ~ final_choice = "expose" - #complete_task:final_resolution -> choice_expose // ================================================ diff --git a/scenarios/m01_first_contact/ink/m01_derek_confrontation.json b/scenarios/m01_first_contact/ink/m01_derek_confrontation.json index e245564..ee4b81d 100644 --- a/scenarios/m01_first_contact/ink/m01_derek_confrontation.json +++ b/scenarios/m01_first_contact/ink/m01_derek_confrontation.json @@ -1 +1 @@ -{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":[["#","^complete_task:confront_derek","/#","^Derek: Working late on the security audit?","\n","^Derek: You've been very thorough. Accessing locked offices, reviewing server logs, talking to everyone.","\n","ev","str","^Just doing my job as an IT contractor","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I know who you are, Derek","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^I have questions about your network activity","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","ev","str","^diplomatic","/str","/ev",{"VAR=":"confrontation_approach","re":true},{"->":"derek_response_cover"},null],"c-1":["\n","ev","str","^aggressive","/str","/ev",{"VAR=":"confrontation_approach","re":true},"ev",true,"/ev",{"VAR=":"derek_knows_safetynet","re":true},{"->":"derek_response_direct"},null],"c-2":["\n","ev","str","^evidence_based","/str","/ev",{"VAR=":"confrontation_approach","re":true},{"->":"derek_response_evidence"},null]}],null],"derek_response_cover":[["^Derek: Of course. Very professional.","\n","^Derek: But we both know you're not really an IT contractor, are we?","\n","^Derek: The way you move, the questions you ask, the systems you've accessed...","\n","ev","str","^I don't know what you mean","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^You're right. I'm SAFETYNET","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_calls_bluff"},null],"c-1":["\n","ev",true,"/ev",{"VAR=":"derek_knows_safetynet","re":true},{"->":"derek_response_safetynet"},null]}],null],"derek_calls_bluff":["^Derek: Come on. Give me some credit.","\n","^Derek: I've been watching you watch me. We're professionals here.","\n",{"->":"derek_response_safetynet"},null],"derek_response_direct":[["^Derek: SAFETYNET. I wondered when you'd show up.","\n","^Derek: Took you long enough. I've been operating here for three months.","\n","ev","str","^That ends tonight","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^We know about Social Fabric","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_challenge"},null],"c-1":["\n",{"->":"derek_social_fabric"},null]}],null],"derek_challenge":["^Derek: Does it? You're one agent. I'm one operative. What happens now?","\n",{"->":"present_evidence"},null],"derek_social_fabric":["^Derek: Social Fabric. The Architect. Phase 3. You know the names but not what they mean.","\n",{"->":"present_evidence"},null],"derek_response_evidence":[["^Derek: Network activity. How specific.","\n","^Derek: Let me guess—you found the backdoor, the server access, the encrypted communications?","\n","ev","str","^All of it","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Enough to know you're ENTROPY","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_impressed"},null],"c-1":["\n","ev",true,"/ev",{"VAR=":"derek_knows_safetynet","re":true},{"->":"derek_response_safetynet"},null]}],null],"derek_impressed":["^Derek: Thorough. I'm actually impressed.","\n","^Derek: Not many people could piece that together. SAFETYNET training, I assume?","\n","ev",true,"/ev",{"VAR=":"derek_knows_safetynet","re":true},{"->":"derek_response_safetynet"},null],"derek_response_safetynet":["^Derek: So what now? You arrest me? Call in your team?","\n","^Derek: Or did you come alone to have a conversation first?","\n",{"->":"present_evidence"},null],"present_evidence":[["^You explain what you've found:","\n","^You: Firmware backdoor in the edge router. Three months of network monitoring.","\n","^You: Encrypted communications with other ENTROPY cells. Demographic data collection.","\n","^You: Disinformation campaign planning. Phase 3 references.","\n","^Derek: You have been thorough.","\n","ev","str","^What is Phase 3?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Why do this? Why ENTROPY?","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^This stops now","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n",{"->":"phase_3_explanation"},null],"c-1":["\n",{"->":"derek_motivation"},null],"c-2":["\n",{"->":"confrontation_choice"},null]}],null],"phase_3_explanation":[["^Derek: Phase 3 is... enlightenment, you could call it.","\n","^Derek: The Architect believes systems inherently tend toward chaos. We just accelerate the inevitable.","\n","ev","str","^That's justification for terrorism","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^You're manipulating people","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Derek: Is it terrorism to reveal truth? To demonstrate that security is an illusion?","\n",{"->":"derek_philosophy"},null],"c-1":["\n","^Derek: Everyone manipulates people. We're just honest about it.","\n",{"->":"derek_philosophy"},null]}],null],"derek_philosophy":["^Derek: You think your elections are secure? Your infrastructure is protected?","\n","^Derek: We'll prove otherwise. Not with bombs—with demonstration of how fragile everything really is.","\n",{"->":"derek_motivation"},null],"derek_motivation":[["^Derek: Why ENTROPY? Because The Architect showed me the truth.","\n","^Derek: Every security system fails. Every organization collapses. Entropy always wins.","\n","^Derek: We're not villains. We're... educators. Demonstrating reality that people refuse to see.","\n","ev","str","^You're rationalizing harm","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^You sound like you actually believe this","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev","str","^aggressive","/str","/ev",{"VAR=":"confrontation_approach","re":true},"^Derek: And you're rationalizing surveillance and control. We're not so different.","\n",{"->":"confrontation_choice"},null],"c-1":["\n","ev","str","^diplomatic","/str","/ev",{"VAR=":"confrontation_approach","re":true},"ev",true,"/ev",{"VAR=":"derek_cooperative","re":true},"^Derek: I do. That's what makes us dangerous—we're not criminals chasing money. We're believers.","\n",{"->":"confrontation_choice"},null]}],null],"confrontation_choice":[["^Derek: So. Here we are.","\n","^Derek: What happens next is up to you.","\n","ev","str","^I'm calling in SAFETYNET. You're under arrest","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I have a proposition—work for us instead","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^I'm exposing everything publicly","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","ev","str","^arrest","/str","/ev",{"VAR=":"final_choice","re":true},"#","^complete_task:final_resolution","/#",{"->":"choice_arrest"},null],"c-1":["\n","ev","str","^recruit","/str","/ev",{"VAR=":"final_choice","re":true},"#","^complete_task:final_resolution","/#",{"->":"choice_recruit"},null],"c-2":["\n","ev","str","^expose","/str","/ev",{"VAR=":"final_choice","re":true},"#","^complete_task:final_resolution","/#",{"->":"choice_expose"},null]}],null],"choice_arrest":["^You: You'll face justice through proper channels.","\n","ev",{"VAR?":"derek_cooperative"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Derek: Interesting. You could eliminate me quietly, but you're choosing the legal path.","\n","^Derek: I respect that, actually. It's principled.","\n",{"->":"arrest_cooperative"},{"->":".^.^.^.7"},null]}],[{"->":".^.b"},{"b":["\n","^Derek: The legal system. How quaint.","\n","^Derek: You realize I'll claim whistleblower protection? Expose corporate surveillance?","\n",{"->":"arrest_hostile"},{"->":".^.^.^.7"},null]}],"nop","\n",null],"arrest_cooperative":["^Derek: I won't resist. But you should know—there are others.","\n","^Derek: Social Fabric isn't just me. Phase 3 continues with or without this operation.","\n","^You: That's for SAFETYNET to handle.","\n","^You call in backup. Derek is taken into custody professionally.","\n",{"->":"arrest_outcome"},null],"arrest_hostile":["^Derek: This will get messy. Media attention, legal battles, public scrutiny of SAFETYNET.","\n","^Derek: But if that's how you want to play it...","\n","^You call in backup. Derek is arrested but promises a legal fight.","\n",{"->":"arrest_outcome"},null],"arrest_outcome":["#","^speaker:agent_0x99","/#","^Agent 0x99: Backup team is on site. Derek Lawson in custody.","\n","^Agent 0x99: Good work, ","ev",{"VAR?":"player_name"},"out","/ev","^. Clean operation.","\n","ev",true,"/ev",{"VAR=":"derek_confronted","re":true},"#","^exit_conversation","/#","end",null],"choice_recruit":[["^You: ENTROPY is going down. You can go down with it, or you can help us stop Phase 3.","\n","^Derek: Become a double agent? Feed you intelligence while maintaining my ENTROPY cover?","\n","ev","str","^Exactly. You keep your cell's trust, we get inside information","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Or face prosecution. Your choice","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"recruit_negotiation"},null],"c-1":["\n",{"->":"recruit_pressure"},null]}],null],"recruit_negotiation":[["^Derek: Interesting proposition.","\n","^Derek: What's in it for me? Immunity? Protection?","\n","ev","str","^Full immunity for cooperation. Witness protection if needed","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^A chance to do the right thing","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",true,"/ev",{"VAR=":"derek_cooperative","re":true},{"->":"recruit_accept"},null],"c-1":["\n","^Derek: I'm a true believer, remember? \"Right thing\" is subjective.","\n","^Derek: But immunity and protection... that I can work with.","\n",{"->":"recruit_accept"},null]}],null],"recruit_pressure":["^Derek: Threatening prosecution? That's your angle?","\n","^Derek: Fine. But understand—I'm doing this for my survival, not because I've seen the error of my ways.","\n",{"->":"recruit_accept"},null],"recruit_accept":["^Derek: I'll do it. Feed you intelligence, maintain my ENTROPY connections.","\n","^Derek: But you should know—if The Architect suspects I'm compromised, I'm dead.","\n","^Derek: So keep me alive, and I'll keep you informed about Phase 3.","\n","#","^speaker:agent_0x99","/#","^Agent 0x99: ","ev",{"VAR?":"player_name"},"out","/ev","^, this is high risk. But if it works, we'll have unprecedented ENTROPY access.","\n","^Agent 0x99: Derek Lawson is now Asset NIGHTINGALE. Proceed with extreme caution.","\n","ev",true,"/ev",{"VAR=":"derek_confronted","re":true},"#","^exit_conversation","/#","end",null],"choice_expose":[["^You: I'm taking everything I've found—the backdoors, the emails, the evidence—and going public.","\n","^Derek: Public disclosure? That's bold.","\n","^Derek: You'll expose ENTROPY operations, but also Viral Dynamics' complete security failure.","\n","ev","str","^The public deserves to know the truth","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Transparency is the only way","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"expose_truth"},null],"c-1":["\n",{"->":"expose_transparency"},null]}],null],"expose_truth":[["^Derek: Noble. Naive, but noble.","\n","^Derek: You'll destroy this company, ruin careers, cause panic. All for \"truth.\"","\n","ev","str","^Better than letting ENTROPY operate in shadows","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^The alternative is worse","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"expose_execute"},null],"c-1":["\n",{"->":"expose_execute"},null]}],null],"expose_transparency":["^Derek: Transparency. The Architect would appreciate the irony.","\n","^Derek: You're proving our point—that security through obscurity fails when exposed.","\n",{"->":"expose_execute"},null],"expose_execute":["^Derek: Well, if you're doing this, you should know the full scope.","\n","^Derek: Social Fabric is coordinating with Zero Day Syndicate, Ransomware Inc., and Critical Mass. Multiple cells, one operation.","\n","^Derek: Expose it all. Let the chaos unfold.","\n","^You begin compiling the evidence for public release.","\n","#","^speaker:agent_0x99","/#","^Agent 0x99: ","ev",{"VAR?":"player_name"},"out","/ev","^, Director Netherton is furious. We don't do public disclosures.","\n","^Agent 0x99: But... the evidence is already out there. Viral Dynamics, ENTROPY operations, everything.","\n","^Agent 0x99: The fallout is going to be massive.","\n","ev",true,"/ev",{"VAR=":"derek_confronted","re":true},"#","^exit_conversation","/#","end",null],"global decl":["ev","str","^","/str",{"VAR=":"confrontation_approach"},false,{"VAR=":"derek_knows_safetynet"},false,{"VAR=":"derek_cooperative"},"str","^","/str",{"VAR=":"final_choice"},false,{"VAR=":"derek_confronted"},"str","^Agent 0x00","/str",{"VAR=":"player_name"},false,{"VAR=":"evidence_collected"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":[["#","^complete_task:confront_derek","/#","^Derek: Working late on the security audit?","\n","^Derek: You've been very thorough. Accessing locked offices, reviewing server logs, talking to everyone.","\n","ev","str","^Just doing my job as an IT contractor","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I know who you are, Derek","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^I have questions about your network activity","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","ev","str","^diplomatic","/str","/ev",{"VAR=":"confrontation_approach","re":true},{"->":"derek_response_cover"},null],"c-1":["\n","ev","str","^aggressive","/str","/ev",{"VAR=":"confrontation_approach","re":true},"ev",true,"/ev",{"VAR=":"derek_knows_safetynet","re":true},{"->":"derek_response_direct"},null],"c-2":["\n","ev","str","^evidence_based","/str","/ev",{"VAR=":"confrontation_approach","re":true},{"->":"derek_response_evidence"},null]}],null],"derek_response_cover":[["^Derek: Of course. Very professional.","\n","^Derek: But we both know you're not really an IT contractor, are we?","\n","^Derek: The way you move, the questions you ask, the systems you've accessed...","\n","ev","str","^I don't know what you mean","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^You're right. I'm SAFETYNET","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_calls_bluff"},null],"c-1":["\n","ev",true,"/ev",{"VAR=":"derek_knows_safetynet","re":true},{"->":"derek_response_safetynet"},null]}],null],"derek_calls_bluff":["^Derek: Come on. Give me some credit.","\n","^Derek: I've been watching you watch me. We're professionals here.","\n",{"->":"derek_response_safetynet"},null],"derek_response_direct":[["^Derek: SAFETYNET. I wondered when you'd show up.","\n","^Derek: Took you long enough. I've been operating here for three months.","\n","ev","str","^That ends tonight","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^We know about Social Fabric","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_challenge"},null],"c-1":["\n",{"->":"derek_social_fabric"},null]}],null],"derek_challenge":["^Derek: Does it? You're one agent. I'm one operative. What happens now?","\n",{"->":"present_evidence"},null],"derek_social_fabric":["^Derek: Social Fabric. The Architect. Phase 3. You know the names but not what they mean.","\n",{"->":"present_evidence"},null],"derek_response_evidence":[["^Derek: Network activity. How specific.","\n","^Derek: Let me guess—you found the backdoor, the server access, the encrypted communications?","\n","ev","str","^All of it","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Enough to know you're ENTROPY","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_impressed"},null],"c-1":["\n","ev",true,"/ev",{"VAR=":"derek_knows_safetynet","re":true},{"->":"derek_response_safetynet"},null]}],null],"derek_impressed":["^Derek: Thorough. I'm actually impressed.","\n","^Derek: Not many people could piece that together. SAFETYNET training, I assume?","\n","ev",true,"/ev",{"VAR=":"derek_knows_safetynet","re":true},{"->":"derek_response_safetynet"},null],"derek_response_safetynet":["^Derek: So what now? You arrest me? Call in your team?","\n","^Derek: Or did you come alone to have a conversation first?","\n",{"->":"present_evidence"},null],"present_evidence":[["^You explain what you've found:","\n","^You: Firmware backdoor in the edge router. Three months of network monitoring.","\n","^You: Encrypted communications with other ENTROPY cells. Demographic data collection.","\n","^You: Disinformation campaign planning. Phase 3 references.","\n","^Derek: You have been thorough.","\n","ev","str","^What is Phase 3?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Why do this? Why ENTROPY?","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^This stops now","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n",{"->":"phase_3_explanation"},null],"c-1":["\n",{"->":"derek_motivation"},null],"c-2":["\n",{"->":"confrontation_choice"},null]}],null],"phase_3_explanation":[["^Derek: Phase 3 is... enlightenment, you could call it.","\n","^Derek: The Architect believes systems inherently tend toward chaos. We just accelerate the inevitable.","\n","ev","str","^That's justification for terrorism","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^You're manipulating people","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Derek: Is it terrorism to reveal truth? To demonstrate that security is an illusion?","\n",{"->":"derek_philosophy"},null],"c-1":["\n","^Derek: Everyone manipulates people. We're just honest about it.","\n",{"->":"derek_philosophy"},null]}],null],"derek_philosophy":["^Derek: You think your elections are secure? Your infrastructure is protected?","\n","^Derek: We'll prove otherwise. Not with bombs—with demonstration of how fragile everything really is.","\n",{"->":"derek_motivation"},null],"derek_motivation":[["^Derek: Why ENTROPY? Because The Architect showed me the truth.","\n","^Derek: Every security system fails. Every organization collapses. Entropy always wins.","\n","^Derek: We're not villains. We're... educators. Demonstrating reality that people refuse to see.","\n","ev","str","^You're rationalizing harm","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^You sound like you actually believe this","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev","str","^aggressive","/str","/ev",{"VAR=":"confrontation_approach","re":true},"^Derek: And you're rationalizing surveillance and control. We're not so different.","\n",{"->":"confrontation_choice"},null],"c-1":["\n","ev","str","^diplomatic","/str","/ev",{"VAR=":"confrontation_approach","re":true},"ev",true,"/ev",{"VAR=":"derek_cooperative","re":true},"^Derek: I do. That's what makes us dangerous—we're not criminals chasing money. We're believers.","\n",{"->":"confrontation_choice"},null]}],null],"confrontation_choice":[["^Derek: So. Here we are.","\n","^Derek: What happens next is up to you.","\n","ev","str","^I'm calling in SAFETYNET. You're under arrest","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I have a proposition—work for us instead","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^I'm exposing everything publicly","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","ev","str","^arrest","/str","/ev",{"VAR=":"final_choice","re":true},{"->":"choice_arrest"},null],"c-1":["\n","ev","str","^recruit","/str","/ev",{"VAR=":"final_choice","re":true},{"->":"choice_recruit"},null],"c-2":["\n","ev","str","^expose","/str","/ev",{"VAR=":"final_choice","re":true},{"->":"choice_expose"},null]}],null],"choice_arrest":["^You: You'll face justice through proper channels.","\n","ev",{"VAR?":"derek_cooperative"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Derek: Interesting. You could eliminate me quietly, but you're choosing the legal path.","\n","^Derek: I respect that, actually. It's principled.","\n",{"->":"arrest_cooperative"},{"->":".^.^.^.7"},null]}],[{"->":".^.b"},{"b":["\n","^Derek: The legal system. How quaint.","\n","^Derek: You realize I'll claim whistleblower protection? Expose corporate surveillance?","\n",{"->":"arrest_hostile"},{"->":".^.^.^.7"},null]}],"nop","\n",null],"arrest_cooperative":["^Derek: I won't resist. But you should know—there are others.","\n","^Derek: Social Fabric isn't just me. Phase 3 continues with or without this operation.","\n","^You: That's for SAFETYNET to handle.","\n","^You call in backup. Derek is taken into custody professionally.","\n",{"->":"arrest_outcome"},null],"arrest_hostile":["^Derek: This will get messy. Media attention, legal battles, public scrutiny of SAFETYNET.","\n","^Derek: But if that's how you want to play it...","\n","^You call in backup. Derek is arrested but promises a legal fight.","\n",{"->":"arrest_outcome"},null],"arrest_outcome":["#","^speaker:agent_0x99","/#","^Agent 0x99: Backup team is on site. Derek Lawson in custody.","\n","^Agent 0x99: Good work, ","ev",{"VAR?":"player_name"},"out","/ev","^. Clean operation.","\n","ev",true,"/ev",{"VAR=":"derek_confronted","re":true},"#","^exit_conversation","/#","end",null],"choice_recruit":[["^You: ENTROPY is going down. You can go down with it, or you can help us stop Phase 3.","\n","^Derek: Become a double agent? Feed you intelligence while maintaining my ENTROPY cover?","\n","ev","str","^Exactly. You keep your cell's trust, we get inside information","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Or face prosecution. Your choice","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"recruit_negotiation"},null],"c-1":["\n",{"->":"recruit_pressure"},null]}],null],"recruit_negotiation":[["^Derek: Interesting proposition.","\n","^Derek: What's in it for me? Immunity? Protection?","\n","ev","str","^Full immunity for cooperation. Witness protection if needed","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^A chance to do the right thing","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",true,"/ev",{"VAR=":"derek_cooperative","re":true},{"->":"recruit_accept"},null],"c-1":["\n","^Derek: I'm a true believer, remember? \"Right thing\" is subjective.","\n","^Derek: But immunity and protection... that I can work with.","\n",{"->":"recruit_accept"},null]}],null],"recruit_pressure":["^Derek: Threatening prosecution? That's your angle?","\n","^Derek: Fine. But understand—I'm doing this for my survival, not because I've seen the error of my ways.","\n",{"->":"recruit_accept"},null],"recruit_accept":["^Derek: I'll do it. Feed you intelligence, maintain my ENTROPY connections.","\n","^Derek: But you should know—if The Architect suspects I'm compromised, I'm dead.","\n","^Derek: So keep me alive, and I'll keep you informed about Phase 3.","\n","#","^speaker:agent_0x99","/#","^Agent 0x99: ","ev",{"VAR?":"player_name"},"out","/ev","^, this is high risk. But if it works, we'll have unprecedented ENTROPY access.","\n","^Agent 0x99: Derek Lawson is now Asset NIGHTINGALE. Proceed with extreme caution.","\n","ev",true,"/ev",{"VAR=":"derek_confronted","re":true},"#","^exit_conversation","/#","end",null],"choice_expose":[["^You: I'm taking everything I've found—the backdoors, the emails, the evidence—and going public.","\n","^Derek: Public disclosure? That's bold.","\n","^Derek: You'll expose ENTROPY operations, but also Viral Dynamics' complete security failure.","\n","ev","str","^The public deserves to know the truth","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Transparency is the only way","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"expose_truth"},null],"c-1":["\n",{"->":"expose_transparency"},null]}],null],"expose_truth":[["^Derek: Noble. Naive, but noble.","\n","^Derek: You'll destroy this company, ruin careers, cause panic. All for \"truth.\"","\n","ev","str","^Better than letting ENTROPY operate in shadows","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^The alternative is worse","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"expose_execute"},null],"c-1":["\n",{"->":"expose_execute"},null]}],null],"expose_transparency":["^Derek: Transparency. The Architect would appreciate the irony.","\n","^Derek: You're proving our point—that security through obscurity fails when exposed.","\n",{"->":"expose_execute"},null],"expose_execute":["^Derek: Well, if you're doing this, you should know the full scope.","\n","^Derek: Social Fabric is coordinating with Zero Day Syndicate, Ransomware Inc., and Critical Mass. Multiple cells, one operation.","\n","^Derek: Expose it all. Let the chaos unfold.","\n","^You begin compiling the evidence for public release.","\n","#","^speaker:agent_0x99","/#","^Agent 0x99: ","ev",{"VAR?":"player_name"},"out","/ev","^, Director Netherton is furious. We don't do public disclosures.","\n","^Agent 0x99: But... the evidence is already out there. Viral Dynamics, ENTROPY operations, everything.","\n","^Agent 0x99: The fallout is going to be massive.","\n","ev",true,"/ev",{"VAR=":"derek_confronted","re":true},"#","^exit_conversation","/#","end",null],"global decl":["ev","str","^","/str",{"VAR=":"confrontation_approach"},false,{"VAR=":"derek_knows_safetynet"},false,{"VAR=":"derek_cooperative"},"str","^","/str",{"VAR=":"final_choice"},false,{"VAR=":"derek_confronted"},"str","^Agent 0x00","/str",{"VAR=":"player_name"},false,{"VAR=":"evidence_collected"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/m01_first_contact/ink/m01_npc_kevin.ink b/scenarios/m01_first_contact/ink/m01_npc_kevin.ink index 8502b7a..2e1e1d4 100644 --- a/scenarios/m01_first_contact/ink/m01_npc_kevin.ink +++ b/scenarios/m01_first_contact/ink/m01_npc_kevin.ink @@ -147,7 +147,7 @@ Kevin: They'd rather spend on marketing than IT security. Classic mistake. -> ask_server_room + {influence >= 6 and not can_clone_card} [I'll need to test RFID security. Can I clone your card?] -> request_card_clone -+ {not given_lockpick and discussed_audit} [About that lockpick...] ++ {not given_lockpick and discussed_audit and influence >= 8} [About that lockpick...] -> offer_lockpick + [I'll keep working. Thanks for the help] #exit_conversation @@ -229,6 +229,7 @@ Kevin: We use cloud hosting for everything client-facing. === ask_server_room === ~ discussed_server_room = true ~ influence += 1 +#unlock_task:access_server_room Kevin: Standard setup. Internal servers, network equipment, some legacy systems. diff --git a/scenarios/m01_first_contact/ink/m01_npc_kevin.json b/scenarios/m01_first_contact/ink/m01_npc_kevin.json index 2e656d6..32d48b9 100644 --- a/scenarios/m01_first_contact/ink/m01_npc_kevin.json +++ b/scenarios/m01_first_contact/ink/m01_npc_kevin.json @@ -1 +1 @@ -{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"met_kevin"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev",true,"/ev",{"VAR=":"met_kevin","re":true},"ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Oh, hey! You must be the security auditor. I'm Kevin—IT manager, sole IT department, and occasional coffee addict.","\n","^Kevin: Thank god you're here. I've been telling them we need a security review for months.","\n",{"->":"first_meeting"},{"->":"start.5"},null]}],"nop","\n","ev",{"VAR?":"met_kevin"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Kevin: What's up? Found any security nightmares yet?","\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_meeting":[["ev","str","^Happy to help. What's the current security situation?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll need access to systems and the server room","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^Looks like you handle a lot solo","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","ev",true,"/ev",{"VAR=":"discussed_audit","re":true},"#","^complete_task:meet_kevin","/#",{"->":"security_situation"},null],"c-1":["\n","ev",true,"/ev",{"VAR=":"discussed_audit","re":true},"#","^complete_task:meet_kevin","/#",{"->":"access_discussion"},null],"c-2":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","ev",true,"/ev",{"VAR=":"discussed_audit","re":true},"#","^complete_task:meet_kevin","/#",{"->":"commiseration"},null]}],null],"security_situation":[["^Kevin: Honestly? It's not terrible but it's not great.","\n","^Kevin: We have basic stuff—firewalls, access controls, encryption. But I'm one person managing everything.","\n","ev","str","^What worries you most?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll do a thorough assessment","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev",{"->":"security_concerns"},null],"c-1":["\n",{"->":"hub"},null]}],null],"security_concerns":[["^Kevin: Physical security, mainly. People write passwords on sticky notes, leave doors unlocked.","\n","^Kevin: I can lock down the network all day, but if someone can walk in and access a terminal...","\n","ev","str","^That's what I'm here to check","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Social engineering is often the biggest vulnerability","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Exactly. Look, I've got something that might help you test physical security.","\n",{"->":"offer_lockpick"},null],"c-1":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Right? Technology is only as secure as the people using it.","\n",{"->":"hub"},null]}],null],"access_discussion":["^Kevin: I can get you into most places. Server room, you'll need my RFID card or...","\n","^Kevin: Actually, you should test our physical security anyway.","\n",{"->":"offer_lockpick"},null],"commiseration":[["^Kevin: Yeah, it's just me. Budget constraints, you know?","\n","^Kevin: They'd rather spend on marketing than IT security. Classic mistake.","\n","ev","str","^That's unfortunately common","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Well, I'm here to help now","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Tell me about it. Anyway, what can I help you with?","\n",{"->":"hub"},null],"c-1":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev",{"->":"hub"},null]}],null],"offer_lockpick":["ev",{"VAR?":"given_lockpick"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","^Kevin: I've got a lockpick set in my desk. Bought it for when people lock themselves out.","\n","^Kevin: You should use it to test our physical locks. See how easy it is to bypass security.","\n","ev","str","^That would be very useful","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll stick to my authorized access for now","/str","/ev",{"*":".^.c-1","flg":4},{"->":".^.^.^.5"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"given_lockpick","re":true},"ev",{"VAR?":"influence"},3,"+",{"VAR=":"influence","re":true},"/ev","#","^give_item:lockpick","/#","#","^complete_task:receive_lockpick","/#","^Kevin: Here. Just... officially you're testing security. Unofficially, try not to break anything.","\n","^Kevin: Storage closet is a good place to practice. Simple lock, nothing valuable inside.","\n",{"->":"hub"},null],"c-1":["\n","ev",{"VAR?":"influence"},1,"-",{"VAR=":"influence","re":true},"/ev","^Kevin: Your call. Offer stands if you change your mind.","\n",{"->":"hub"},null]}]}],"nop","\n","ev",{"VAR?":"given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Kevin: You already have the lockpick. Go test those locks!","\n",{"->":"hub"},{"->":".^.^.^.11"},null]}],"nop","\n",null],"hub":[["ev","str","^Can you tell me about password policies here?","/str",{"VAR?":"asked_about_passwords"},"!",{"VAR?":"influence"},3,">=","&&","/ev",{"*":".^.c-0","flg":5},"ev","str","^Anyone using weak security I should know about?","/str",{"VAR?":"asked_about_derek"},"!",{"VAR?":"influence"},4,">=","&&","/ev",{"*":".^.c-1","flg":5},"ev","str","^Tell me about the server room setup","/str",{"VAR?":"discussed_server_room"},"!","/ev",{"*":".^.c-2","flg":5},"ev","str","^I'll need to test RFID security. Can I clone your card?","/str",{"VAR?":"influence"},6,">=",{"VAR?":"can_clone_card"},"!","&&","/ev",{"*":".^.c-3","flg":5},"ev","str","^About that lockpick...","/str",{"VAR?":"given_lockpick"},"!",{"VAR?":"discussed_audit"},"&&","/ev",{"*":".^.c-4","flg":5},"ev","str","^I'll keep working. Thanks for the help","/str","/ev",{"*":".^.c-5","flg":4},{"c-0":["\n",{"->":"ask_passwords"},null],"c-1":["\n",{"->":"ask_weak_security"},null],"c-2":["\n",{"->":"ask_server_room"},null],"c-3":["\n",{"->":"request_card_clone"},null],"c-4":["\n",{"->":"offer_lockpick"},null],"c-5":["\n","#","^exit_conversation","/#","^Kevin: No problem. Let me know if you find anything scary.","\n",{"->":"hub"},null]}],null],"ask_passwords":[["ev",true,"/ev",{"VAR=":"asked_about_passwords","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Official policy is 12 characters, mixed case, numbers, symbols. We enforce it on domain accounts.","\n","^Kevin: Reality? People use patterns to remember them.","\n","ev","str","^What kind of patterns?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^That's pretty standard","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",true,"/ev",{"VAR=":"given_password_hints","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","#","^complete_task:gather_password_hints","/#",{"->":"password_patterns"},null],"c-1":["\n",{"->":"hub"},null]}],null],"password_patterns":[["^Kevin: Company name plus numbers. Birth years. \"Marketing123\" type stuff.","\n","^Kevin: Derek uses his birthday in passwords. I've seen his sticky notes.","\n","^Kevin: Maya from accounting uses \"Campaign\" plus the year. Same password for everything.","\n","ev","str","^That's... not great security","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Tell me about it. That's why we need this audit.","\n","^Kevin: Maybe your report will convince them to take password security seriously.","\n",{"->":"hub"},null]}],null],"ask_weak_security":[["ev",true,"/ev",{"VAR=":"asked_about_derek","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Derek's the worst offender, honestly. Senior marketing guy.","\n","^Kevin: He requested \"enhanced privacy\" for his office systems. Made me set up separate network segments.","\n","ev","str","^That's unusual","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Maybe he handles sensitive client data?","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Right? He says it's for client confidentiality, but the segmentation is weird.","\n","^Kevin: And I've caught him in the server room twice. Said he was \"checking campaign servers.\"","\n",{"->":"derek_server_access"},null],"c-1":["\n","^Kevin: Maybe. But it still seems excessive.","\n",{"->":"hub"},null]}],null],"derek_server_access":[["^Kevin: The thing is, there are no \"campaign servers\" in our server room.","\n","^Kevin: We use cloud hosting for everything client-facing.","\n","ev","str","^So what was he really doing?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll look into it","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: I don't know. But you're auditing security—might want to check his systems.","\n","^Kevin: His office is usually locked when he's not there, though.","\n",{"->":"hub"},null],"c-1":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev",{"->":"hub"},null]}],null],"ask_server_room":[["ev",true,"/ev",{"VAR=":"discussed_server_room","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Standard setup. Internal servers, network equipment, some legacy systems.","\n","^Kevin: Access is RFID controlled. I'm the only one with a card besides management.","\n","ev","str","^What about testing RFID security?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll need access for the audit","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",true,"/ev",{"VAR=":"can_clone_card","re":true},"^Kevin: Good point. You should probably test if our cards can be cloned.","\n",{"->":"hub"},null],"c-1":["\n","^Kevin: Yeah, about that... I can give you my card, or you could test our RFID security by cloning it?","\n","ev",true,"/ev",{"VAR=":"can_clone_card","re":true},{"->":"hub"},null]}],null],"request_card_clone":["ev",{"VAR?":"can_clone_card"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Kevin: Yeah, good idea to test that. RFID security is important.","\n","^Kevin: Here, you can use my card to clone onto a blank. Standard security test.","\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","#","^complete_task:clone_kevin_card","/#","#","^give_item:rfid_cloner","/#","^Kevin: Just make sure to document this in your report. We need to know if our access system is vulnerable.","\n",{"->":"hub"},{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Kevin: Hmm, I'm not sure about that. Let me think about it.","\n",{"->":"hub"},{"->":".^.^.^.5"},null]}],"nop","\n",null],"global decl":["ev",0,{"VAR=":"influence"},false,{"VAR=":"met_kevin"},false,{"VAR=":"discussed_audit"},false,{"VAR=":"asked_about_derek"},false,{"VAR=":"asked_about_passwords"},false,{"VAR=":"given_lockpick"},false,{"VAR=":"given_password_hints"},false,{"VAR=":"discussed_server_room"},false,{"VAR=":"can_clone_card"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"met_kevin"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev",true,"/ev",{"VAR=":"met_kevin","re":true},"ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Oh, hey! You must be the security auditor. I'm Kevin—IT manager, sole IT department, and occasional coffee addict.","\n","^Kevin: Thank god you're here. I've been telling them we need a security review for months.","\n",{"->":"first_meeting"},{"->":"start.5"},null]}],"nop","\n","ev",{"VAR?":"met_kevin"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Kevin: What's up? Found any security nightmares yet?","\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_meeting":[["ev","str","^Happy to help. What's the current security situation?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll need access to systems and the server room","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^Looks like you handle a lot solo","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","ev",true,"/ev",{"VAR=":"discussed_audit","re":true},"#","^complete_task:meet_kevin","/#",{"->":"security_situation"},null],"c-1":["\n","ev",true,"/ev",{"VAR=":"discussed_audit","re":true},"#","^complete_task:meet_kevin","/#",{"->":"access_discussion"},null],"c-2":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","ev",true,"/ev",{"VAR=":"discussed_audit","re":true},"#","^complete_task:meet_kevin","/#",{"->":"commiseration"},null]}],null],"security_situation":[["^Kevin: Honestly? It's not terrible but it's not great.","\n","^Kevin: We have basic stuff—firewalls, access controls, encryption. But I'm one person managing everything.","\n","ev","str","^What worries you most?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll do a thorough assessment","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev",{"->":"security_concerns"},null],"c-1":["\n",{"->":"hub"},null]}],null],"security_concerns":[["^Kevin: Physical security, mainly. People write passwords on sticky notes, leave doors unlocked.","\n","^Kevin: I can lock down the network all day, but if someone can walk in and access a terminal...","\n","ev","str","^That's what I'm here to check","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Social engineering is often the biggest vulnerability","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Exactly. Look, I've got something that might help you test physical security.","\n",{"->":"offer_lockpick"},null],"c-1":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Right? Technology is only as secure as the people using it.","\n",{"->":"hub"},null]}],null],"access_discussion":["^Kevin: I can get you into most places. Server room, you'll need my RFID card or...","\n","^Kevin: Actually, you should test our physical security anyway.","\n",{"->":"offer_lockpick"},null],"commiseration":[["^Kevin: Yeah, it's just me. Budget constraints, you know?","\n","^Kevin: They'd rather spend on marketing than IT security. Classic mistake.","\n","ev","str","^That's unfortunately common","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Well, I'm here to help now","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Tell me about it. Anyway, what can I help you with?","\n",{"->":"hub"},null],"c-1":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev",{"->":"hub"},null]}],null],"offer_lockpick":["ev",{"VAR?":"given_lockpick"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","^Kevin: I've got a lockpick set in my desk. Bought it for when people lock themselves out.","\n","^Kevin: You should use it to test our physical locks. See how easy it is to bypass security.","\n","ev","str","^That would be very useful","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll stick to my authorized access for now","/str","/ev",{"*":".^.c-1","flg":4},{"->":".^.^.^.5"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"given_lockpick","re":true},"ev",{"VAR?":"influence"},3,"+",{"VAR=":"influence","re":true},"/ev","#","^give_item:lockpick","/#","#","^complete_task:receive_lockpick","/#","^Kevin: Here. Just... officially you're testing security. Unofficially, try not to break anything.","\n","^Kevin: Storage closet is a good place to practice. Simple lock, nothing valuable inside.","\n",{"->":"hub"},null],"c-1":["\n","ev",{"VAR?":"influence"},1,"-",{"VAR=":"influence","re":true},"/ev","^Kevin: Your call. Offer stands if you change your mind.","\n",{"->":"hub"},null]}]}],"nop","\n","ev",{"VAR?":"given_lockpick"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Kevin: You already have the lockpick. Go test those locks!","\n",{"->":"hub"},{"->":".^.^.^.11"},null]}],"nop","\n",null],"hub":[["ev","str","^Can you tell me about password policies here?","/str",{"VAR?":"asked_about_passwords"},"!",{"VAR?":"influence"},3,">=","&&","/ev",{"*":".^.c-0","flg":5},"ev","str","^Anyone using weak security I should know about?","/str",{"VAR?":"asked_about_derek"},"!",{"VAR?":"influence"},4,">=","&&","/ev",{"*":".^.c-1","flg":5},"ev","str","^Tell me about the server room setup","/str",{"VAR?":"discussed_server_room"},"!","/ev",{"*":".^.c-2","flg":5},"ev","str","^I'll need to test RFID security. Can I clone your card?","/str",{"VAR?":"influence"},6,">=",{"VAR?":"can_clone_card"},"!","&&","/ev",{"*":".^.c-3","flg":5},"ev","str","^About that lockpick...","/str",{"VAR?":"given_lockpick"},"!",{"VAR?":"discussed_audit"},"&&",{"VAR?":"influence"},8,">=","&&","/ev",{"*":".^.c-4","flg":5},"ev","str","^I'll keep working. Thanks for the help","/str","/ev",{"*":".^.c-5","flg":4},{"c-0":["\n",{"->":"ask_passwords"},null],"c-1":["\n",{"->":"ask_weak_security"},null],"c-2":["\n",{"->":"ask_server_room"},null],"c-3":["\n",{"->":"request_card_clone"},null],"c-4":["\n",{"->":"offer_lockpick"},null],"c-5":["\n","#","^exit_conversation","/#","^Kevin: No problem. Let me know if you find anything scary.","\n",{"->":"hub"},null]}],null],"ask_passwords":[["ev",true,"/ev",{"VAR=":"asked_about_passwords","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Official policy is 12 characters, mixed case, numbers, symbols. We enforce it on domain accounts.","\n","^Kevin: Reality? People use patterns to remember them.","\n","ev","str","^What kind of patterns?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^That's pretty standard","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",true,"/ev",{"VAR=":"given_password_hints","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","#","^complete_task:gather_password_hints","/#",{"->":"password_patterns"},null],"c-1":["\n",{"->":"hub"},null]}],null],"password_patterns":[["^Kevin: Company name plus numbers. Birth years. \"Marketing123\" type stuff.","\n","^Kevin: Derek uses his birthday in passwords. I've seen his sticky notes.","\n","^Kevin: Maya from accounting uses \"Campaign\" plus the year. Same password for everything.","\n","ev","str","^That's... not great security","/str","/ev",{"*":".^.c-0","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Tell me about it. That's why we need this audit.","\n","^Kevin: Maybe your report will convince them to take password security seriously.","\n",{"->":"hub"},null]}],null],"ask_weak_security":[["ev",true,"/ev",{"VAR=":"asked_about_derek","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Derek's the worst offender, honestly. Senior marketing guy.","\n","^Kevin: He requested \"enhanced privacy\" for his office systems. Made me set up separate network segments.","\n","ev","str","^That's unusual","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Maybe he handles sensitive client data?","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: Right? He says it's for client confidentiality, but the segmentation is weird.","\n","^Kevin: And I've caught him in the server room twice. Said he was \"checking campaign servers.\"","\n",{"->":"derek_server_access"},null],"c-1":["\n","^Kevin: Maybe. But it still seems excessive.","\n",{"->":"hub"},null]}],null],"derek_server_access":[["^Kevin: The thing is, there are no \"campaign servers\" in our server room.","\n","^Kevin: We use cloud hosting for everything client-facing.","\n","ev","str","^So what was he really doing?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll look into it","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Kevin: I don't know. But you're auditing security—might want to check his systems.","\n","^Kevin: His office is usually locked when he's not there, though.","\n",{"->":"hub"},null],"c-1":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev",{"->":"hub"},null]}],null],"ask_server_room":[["ev",true,"/ev",{"VAR=":"discussed_server_room","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","#","^unlock_task:access_server_room","/#","^Kevin: Standard setup. Internal servers, network equipment, some legacy systems.","\n","^Kevin: Access is RFID controlled. I'm the only one with a card besides management.","\n","ev","str","^What about testing RFID security?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'll need access for the audit","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",true,"/ev",{"VAR=":"can_clone_card","re":true},"^Kevin: Good point. You should probably test if our cards can be cloned.","\n",{"->":"hub"},null],"c-1":["\n","^Kevin: Yeah, about that... I can give you my card, or you could test our RFID security by cloning it?","\n","ev",true,"/ev",{"VAR=":"can_clone_card","re":true},{"->":"hub"},null]}],null],"request_card_clone":["ev",{"VAR?":"can_clone_card"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Kevin: Yeah, good idea to test that. RFID security is important.","\n","^Kevin: Here, you can use my card to clone onto a blank. Standard security test.","\n","ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","#","^complete_task:clone_kevin_card","/#","#","^give_item:rfid_cloner","/#","^Kevin: Just make sure to document this in your report. We need to know if our access system is vulnerable.","\n",{"->":"hub"},{"->":".^.^.^.5"},null]}],[{"->":".^.b"},{"b":["\n","^Kevin: Hmm, I'm not sure about that. Let me think about it.","\n",{"->":"hub"},{"->":".^.^.^.5"},null]}],"nop","\n",null],"global decl":["ev",0,{"VAR=":"influence"},false,{"VAR=":"met_kevin"},false,{"VAR=":"discussed_audit"},false,{"VAR=":"asked_about_derek"},false,{"VAR=":"asked_about_passwords"},false,{"VAR=":"given_lockpick"},false,{"VAR=":"given_password_hints"},false,{"VAR=":"discussed_server_room"},false,{"VAR=":"can_clone_card"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/m01_first_contact/ink/m01_npc_sarah.ink b/scenarios/m01_first_contact/ink/m01_npc_sarah.ink index 5113fdb..df33a10 100644 --- a/scenarios/m01_first_contact/ink/m01_npc_sarah.ink +++ b/scenarios/m01_first_contact/ink/m01_npc_sarah.ink @@ -151,6 +151,7 @@ Sarah: And I've seen him in the server room a couple times. Told me he was check + [That does seem odd] ~ influence += 1 + #unlock_task:investigate_derek Sarah: Right? But I'm just the receptionist. What do I know? -> hub + [Maybe he's just thorough] diff --git a/scenarios/m01_first_contact/ink/m01_npc_sarah.json b/scenarios/m01_first_contact/ink/m01_npc_sarah.json index dceea9d..64b26d4 100644 --- a/scenarios/m01_first_contact/ink/m01_npc_sarah.json +++ b/scenarios/m01_first_contact/ink/m01_npc_sarah.json @@ -1 +1 @@ -{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"met_sarah"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev",true,"/ev",{"VAR=":"met_sarah","re":true},"ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Hi! You must be the IT contractor. I'm Sarah, the receptionist.","\n","^Sarah: Let me get you checked in.","\n",{"->":"first_checkin"},{"->":"start.5"},null]}],"nop","\n","ev",{"VAR?":"met_sarah"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Sarah: Hey, need anything else?","\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_checkin":[["ev","str","^Thanks. I'm here to audit your network security","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Just point me to IT and I'll get started","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Oh good! Kevin mentioned you'd be coming.","\n","^Sarah: Let me print your visitor badge.","\n",{"->":"receive_badge"},null],"c-1":["\n","^Sarah: Sure thing. Let me get your badge first.","\n",{"->":"receive_badge"},null]}],null],"receive_badge":["ev",true,"/ev",{"VAR=":"has_badge","re":true},"#","^give_item:id_badge","/#","#","^complete_task:meet_reception","/#","^Sarah: Here you go. This gets you into public areas.","\n","^Sarah: Restricted areas need keycard access or you'll need to ask Kevin.","\n",{"->":"hub"},null],"hub":[["ev","str","^Where can I find Kevin?","/str",{"VAR?":"asked_about_kevin"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Can you tell me about the office layout?","/str",{"VAR?":"asked_about_office"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Anyone working late I should know about?","/str",{"VAR?":"asked_about_derek"},"!",{"VAR?":"influence"},3,">=","&&","/ev",{"*":".^.c-2","flg":5},"ev","str","^Thanks, I'll get started","/str","/ev",{"*":".^.c-3","flg":4},{"c-0":["\n",{"->":"ask_kevin_location"},null],"c-1":["\n",{"->":"ask_office_layout"},null],"c-2":["\n",{"->":"ask_late_workers"},null],"c-3":["\n","#","^exit_conversation","/#","^Sarah: Good luck with the audit!","\n",{"->":"hub"},null]}],null],"ask_kevin_location":[["ev",true,"/ev",{"VAR=":"asked_about_kevin","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Kevin's desk is in the main office area—can't miss it. Covered in monitors and coffee cups.","\n","^Sarah: He's usually there this time of day.","\n","ev","str","^What's he like?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"kevin_personality"},null],"c-1":["\n",{"->":"hub"},null]}],null],"kevin_personality":["ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Super helpful, kind of overworked. The company relies on him way too much.","\n","^Sarah: He'll appreciate having someone competent help out.","\n",{"->":"hub"},null],"ask_office_layout":[["ev",true,"/ev",{"VAR=":"asked_about_office","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Main office is through there—hot-desking setup. Conference room on the west side, break room to the east.","\n","^Sarah: Server room is behind main office, but you'll need Kevin's access for that.","\n","ev","str","^What about executive offices?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it, thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"ask_executive_offices"},null],"c-1":["\n",{"->":"hub"},null]}],null],"ask_executive_offices":["ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Derek's office is off the main area—he's our Senior Marketing Manager. Usually locks his door when he's out.","\n","^Sarah: Most people just have desk space, but Derek got an office because of client confidentiality stuff.","\n",{"->":"hub"},null],"ask_late_workers":[["ev",true,"/ev",{"VAR=":"asked_about_derek","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Derek's usually here late. Like, really late. Sometimes I leave at 6 and he's still working.","\n","^Sarah: He says it's because of client timezones, but...","\n","ev","str","^But what?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Dedication, I guess","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_suspicion"},null],"c-1":["\n",{"->":"hub"},null]}],null],"derek_suspicion":[["ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: I don't know. It just seems weird, you know? He's marketing, not IT.","\n","^Sarah: And I've seen him in the server room a couple times. Told me he was checking on campaign servers.","\n","ev","str","^That does seem odd","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Maybe he's just thorough","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Right? But I'm just the receptionist. What do I know?","\n",{"->":"hub"},null],"c-1":["\n","^Sarah: Maybe. Anyway, Kevin would know more about the technical stuff.","\n",{"->":"hub"},null]}],null],"global decl":["ev",0,{"VAR=":"influence"},false,{"VAR=":"met_sarah"},false,{"VAR=":"has_badge"},false,{"VAR=":"asked_about_derek"},false,{"VAR=":"asked_about_office"},false,{"VAR=":"asked_about_kevin"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"met_sarah"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n","ev",true,"/ev",{"VAR=":"met_sarah","re":true},"ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Hi! You must be the IT contractor. I'm Sarah, the receptionist.","\n","^Sarah: Let me get you checked in.","\n",{"->":"first_checkin"},{"->":"start.5"},null]}],"nop","\n","ev",{"VAR?":"met_sarah"},"/ev",[{"->":".^.b","c":true},{"b":["\n","^Sarah: Hey, need anything else?","\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_checkin":[["ev","str","^Thanks. I'm here to audit your network security","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Just point me to IT and I'll get started","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Oh good! Kevin mentioned you'd be coming.","\n","^Sarah: Let me print your visitor badge.","\n",{"->":"receive_badge"},null],"c-1":["\n","^Sarah: Sure thing. Let me get your badge first.","\n",{"->":"receive_badge"},null]}],null],"receive_badge":["ev",true,"/ev",{"VAR=":"has_badge","re":true},"#","^give_item:id_badge","/#","#","^complete_task:meet_reception","/#","^Sarah: Here you go. This gets you into public areas.","\n","^Sarah: Restricted areas need keycard access or you'll need to ask Kevin.","\n",{"->":"hub"},null],"hub":[["ev","str","^Where can I find Kevin?","/str",{"VAR?":"asked_about_kevin"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Can you tell me about the office layout?","/str",{"VAR?":"asked_about_office"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Anyone working late I should know about?","/str",{"VAR?":"asked_about_derek"},"!",{"VAR?":"influence"},3,">=","&&","/ev",{"*":".^.c-2","flg":5},"ev","str","^Thanks, I'll get started","/str","/ev",{"*":".^.c-3","flg":4},{"c-0":["\n",{"->":"ask_kevin_location"},null],"c-1":["\n",{"->":"ask_office_layout"},null],"c-2":["\n",{"->":"ask_late_workers"},null],"c-3":["\n","#","^exit_conversation","/#","^Sarah: Good luck with the audit!","\n",{"->":"hub"},null]}],null],"ask_kevin_location":[["ev",true,"/ev",{"VAR=":"asked_about_kevin","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Kevin's desk is in the main office area—can't miss it. Covered in monitors and coffee cups.","\n","^Sarah: He's usually there this time of day.","\n","ev","str","^What's he like?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"kevin_personality"},null],"c-1":["\n",{"->":"hub"},null]}],null],"kevin_personality":["ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Super helpful, kind of overworked. The company relies on him way too much.","\n","^Sarah: He'll appreciate having someone competent help out.","\n",{"->":"hub"},null],"ask_office_layout":[["ev",true,"/ev",{"VAR=":"asked_about_office","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Main office is through there—hot-desking setup. Conference room on the west side, break room to the east.","\n","^Sarah: Server room is behind main office, but you'll need Kevin's access for that.","\n","ev","str","^What about executive offices?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it, thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"ask_executive_offices"},null],"c-1":["\n",{"->":"hub"},null]}],null],"ask_executive_offices":["ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Derek's office is off the main area—he's our Senior Marketing Manager. Usually locks his door when he's out.","\n","^Sarah: Most people just have desk space, but Derek got an office because of client confidentiality stuff.","\n",{"->":"hub"},null],"ask_late_workers":[["ev",true,"/ev",{"VAR=":"asked_about_derek","re":true},"ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: Derek's usually here late. Like, really late. Sometimes I leave at 6 and he's still working.","\n","^Sarah: He says it's because of client timezones, but...","\n","ev","str","^But what?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Dedication, I guess","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"derek_suspicion"},null],"c-1":["\n",{"->":"hub"},null]}],null],"derek_suspicion":[["ev",{"VAR?":"influence"},2,"+",{"VAR=":"influence","re":true},"/ev","^Sarah: I don't know. It just seems weird, you know? He's marketing, not IT.","\n","^Sarah: And I've seen him in the server room a couple times. Told me he was checking on campaign servers.","\n","ev","str","^That does seem odd","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Maybe he's just thorough","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","ev",{"VAR?":"influence"},1,"+",{"VAR=":"influence","re":true},"/ev","#","^unlock_task:investigate_derek","/#","^Sarah: Right? But I'm just the receptionist. What do I know?","\n",{"->":"hub"},null],"c-1":["\n","^Sarah: Maybe. Anyway, Kevin would know more about the technical stuff.","\n",{"->":"hub"},null]}],null],"global decl":["ev",0,{"VAR=":"influence"},false,{"VAR=":"met_sarah"},false,{"VAR=":"has_badge"},false,{"VAR=":"asked_about_derek"},false,{"VAR=":"asked_about_office"},false,{"VAR=":"asked_about_kevin"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/m01_first_contact/ink/m01_phone_agent0x99.ink b/scenarios/m01_first_contact/ink/m01_phone_agent0x99.ink index 936b4bb..eafc34a 100644 --- a/scenarios/m01_first_contact/ink/m01_phone_agent0x99.ink +++ b/scenarios/m01_first_contact/ink/m01_phone_agent0x99.ink @@ -186,6 +186,27 @@ Agent 0x99: Remember, you're testing security—officially. + [Any lockpicking tips?] -> lockpick_help +// ================================================ +// EVENT: SERVER ROOM ENTERED +// ================================================ + +=== event_server_room_entered === +#speaker:agent_0x99 +#complete_task:access_server_room +#unlock_task:access_vm + +Agent 0x99: You're in the server room. Good work getting access. + +Agent 0x99: Look for the compromised systems. VM access will give you deeper intelligence. + ++ [What am I looking for?] + Agent 0x99: Evidence of ENTROPY's infrastructure. Backdoors, encrypted communications, anything linking Derek to other cells. + #exit_conversation + -> support_hub ++ [On it] + #exit_conversation + -> support_hub + // ================================================ // EVENT: FIRST FLAG SUBMITTED // ================================================ @@ -210,8 +231,11 @@ Agent 0x99: Each flag unlocks intelligence. Keep correlating VM findings with ph // EVENT: DEREK'S OFFICE ACCESSED // ================================================ -=== event_derek_office === +=== event_derek_office_entered === #speaker:agent_0x99 +#unlock_task:find_campaign_materials +#unlock_task:discover_manifesto +#unlock_task:decode_communications Agent 0x99: You're in Derek's office. Good. diff --git a/scenarios/m01_first_contact/ink/m01_phone_agent0x99.json b/scenarios/m01_first_contact/ink/m01_phone_agent0x99.json index 86885d0..31d85dc 100644 --- a/scenarios/m01_first_contact/ink/m01_phone_agent0x99.json +++ b/scenarios/m01_first_contact/ink/m01_phone_agent0x99.json @@ -1 +1 @@ -{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"first_contact"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev",false,"/ev",{"VAR=":"first_contact","re":true},{"->":"first_call"},{"->":"start.4"},null]}],"nop","\n","ev",{"VAR?":"first_contact"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n",{"->":"support_hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_call":[["#","^speaker:agent_0x99","/#","^Agent 0x99: ","ev",{"VAR?":"player_name"},"out","/ev","^, checking in. How's the infiltration going?","\n","^Agent 0x99: If you need guidance on any challenges, I'm here. That's what handlers are for.","\n","ev","str","^Everything's going smoothly so far","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I could use some tips","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^I'll call if I need help","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","^Agent 0x99: Good. Remember, take your time. Rushing creates mistakes.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null],"c-2":["\n","#","^exit_conversation","/#","^Agent 0x99: Roger that. I'm here when you need me.","\n",{"->":"support_hub"},null]}],null],"support_hub":[["#","^speaker:agent_0x99","/#","^Agent 0x99: What do you need help with?","\n","ev","str","^Lockpicking guidance","/str",{"VAR?":"lockpick_hint_given"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^SSH brute force help","/str",{"VAR?":"ssh_hint_given"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Linux navigation tips","/str",{"VAR?":"linux_hint_given"},"!","/ev",{"*":".^.c-2","flg":5},"ev","str","^Privilege escalation guidance","/str",{"VAR?":"sudo_hint_given"},"!","/ev",{"*":".^.c-3","flg":5},"ev","str","^General mission advice","/str","/ev",{"*":".^.c-4","flg":4},"ev","str","^I'm good for now","/str","/ev",{"*":".^.c-5","flg":4},{"c-0":["\n",{"->":"lockpick_help"},null],"c-1":["\n",{"->":"ssh_help"},null],"c-2":["\n",{"->":"linux_help"},null],"c-3":["\n",{"->":"sudo_help"},null],"c-4":["\n",{"->":"general_advice"},null],"c-5":["\n","#","^exit_conversation","/#","^Agent 0x99: Copy that. Call anytime.","\n",{"->":".^.^.^"},null]}],null],"lockpick_help":[["ev",true,"/ev",{"VAR=":"lockpick_hint_given","re":true},"^Agent 0x99: Lockpicking is about patience and listening.","\n","^Agent 0x99: Each pin has a sweet spot. Apply tension, test each pin, feel for the feedback.","\n","^Agent 0x99: Start with the storage closet practice safe—low stakes, good for learning.","\n","ev","str","^Any other tips?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it, thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Don't force it. If you're stuck, reset and try again. There's no timer.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null]}],null],"ssh_help":[["ev",true,"/ev",{"VAR=":"ssh_hint_given","re":true},"^Agent 0x99: SSH brute force uses Hydra to test password lists against login prompts.","\n","^Agent 0x99: The key is using good password lists. Kevin's hints about \"ViralDynamics2025\" variations are gold.","\n","^Agent 0x99: Command format: hydra -l username -P passwordlist.txt ssh:","\n","ev","str","^What if I don't have a password list?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks, that helps","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Build one from intel. Kevin mentioned patterns, the whiteboard had clues. Social engineering works.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null]}],null],"linux_help":[["ev",true,"/ev",{"VAR=":"linux_hint_given","re":true},"^Agent 0x99: Linux navigation basics: ls lists files, cd changes directory, cat reads files.","\n","^Agent 0x99: Check the home directory first. User files, hidden configs—look for .bashrc, .ssh, personal directories.","\n","^Agent 0x99: Hidden files start with a dot. Use ls -la to see them.","\n","ev","str","^Where should I look for flags?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Home directories, user documents, sometimes hidden in config files. Explore methodically.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null]}],null],"sudo_help":[["ev",true,"/ev",{"VAR=":"sudo_hint_given","re":true},"^Agent 0x99: Privilege escalation means gaining access to other accounts or higher permissions.","\n","^Agent 0x99: Try \"sudo -l\" to see what sudo permissions you have. Some accounts allow switching users.","\n","^Agent 0x99: Command: sudo -u otherusername bash gives you a shell as that user.","\n","ev","str","^What if I don't have sudo access?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Check for misconfigured files, world-writable directories, or SUID binaries. But for this mission, sudo works.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null]}],null],"general_advice":[["^Agent 0x99: Remember the mission priorities: gather evidence, identify operatives, minimize innocent casualties.","\n","^Agent 0x99: Most people at Viral Dynamics are legitimate employees. We want ENTROPY, not collateral damage.","\n","ev","str","^How do I know who's ENTROPY?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^What about Maya?","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^Understood","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","^Agent 0x99: Evidence correlation. Look for encrypted communications, connections to known cells, suspicious behavior.","\n","^Agent 0x99: Derek's our primary suspect, but gather proof before confronting.","\n",{"->":"support_hub"},null],"c-1":["\n","^Agent 0x99: Protect her. She's the informant who brought this to us. Don't expose her unless absolutely necessary.","\n",{"->":"support_hub"},null],"c-2":["\n",{"->":"support_hub"},null]}],null],"event_lockpick_acquired":[["#","^speaker:agent_0x99","/#","^Agent 0x99: I see Kevin gave you lockpicks. Smart social engineering.","\n","^Agent 0x99: Practice on low-risk targets first. Storage closet, unlocked areas.","\n","^Agent 0x99: Remember, you're testing security—officially.","\n","ev","str","^Will do","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Any lockpicking tips?","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n",{"->":"lockpick_help"},null]}],null],"event_first_flag":[["#","^speaker:agent_0x99","/#","^Agent 0x99: First flag submitted. Nice work, ","ev",{"VAR?":"player_name"},"out","/ev","^.","\n","^Agent 0x99: Each flag unlocks intelligence. Keep correlating VM findings with physical evidence.","\n","ev","str","^What should I focus on next?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Continue the VM challenges, but don't forget physical investigation. Derek's office, filing cabinets, computer access.","\n","^Agent 0x99: Hybrid approach—digital and physical evidence together.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"event_derek_office":[["#","^speaker:agent_0x99","/#","^Agent 0x99: You're in Derek's office. Good.","\n","^Agent 0x99: Look for communications, project documents, anything linking him to ENTROPY.","\n","^Agent 0x99: Whiteboard messages, computer files, filing cabinets. Be thorough.","\n","ev","str","^What if Derek catches me?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^On it","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Your cover is solid. You're doing a security audit—accessing offices is expected.","\n","^Agent 0x99: But don't tip your hand too early. Gather evidence before confronting.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"event_all_flags":[["#","^speaker:agent_0x99","/#","^Agent 0x99: All VM flags submitted. Excellent work.","\n","^Agent 0x99: Intelligence confirms Derek Lawson as primary operative, coordinating with Zero Day Syndicate.","\n","^Agent 0x99: Now correlate with physical evidence. Then we can move to confrontation.","\n","ev","str","^What's the confrontation plan?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Roger that","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: That's your call. Direct, silent extraction, or something creative.","\n","^Agent 0x99: I trust your judgment. You've proven capable.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"event_act2_complete":[["#","^speaker:agent_0x99","/#","^Agent 0x99: You've identified the operatives and gathered the evidence.","\n","^Agent 0x99: Time to decide: How do you want to resolve this?","\n","^Agent 0x99: Confrontation, silent extraction, or public exposure. Each has consequences.","\n","ev","str","^I need to think about this","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'm ready to proceed","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Take your time. This is the part where your choices matter most.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","^Agent 0x99: Good luck, ","ev",{"VAR?":"player_name"},"out","/ev","^. You've got this.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"global decl":["ev",false,{"VAR=":"lockpick_hint_given"},false,{"VAR=":"ssh_hint_given"},false,{"VAR=":"linux_hint_given"},false,{"VAR=":"sudo_hint_given"},true,{"VAR=":"first_contact"},"str","^Agent 0x00","/str",{"VAR=":"player_name"},"str","^","/str",{"VAR=":"current_task"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"first_contact"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev",false,"/ev",{"VAR=":"first_contact","re":true},{"->":"first_call"},{"->":"start.4"},null]}],"nop","\n","ev",{"VAR?":"first_contact"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n",{"->":"support_hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_call":[["#","^speaker:agent_0x99","/#","^Agent 0x99: ","ev",{"VAR?":"player_name"},"out","/ev","^, checking in. How's the infiltration going?","\n","^Agent 0x99: If you need guidance on any challenges, I'm here. That's what handlers are for.","\n","ev","str","^Everything's going smoothly so far","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I could use some tips","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^I'll call if I need help","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","^Agent 0x99: Good. Remember, take your time. Rushing creates mistakes.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null],"c-2":["\n","#","^exit_conversation","/#","^Agent 0x99: Roger that. I'm here when you need me.","\n",{"->":"support_hub"},null]}],null],"support_hub":[["#","^speaker:agent_0x99","/#","^Agent 0x99: What do you need help with?","\n","ev","str","^Lockpicking guidance","/str",{"VAR?":"lockpick_hint_given"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^SSH brute force help","/str",{"VAR?":"ssh_hint_given"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Linux navigation tips","/str",{"VAR?":"linux_hint_given"},"!","/ev",{"*":".^.c-2","flg":5},"ev","str","^Privilege escalation guidance","/str",{"VAR?":"sudo_hint_given"},"!","/ev",{"*":".^.c-3","flg":5},"ev","str","^General mission advice","/str","/ev",{"*":".^.c-4","flg":4},"ev","str","^I'm good for now","/str","/ev",{"*":".^.c-5","flg":4},{"c-0":["\n",{"->":"lockpick_help"},null],"c-1":["\n",{"->":"ssh_help"},null],"c-2":["\n",{"->":"linux_help"},null],"c-3":["\n",{"->":"sudo_help"},null],"c-4":["\n",{"->":"general_advice"},null],"c-5":["\n","#","^exit_conversation","/#","^Agent 0x99: Copy that. Call anytime.","\n",{"->":".^.^.^"},null]}],null],"lockpick_help":[["ev",true,"/ev",{"VAR=":"lockpick_hint_given","re":true},"^Agent 0x99: Lockpicking is about patience and listening.","\n","^Agent 0x99: Each pin has a sweet spot. Apply tension, test each pin, feel for the feedback.","\n","^Agent 0x99: Start with the storage closet practice safe—low stakes, good for learning.","\n","ev","str","^Any other tips?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it, thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Don't force it. If you're stuck, reset and try again. There's no timer.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null]}],null],"ssh_help":[["ev",true,"/ev",{"VAR=":"ssh_hint_given","re":true},"^Agent 0x99: SSH brute force uses Hydra to test password lists against login prompts.","\n","^Agent 0x99: The key is using good password lists. Kevin's hints about \"ViralDynamics2025\" variations are gold.","\n","^Agent 0x99: Command format: hydra -l username -P passwordlist.txt ssh:","\n","ev","str","^What if I don't have a password list?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks, that helps","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Build one from intel. Kevin mentioned patterns, the whiteboard had clues. Social engineering works.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null]}],null],"linux_help":[["ev",true,"/ev",{"VAR=":"linux_hint_given","re":true},"^Agent 0x99: Linux navigation basics: ls lists files, cd changes directory, cat reads files.","\n","^Agent 0x99: Check the home directory first. User files, hidden configs—look for .bashrc, .ssh, personal directories.","\n","^Agent 0x99: Hidden files start with a dot. Use ls -la to see them.","\n","ev","str","^Where should I look for flags?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Home directories, user documents, sometimes hidden in config files. Explore methodically.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null]}],null],"sudo_help":[["ev",true,"/ev",{"VAR=":"sudo_hint_given","re":true},"^Agent 0x99: Privilege escalation means gaining access to other accounts or higher permissions.","\n","^Agent 0x99: Try \"sudo -l\" to see what sudo permissions you have. Some accounts allow switching users.","\n","^Agent 0x99: Command: sudo -u otherusername bash gives you a shell as that user.","\n","ev","str","^What if I don't have sudo access?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Check for misconfigured files, world-writable directories, or SUID binaries. But for this mission, sudo works.","\n",{"->":"support_hub"},null],"c-1":["\n",{"->":"support_hub"},null]}],null],"general_advice":[["^Agent 0x99: Remember the mission priorities: gather evidence, identify operatives, minimize innocent casualties.","\n","^Agent 0x99: Most people at Viral Dynamics are legitimate employees. We want ENTROPY, not collateral damage.","\n","ev","str","^How do I know who's ENTROPY?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^What about Maya?","/str","/ev",{"*":".^.c-1","flg":4},"ev","str","^Understood","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n","^Agent 0x99: Evidence correlation. Look for encrypted communications, connections to known cells, suspicious behavior.","\n","^Agent 0x99: Derek's our primary suspect, but gather proof before confronting.","\n",{"->":"support_hub"},null],"c-1":["\n","^Agent 0x99: Protect her. She's the informant who brought this to us. Don't expose her unless absolutely necessary.","\n",{"->":"support_hub"},null],"c-2":["\n",{"->":"support_hub"},null]}],null],"event_lockpick_acquired":[["#","^speaker:agent_0x99","/#","^Agent 0x99: I see Kevin gave you lockpicks. Smart social engineering.","\n","^Agent 0x99: Practice on low-risk targets first. Storage closet, unlocked areas.","\n","^Agent 0x99: Remember, you're testing security—officially.","\n","ev","str","^Will do","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Any lockpicking tips?","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n",{"->":"lockpick_help"},null]}],null],"event_server_room_entered":[["#","^speaker:agent_0x99","/#","#","^complete_task:access_server_room","/#","#","^unlock_task:access_vm","/#","^Agent 0x99: You're in the server room. Good work getting access.","\n","^Agent 0x99: Look for the compromised systems. VM access will give you deeper intelligence.","\n","ev","str","^What am I looking for?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^On it","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Evidence of ENTROPY's infrastructure. Backdoors, encrypted communications, anything linking Derek to other cells.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"event_first_flag":[["#","^speaker:agent_0x99","/#","^Agent 0x99: First flag submitted. Nice work, ","ev",{"VAR?":"player_name"},"out","/ev","^.","\n","^Agent 0x99: Each flag unlocks intelligence. Keep correlating VM findings with physical evidence.","\n","ev","str","^What should I focus on next?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Thanks","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Continue the VM challenges, but don't forget physical investigation. Derek's office, filing cabinets, computer access.","\n","^Agent 0x99: Hybrid approach—digital and physical evidence together.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"event_derek_office_entered":[["#","^speaker:agent_0x99","/#","#","^unlock_task:find_campaign_materials","/#","#","^unlock_task:discover_manifesto","/#","#","^unlock_task:decode_communications","/#","^Agent 0x99: You're in Derek's office. Good.","\n","^Agent 0x99: Look for communications, project documents, anything linking him to ENTROPY.","\n","^Agent 0x99: Whiteboard messages, computer files, filing cabinets. Be thorough.","\n","ev","str","^What if Derek catches me?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^On it","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Your cover is solid. You're doing a security audit—accessing offices is expected.","\n","^Agent 0x99: But don't tip your hand too early. Gather evidence before confronting.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"event_all_flags":[["#","^speaker:agent_0x99","/#","^Agent 0x99: All VM flags submitted. Excellent work.","\n","^Agent 0x99: Intelligence confirms Derek Lawson as primary operative, coordinating with Zero Day Syndicate.","\n","^Agent 0x99: Now correlate with physical evidence. Then we can move to confrontation.","\n","ev","str","^What's the confrontation plan?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Roger that","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: That's your call. Direct, silent extraction, or something creative.","\n","^Agent 0x99: I trust your judgment. You've proven capable.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"event_act2_complete":[["#","^speaker:agent_0x99","/#","^Agent 0x99: You've identified the operatives and gathered the evidence.","\n","^Agent 0x99: Time to decide: How do you want to resolve this?","\n","^Agent 0x99: Confrontation, silent extraction, or public exposure. Each has consequences.","\n","ev","str","^I need to think about this","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I'm ready to proceed","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n","^Agent 0x99: Take your time. This is the part where your choices matter most.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null],"c-1":["\n","^Agent 0x99: Good luck, ","ev",{"VAR?":"player_name"},"out","/ev","^. You've got this.","\n","#","^exit_conversation","/#",{"->":"support_hub"},null]}],null],"global decl":["ev",false,{"VAR=":"lockpick_hint_given"},false,{"VAR=":"ssh_hint_given"},false,{"VAR=":"linux_hint_given"},false,{"VAR=":"sudo_hint_given"},true,{"VAR=":"first_contact"},"str","^Agent 0x00","/str",{"VAR=":"player_name"},"str","^","/str",{"VAR=":"current_task"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/m01_first_contact/ink/m01_terminal_cyberchef.ink b/scenarios/m01_first_contact/ink/m01_terminal_cyberchef.ink index 4b60bbb..1c92dc9 100644 --- a/scenarios/m01_first_contact/ink/m01_terminal_cyberchef.ink +++ b/scenarios/m01_first_contact/ink/m01_terminal_cyberchef.ink @@ -100,7 +100,7 @@ Enter Base64 string from Derek's whiteboard: === whiteboard_decoded === ~ decoded_whiteboard = true -#complete_task:decode_whiteboard +#complete_task:decode_communications DECODING... Base64 → ASCII diff --git a/scenarios/m01_first_contact/ink/m01_terminal_cyberchef.json b/scenarios/m01_first_contact/ink/m01_terminal_cyberchef.json index 32d8e87..2be4056 100644 --- a/scenarios/m01_first_contact/ink/m01_terminal_cyberchef.json +++ b/scenarios/m01_first_contact/ink/m01_terminal_cyberchef.json @@ -1 +1 @@ -{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"first_use"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev",false,"/ev",{"VAR=":"first_use","re":true},{"->":"first_access"},{"->":"start.4"},null]}],"nop","\n","ev",{"VAR?":"first_use"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_access":[["^CYBERCHEF WORKSTATION","\n","^Data Transformation & Analysis Tool","\n","^This tool helps decode and analyze data. Perfect for messages that aren't encrypted, just encoded.","\n","ev","str","^What's the difference between encoding and encryption?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I have something to decode","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"encoding_tutorial"},null],"c-1":["\n",{"->":"hub"},null]}],null],"encoding_tutorial":[["ev",true,"/ev",{"VAR=":"learned_encoding","re":true},"^ENCODING vs. ENCRYPTION:","\n","^Encoding transforms data for compatibility or readability (Base64, URL encoding).","\n","^Encryption transforms data for secrecy using keys (AES, RSA).","\n","^Key difference: Encoding is reversible by anyone. Encryption requires a key.","\n","ev","str","^So Base64 isn't secure?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it. Let me decode something","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"base64_explanation"},null],"c-1":["\n",{"->":"hub"},null]}],null],"base64_explanation":["^Exactly. Base64 is just a way to represent binary data in ASCII text.","\n","^It's used for compatibility, not security. Anyone can decode it instantly.","\n","^If you see Base64, it's likely obfuscation, not real encryption.","\n",{"->":"hub"},null],"hub":[["^CYBERCHEF > Select operation","\n","ev","str","^Decode Base64 message from whiteboard","/str",{"VAR?":"decoded_whiteboard"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Learn about encoding vs encryption","/str",{"VAR?":"learned_encoding"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Exit workstation","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n",{"->":"decode_whiteboard_message"},null],"c-1":["\n",{"->":"encoding_tutorial"},null],"c-2":["\n","#","^exit_conversation","/#","^Workstation session closed.","\n",{"->":"hub"},null]}],null],"decode_whiteboard_message":[["^Enter Base64 string from Derek's whiteboard:","\n","^[Player enters: Q2xpZW50IGxpc3QgdXBkYXRlOiBDb29yZGluYXRpbmcgd2l0aCBaRFMgZm9yIHRlY2huaWNhbCBpbmZyYXN0cnVjdHVyZQ==]","\n","ev","str","^Q2xpZW50IGxpc3QgdXBkYXRlOiBDb29yZGluYXRpbmcgd2l0aCBaRFMgZm9yIHRlY2huaWNhbCBpbmZyYXN0cnVjdHVyZQ==","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Different string","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"whiteboard_decoded"},null],"c-1":["\n",{"->":"decode_retry"},null]}],null],"whiteboard_decoded":["ev",true,"/ev",{"VAR=":"decoded_whiteboard","re":true},"#","^complete_task:decode_whiteboard","/#","^DECODING... Base64 → ASCII","\n","^DECODED MESSAGE:","\n","^\"Client list update: Coordinating with ZDS for technical infrastructure\"","\n","^Analysis: \"ZDS\" likely refers to Zero Day Syndicate, known ENTROPY cell.","\n","^\"Technical infrastructure\" suggests exploit coordination for disinformation campaign.","\n","#","^speaker:agent_0x99","/#","^Agent 0x99: Good find. Derek's coordinating with Zero Day Syndicate. That's a dangerous partnership.","\n","^Agent 0x99: Use this intel to guide your VM investigation. Look for technical infrastructure on the compromised server.","\n",{"->":"hub"},null],"decode_retry":["^ERROR: Invalid Base64 string","\n","^Check Derek's whiteboard carefully. Copy the entire Base64 string exactly as written.","\n",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"decoded_whiteboard"},false,{"VAR=":"learned_encoding"},true,{"VAR=":"first_use"},"str","^Agent 0x00","/str",{"VAR=":"player_name"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"first_use"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev",false,"/ev",{"VAR=":"first_use","re":true},{"->":"first_access"},{"->":"start.4"},null]}],"nop","\n","ev",{"VAR?":"first_use"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_access":[["^CYBERCHEF WORKSTATION","\n","^Data Transformation & Analysis Tool","\n","^This tool helps decode and analyze data. Perfect for messages that aren't encrypted, just encoded.","\n","ev","str","^What's the difference between encoding and encryption?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^I have something to decode","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"encoding_tutorial"},null],"c-1":["\n",{"->":"hub"},null]}],null],"encoding_tutorial":[["ev",true,"/ev",{"VAR=":"learned_encoding","re":true},"^ENCODING vs. ENCRYPTION:","\n","^Encoding transforms data for compatibility or readability (Base64, URL encoding).","\n","^Encryption transforms data for secrecy using keys (AES, RSA).","\n","^Key difference: Encoding is reversible by anyone. Encryption requires a key.","\n","ev","str","^So Base64 isn't secure?","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Got it. Let me decode something","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"base64_explanation"},null],"c-1":["\n",{"->":"hub"},null]}],null],"base64_explanation":["^Exactly. Base64 is just a way to represent binary data in ASCII text.","\n","^It's used for compatibility, not security. Anyone can decode it instantly.","\n","^If you see Base64, it's likely obfuscation, not real encryption.","\n",{"->":"hub"},null],"hub":[["^CYBERCHEF > Select operation","\n","ev","str","^Decode Base64 message from whiteboard","/str",{"VAR?":"decoded_whiteboard"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Learn about encoding vs encryption","/str",{"VAR?":"learned_encoding"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Exit workstation","/str","/ev",{"*":".^.c-2","flg":4},{"c-0":["\n",{"->":"decode_whiteboard_message"},null],"c-1":["\n",{"->":"encoding_tutorial"},null],"c-2":["\n","#","^exit_conversation","/#","^Workstation session closed.","\n",{"->":"hub"},null]}],null],"decode_whiteboard_message":[["^Enter Base64 string from Derek's whiteboard:","\n","^[Player enters: Q2xpZW50IGxpc3QgdXBkYXRlOiBDb29yZGluYXRpbmcgd2l0aCBaRFMgZm9yIHRlY2huaWNhbCBpbmZyYXN0cnVjdHVyZQ==]","\n","ev","str","^Q2xpZW50IGxpc3QgdXBkYXRlOiBDb29yZGluYXRpbmcgd2l0aCBaRFMgZm9yIHRlY2huaWNhbCBpbmZyYXN0cnVjdHVyZQ==","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Different string","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"whiteboard_decoded"},null],"c-1":["\n",{"->":"decode_retry"},null]}],null],"whiteboard_decoded":["ev",true,"/ev",{"VAR=":"decoded_whiteboard","re":true},"#","^complete_task:decode_communications","/#","^DECODING... Base64 → ASCII","\n","^DECODED MESSAGE:","\n","^\"Client list update: Coordinating with ZDS for technical infrastructure\"","\n","^Analysis: \"ZDS\" likely refers to Zero Day Syndicate, known ENTROPY cell.","\n","^\"Technical infrastructure\" suggests exploit coordination for disinformation campaign.","\n","#","^speaker:agent_0x99","/#","^Agent 0x99: Good find. Derek's coordinating with Zero Day Syndicate. That's a dangerous partnership.","\n","^Agent 0x99: Use this intel to guide your VM investigation. Look for technical infrastructure on the compromised server.","\n",{"->":"hub"},null],"decode_retry":["^ERROR: Invalid Base64 string","\n","^Check Derek's whiteboard carefully. Copy the entire Base64 string exactly as written.","\n",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"decoded_whiteboard"},false,{"VAR=":"learned_encoding"},true,{"VAR=":"first_use"},"str","^Agent 0x00","/str",{"VAR=":"player_name"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/m01_first_contact/ink/m01_terminal_dropsite.ink b/scenarios/m01_first_contact/ink/m01_terminal_dropsite.ink index 5b784cb..c983975 100644 --- a/scenarios/m01_first_contact/ink/m01_terminal_dropsite.ink +++ b/scenarios/m01_first_contact/ink/m01_terminal_dropsite.ink @@ -30,6 +30,10 @@ VAR player_name = "Agent 0x00" // ================================================ === first_access === +#unlock_task:submit_ssh_flag +#unlock_task:submit_linux_flag +#unlock_task:submit_sudo_flag + SAFETYNET DROP-SITE TERMINAL Secure Flag Submission Interface v2.3.1 @@ -117,7 +121,7 @@ Enter Linux Navigation Flag: === navigation_success === ~ navigation_flag_submitted = true -#complete_task:submit_navigation_flag +#complete_task:submit_linux_flag ✓ FLAG VERIFIED: Linux Navigation @@ -151,6 +155,7 @@ Enter Privilege Escalation Flag: === sudo_success === ~ sudo_flag_submitted = true #complete_task:submit_sudo_flag +#unlock_task:confront_derek ✓ FLAG VERIFIED: Privilege Escalation @@ -160,7 +165,7 @@ Bystander account files reveal Derek Lawson's coordination with Zero Day Syndica Evidence: Encrypted communications referencing "Phase 3" election manipulation timeline. -Agent 0x99: This confirms Derek is the primary operative. Gather physical evidence to correlate. +Agent 0x99: This confirms Derek is the primary operative. You now have sufficient evidence to confront him. -> hub diff --git a/scenarios/m01_first_contact/ink/m01_terminal_dropsite.json b/scenarios/m01_first_contact/ink/m01_terminal_dropsite.json index c95a079..8ce0369 100644 --- a/scenarios/m01_first_contact/ink/m01_terminal_dropsite.json +++ b/scenarios/m01_first_contact/ink/m01_terminal_dropsite.json @@ -1 +1 @@ -{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"first_use"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev",false,"/ev",{"VAR=":"first_use","re":true},{"->":"first_access"},{"->":"start.4"},null]}],"nop","\n","ev",{"VAR?":"first_use"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_access":[["^SAFETYNET DROP-SITE TERMINAL","\n","^Secure Flag Submission Interface v2.3.1","\n","^This terminal accepts flags from VM challenges. Each flag unlocks intelligence or resources.","\n","ev","str","^View available flag categories","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Submit a flag","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"flag_categories"},null],"c-1":["\n",{"->":"hub"},null]}],null],"flag_categories":[["^AVAILABLE CATEGORIES:","\n",["^SSH Access (Brute Force)","\n",["^Linux Navigation (File System)","\n",["^Privilege Escalation (Sudo)","\n","^Each successful submission provides actionable intelligence.","\n",{"->":"hub"},{"#n":"g-2"}],{"#n":"g-1"}],{"#n":"g-0"}],null],null],"hub":[["^SAFETYNET DROP-SITE > Ready for submission","\n","ev","str","^Submit SSH Access Flag","/str",{"VAR?":"ssh_flag_submitted"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Submit Linux Navigation Flag","/str",{"VAR?":"navigation_flag_submitted"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Submit Privilege Escalation Flag","/str",{"VAR?":"sudo_flag_submitted"},"!","/ev",{"*":".^.c-2","flg":5},"ev","str","^Exit terminal","/str","/ev",{"*":".^.c-3","flg":4},{"c-0":["\n",{"->":"submit_ssh"},null],"c-1":["\n",{"->":"submit_navigation"},null],"c-2":["\n",{"->":"submit_sudo"},null],"c-3":["\n","#","^exit_conversation","/#","^Terminal session closed.","\n",{"->":"hub"},null]}],null],"submit_ssh":[["^Enter SSH Access Flag:","\n","^[Player enters flag from VM - Hydra brute force]","\n","ev","str","^FLAG_SSH_BRUTE_FORCE_SUCCESS","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Wrong flag","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"ssh_success"},null],"c-1":["\n",{"->":"ssh_retry"},null]}],null],"ssh_success":["ev",true,"/ev",{"VAR=":"ssh_flag_submitted","re":true},"#","^complete_task:submit_ssh_flag","/#","^✓ FLAG VERIFIED: SSH Access","\n","^Intelligence unlocked: Credentials provide access to victim user account on compromised server.","\n","^Agent 0x99 has been notified. Proceed with Linux navigation challenges.","\n",{"->":"hub"},null],"ssh_retry":["^✗ FLAG REJECTED","\n","^Check your VM terminal output. Flag format should match exactly.","\n",{"->":"hub"},null],"submit_navigation":[["^Enter Linux Navigation Flag:","\n","^[Player enters flag from VM - found in home directory]","\n","ev","str","^FLAG_LINUX_NAVIGATION_COMPLETE","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Wrong flag","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"navigation_success"},null],"c-1":["\n",{"->":"navigation_retry"},null]}],null],"navigation_success":["ev",true,"/ev",{"VAR=":"navigation_flag_submitted","re":true},"#","^complete_task:submit_navigation_flag","/#","^✓ FLAG VERIFIED: Linux Navigation","\n","^Intelligence unlocked: File system mapping reveals additional user accounts. Investigate privilege escalation options.","\n","^Agent 0x99: Good work. Look for sudo access or other privilege escalation vectors.","\n",{"->":"hub"},null],"navigation_retry":["^✗ FLAG REJECTED","\n","^Navigate the victim's file system carefully. Check hidden files and directories.","\n",{"->":"hub"},null],"submit_sudo":[["^Enter Privilege Escalation Flag:","\n","^[Player enters flag from VM - bystander account access]","\n","ev","str","^FLAG_SUDO_ESCALATION_COMPLETE","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Wrong flag","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"sudo_success"},null],"c-1":["\n",{"->":"sudo_retry"},null]}],null],"sudo_success":["ev",true,"/ev",{"VAR=":"sudo_flag_submitted","re":true},"#","^complete_task:submit_sudo_flag","/#","^✓ FLAG VERIFIED: Privilege Escalation","\n","^CRITICAL INTELLIGENCE UNLOCKED:","\n","^Bystander account files reveal Derek Lawson's coordination with Zero Day Syndicate cell.","\n","^Evidence: Encrypted communications referencing \"Phase 3\" election manipulation timeline.","\n","^Agent 0x99: This confirms Derek is the primary operative. Gather physical evidence to correlate.","\n",{"->":"hub"},null],"sudo_retry":["^✗ FLAG REJECTED","\n","^Use sudo commands to access other user accounts. Check for lateral movement opportunities.","\n",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"ssh_flag_submitted"},false,{"VAR=":"navigation_flag_submitted"},false,{"VAR=":"sudo_flag_submitted"},true,{"VAR=":"first_use"},"str","^Agent 0x00","/str",{"VAR=":"player_name"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file +{"inkVersion":21,"root":[[["done",{"#n":"g-0"}],null],"done",{"start":["ev",{"VAR?":"first_use"},"/ev",[{"->":".^.b","c":true},{"b":["\n","ev",false,"/ev",{"VAR=":"first_use","re":true},{"->":"first_access"},{"->":"start.4"},null]}],"nop","\n","ev",{"VAR?":"first_use"},"!","/ev",[{"->":".^.b","c":true},{"b":["\n",{"->":"hub"},{"->":"start.11"},null]}],"nop","\n",null],"first_access":[["#","^unlock_task:submit_ssh_flag","/#","#","^unlock_task:submit_linux_flag","/#","#","^unlock_task:submit_sudo_flag","/#","^SAFETYNET DROP-SITE TERMINAL","\n","^Secure Flag Submission Interface v2.3.1","\n","^This terminal accepts flags from VM challenges. Each flag unlocks intelligence or resources.","\n","ev","str","^View available flag categories","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Submit a flag","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"flag_categories"},null],"c-1":["\n",{"->":"hub"},null]}],null],"flag_categories":[["^AVAILABLE CATEGORIES:","\n",["^SSH Access (Brute Force)","\n",["^Linux Navigation (File System)","\n",["^Privilege Escalation (Sudo)","\n","^Each successful submission provides actionable intelligence.","\n",{"->":"hub"},{"#n":"g-2"}],{"#n":"g-1"}],{"#n":"g-0"}],null],null],"hub":[["^SAFETYNET DROP-SITE > Ready for submission","\n","ev","str","^Submit SSH Access Flag","/str",{"VAR?":"ssh_flag_submitted"},"!","/ev",{"*":".^.c-0","flg":5},"ev","str","^Submit Linux Navigation Flag","/str",{"VAR?":"navigation_flag_submitted"},"!","/ev",{"*":".^.c-1","flg":5},"ev","str","^Submit Privilege Escalation Flag","/str",{"VAR?":"sudo_flag_submitted"},"!","/ev",{"*":".^.c-2","flg":5},"ev","str","^Exit terminal","/str","/ev",{"*":".^.c-3","flg":4},{"c-0":["\n",{"->":"submit_ssh"},null],"c-1":["\n",{"->":"submit_navigation"},null],"c-2":["\n",{"->":"submit_sudo"},null],"c-3":["\n","#","^exit_conversation","/#","^Terminal session closed.","\n",{"->":"hub"},null]}],null],"submit_ssh":[["^Enter SSH Access Flag:","\n","^[Player enters flag from VM - Hydra brute force]","\n","ev","str","^FLAG_SSH_BRUTE_FORCE_SUCCESS","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Wrong flag","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"ssh_success"},null],"c-1":["\n",{"->":"ssh_retry"},null]}],null],"ssh_success":["ev",true,"/ev",{"VAR=":"ssh_flag_submitted","re":true},"#","^complete_task:submit_ssh_flag","/#","^✓ FLAG VERIFIED: SSH Access","\n","^Intelligence unlocked: Credentials provide access to victim user account on compromised server.","\n","^Agent 0x99 has been notified. Proceed with Linux navigation challenges.","\n",{"->":"hub"},null],"ssh_retry":["^✗ FLAG REJECTED","\n","^Check your VM terminal output. Flag format should match exactly.","\n",{"->":"hub"},null],"submit_navigation":[["^Enter Linux Navigation Flag:","\n","^[Player enters flag from VM - found in home directory]","\n","ev","str","^FLAG_LINUX_NAVIGATION_COMPLETE","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Wrong flag","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"navigation_success"},null],"c-1":["\n",{"->":"navigation_retry"},null]}],null],"navigation_success":["ev",true,"/ev",{"VAR=":"navigation_flag_submitted","re":true},"#","^complete_task:submit_linux_flag","/#","^✓ FLAG VERIFIED: Linux Navigation","\n","^Intelligence unlocked: File system mapping reveals additional user accounts. Investigate privilege escalation options.","\n","^Agent 0x99: Good work. Look for sudo access or other privilege escalation vectors.","\n",{"->":"hub"},null],"navigation_retry":["^✗ FLAG REJECTED","\n","^Navigate the victim's file system carefully. Check hidden files and directories.","\n",{"->":"hub"},null],"submit_sudo":[["^Enter Privilege Escalation Flag:","\n","^[Player enters flag from VM - bystander account access]","\n","ev","str","^FLAG_SUDO_ESCALATION_COMPLETE","/str","/ev",{"*":".^.c-0","flg":4},"ev","str","^Wrong flag","/str","/ev",{"*":".^.c-1","flg":4},{"c-0":["\n",{"->":"sudo_success"},null],"c-1":["\n",{"->":"sudo_retry"},null]}],null],"sudo_success":["ev",true,"/ev",{"VAR=":"sudo_flag_submitted","re":true},"#","^complete_task:submit_sudo_flag","/#","#","^unlock_task:confront_derek","/#","^✓ FLAG VERIFIED: Privilege Escalation","\n","^CRITICAL INTELLIGENCE UNLOCKED:","\n","^Bystander account files reveal Derek Lawson's coordination with Zero Day Syndicate cell.","\n","^Evidence: Encrypted communications referencing \"Phase 3\" election manipulation timeline.","\n","^Agent 0x99: This confirms Derek is the primary operative. You now have sufficient evidence to confront him.","\n",{"->":"hub"},null],"sudo_retry":["^✗ FLAG REJECTED","\n","^Use sudo commands to access other user accounts. Check for lateral movement opportunities.","\n",{"->":"hub"},null],"global decl":["ev",false,{"VAR=":"ssh_flag_submitted"},false,{"VAR=":"navigation_flag_submitted"},false,{"VAR=":"sudo_flag_submitted"},true,{"VAR=":"first_use"},"str","^Agent 0x00","/str",{"VAR=":"player_name"},"/ev","end",null]}],"listDefs":{}} \ No newline at end of file diff --git a/scenarios/m01_first_contact/scenario.json.erb b/scenarios/m01_first_contact/scenario.json.erb index 8959233..ec91ba2 100644 --- a/scenarios/m01_first_contact/scenario.json.erb +++ b/scenarios/m01_first_contact/scenario.json.erb @@ -16,12 +16,126 @@ def base64_encode(text) end # Narrative Content Variables -client_list_message = "Client list update: Coordinating with ZDS for technical infrastructure deployment. Phase 3 timeline: 2 weeks." +client_list_message = "Client list update: Coordinating with ZDS for technical infrastructure deployment. Phase 3 timeline: 2 weeks. FILING_CABINET_PIN: 0419 (Derek's bday - don't forget again!)" password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_Admin, Derek0419" %> { "scenario_brief": "Infiltrate Viral Dynamics Media to investigate suspected ENTROPY operations. Gather evidence of disinformation campaigns while maintaining cover as an IT contractor.", + "objectives": [ + { + "aimId": "identify_operatives", + "title": "Identify ENTROPY Operatives", + "description": "Discover who inside Viral Dynamics is working for ENTROPY", + "status": "active", + "order": 0, + "tasks": [ + { + "taskId": "meet_reception", + "title": "Check in at reception", + "type": "npc_conversation", + "targetNPC": "sarah_martinez", + "status": "active" + }, + { + "taskId": "meet_kevin", + "title": "Meet the IT manager", + "type": "npc_conversation", + "targetNPC": "kevin_park", + "status": "active" + }, + { + "taskId": "investigate_derek", + "title": "Investigate Derek Lawson's office", + "type": "enter_room", + "targetRoom": "derek_office", + "status": "locked" + }, + { + "taskId": "confront_derek", + "title": "Confront the ENTROPY operative", + "type": "npc_conversation", + "targetNPC": "derek_lawson", + "status": "locked" + } + ] + }, + { + "aimId": "gather_evidence", + "title": "Gather Evidence", + "description": "Collect proof of the disinformation operation", + "status": "active", + "order": 1, + "tasks": [ + { + "taskId": "find_campaign_materials", + "title": "Find campaign materials", + "type": "collect_items", + "targetItems": ["notes"], + "status": "locked" + }, + { + "taskId": "discover_manifesto", + "title": "Discover ENTROPY manifesto", + "type": "collect_items", + "targetItems": ["notes"], + "status": "locked" + }, + { + "taskId": "decode_communications", + "title": "Decode encrypted communications", + "type": "unlock_object", + "targetObject": "cyberchef_workstation", + "status": "locked" + } + ] + }, + { + "aimId": "intercept_comms", + "title": "Intercept Communications", + "description": "Access server systems and intercept ENTROPY communications", + "status": "active", + "order": 2, + "tasks": [ + { + "taskId": "access_server_room", + "title": "Access the server room", + "type": "enter_room", + "targetRoom": "server_room", + "status": "locked" + }, + { + "taskId": "access_vm", + "title": "Access compromised systems", + "type": "unlock_object", + "targetObject": "vm_launcher_intro_linux", + "status": "locked" + }, + { + "taskId": "submit_ssh_flag", + "title": "Submit SSH access evidence", + "type": "unlock_object", + "targetObject": "flag_station_dropsite", + "status": "locked" + }, + { + "taskId": "submit_linux_flag", + "title": "Submit Linux navigation evidence", + "type": "unlock_object", + "targetObject": "flag_station_dropsite", + "status": "locked" + }, + { + "taskId": "submit_sudo_flag", + "title": "Submit privilege escalation evidence", + "type": "unlock_object", + "targetObject": "flag_station_dropsite", + "status": "locked" + } + ] + } + ], + "startRoom": "reception_area", "startItemsInInventory": [ @@ -92,6 +206,49 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "observations": "Temporary visitor badge for office access" } ] + }, + { + "id": "agent_0x99", + "displayName": "Agent 0x99 'Haxolottle'", + "npcType": "phone", + "storyPath": "scenarios/m01_first_contact/ink/m01_phone_agent0x99.json", + "avatar": "assets/npc/avatars/npc_helper.png", + "phoneId": "player_phone", + "currentKnot": "start", + "eventMappings": [ + { + "eventPattern": "item_picked_up:lockpick", + "targetKnot": "event_lockpick_acquired", + "onceOnly": true + }, + { + "eventPattern": "room_entered:server_room", + "targetKnot": "event_server_room_entered", + "onceOnly": true + }, + { + "eventPattern": "room_entered:derek_office", + "targetKnot": "event_derek_office_entered", + "onceOnly": true + } + ] + }, + { + "id": "closing_debrief_trigger", + "displayName": "Agent 0x99", + "npcType": "phone", + "storyPath": "scenarios/m01_first_contact/ink/m01_closing_debrief.json", + "avatar": "assets/npc/avatars/npc_helper.png", + "phoneId": "player_phone", + "currentKnot": "start", + "eventMappings": [ + { + "eventPattern": "global_variable_changed:derek_confronted", + "targetKnot": "start", + "condition": "value === true", + "onceOnly": true + } + ] } ], "objects": [ @@ -109,12 +266,10 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "main_office_area": { "type": "room_office", "connections": { - "south": "reception_area", - "north": "derek_office", + "south": ["reception_area", "break_room"], + "north": ["derek_office", "storage_closet"], "east": "server_room", - "west": "conference_room", - "southeast": "break_room", - "northwest": "storage_closet" + "west": "conference_room" }, "npcs": [ { @@ -188,10 +343,9 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "name": "Filing Cabinet", "takeable": false, "locked": true, - "lockType": "key", - "requires": "lockpick", - "difficulty": "medium", - "observations": "Locked filing cabinet - might contain useful documents", + "lockType": "pin", + "pin": "2024", + "observations": "Digital filing cabinet with keypad lock - requires 4-digit PIN", "contents": [ { "type": "notes", @@ -202,6 +356,22 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "observations": "Encrypted correspondence revealing ENTROPY leadership structure" } ] + }, + { + "type": "notes", + "name": "Sticky Note", + "takeable": true, + "readable": true, + "text": "Cabinet PIN:\nElection year = access code\n\n(Remember: 2024)", + "observations": "Password reminder stuck to desk" + }, + { + "type": "notes", + "name": "Maintenance Checklist", + "takeable": true, + "readable": true, + "text": "STORAGE CLOSET MAINTENANCE LOG\n\nWeekly Tasks:\n- [ ] Check fire extinguisher pressure\n- [ ] Test emergency lighting\n- [ ] Verify safe backup code: 1337 (leet!)\n- [ ] Inventory cleaning supplies\n\nLast checked: 2 weeks ago", + "observations": "Maintenance checklist left on desk" } ] }, @@ -226,7 +396,7 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "idleFrameStart": 20, "idleFrameEnd": 23 }, - "storyPath": "scenarios/m01_first_contact/ink/m01_npc_derek.json", + "storyPath": "scenarios/m01_first_contact/ink/m01_derek_confrontation.json", "currentKnot": "start" } ], @@ -244,10 +414,9 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "name": "Derek's Filing Cabinet", "takeable": false, "locked": true, - "lockType": "key", - "requires": "lockpick", - "difficulty": "medium", - "observations": "Executive filing cabinet with secure lock", + "lockType": "pin", + "pin": "0419", + "observations": "Executive filing cabinet with electronic keypad - requires 4-digit PIN", "contents": [ { "type": "notes", @@ -280,20 +449,39 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A }, "objects": [ { - "type": "pc", + "type": "vm-launcher", + "id": "vm_launcher_intro_linux", "name": "VM Access Terminal", "takeable": false, - "observations": "Terminal providing access to compromised systems for investigation", - "vmAccess": true, - "vmScenario": "intro_to_linux_security_lab" + "observations": "Terminal providing access to compromised Social Fabric infrastructure for investigation", + "hacktivityMode": <%= vm_context && vm_context['hacktivity_mode'] ? 'true' : 'false' %>, + "vm": <%= vm_object('intro_to_linux_security_lab', {"id":1,"title":"Social Fabric Server","ip":"192.168.100.50","enable_console":true}) %> }, { - "type": "pc", + "type": "flag-station", + "id": "flag_station_dropsite", "name": "SAFETYNET Drop-Site Terminal", "takeable": false, - "storyPath": "scenarios/m01_first_contact/ink/m01_terminal_dropsite.json", - "currentKnot": "start", - "observations": "Secure terminal for submitting intercepted intelligence" + "observations": "Secure terminal for submitting intercepted intelligence and VM flags", + "acceptsVms": ["intro_to_linux_security_lab"], + "flags": <%= flags_for_vm('intro_to_linux_security_lab', ['flag{ssh_brute_force_success}', 'flag{linux_navigation_complete}', 'flag{privilege_escalation_success}']) %>, + "flagRewards": [ + { + "type": "emit_event", + "event_name": "ssh_flag_submitted", + "description": "SSH access flag submitted - unlocks server intelligence" + }, + { + "type": "emit_event", + "event_name": "navigation_flag_submitted", + "description": "Linux navigation flag submitted - reveals file system evidence" + }, + { + "type": "emit_event", + "event_name": "sudo_flag_submitted", + "description": "Privilege escalation flag submitted - unlocks root-level evidence" + } + ] }, { "type": "notes", @@ -326,7 +514,7 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "break_room": { "type": "room_office", "connections": { - "northwest": "main_office_area" + "north": "main_office_area" }, "objects": [ { @@ -342,12 +530,9 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "storage_closet": { "type": "room_closet", - "locked": true, - "lockType": "key", - "requires": "lockpick", - "difficulty": "tutorial", + "locked": false, "connections": { - "southeast": "main_office_area" + "south": "main_office_area" }, "objects": [ { @@ -355,17 +540,15 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "name": "Storage Safe", "takeable": false, "locked": true, - "lockType": "key", - "requires": "lockpick", - "difficulty": "tutorial", - "observations": "Practice safe for lockpicking tutorial", + "lockType": "pin", + "pin": "1337", + "observations": "Electronic keypad safe - needs 4-digit PIN code", "contents": [ { "type": "key", "name": "Derek's Office Key", "takeable": true, "key_id": "derek_office_key", - "keyPins": [45, 35, 55, 25], "observations": "Spare key to Derek Lawson's office" } ] @@ -389,51 +572,5 @@ password_hints = "Common passwords: Marketing123, Campaign2024, Viral_Dynamics_A "linux_flag_submitted": false, "sudo_flag_submitted": false, "derek_confronted": false - }, - - "phoneNPCs": [ - { - "id": "agent_0x99", - "displayName": "Agent 0x99 'Haxolottle'", - "npcType": "phone", - "storyPath": "scenarios/m01_first_contact/ink/m01_phone_agent0x99.json", - "avatar": "assets/npc/avatars/npc_helper.png", - "phoneId": "player_phone", - "currentKnot": "start", - "eventMappings": [ - { - "eventPattern": "item_picked_up:lockpick", - "targetKnot": "event_lockpick_acquired", - "onceOnly": true - }, - { - "eventPattern": "room_entered:server_room", - "targetKnot": "event_server_room_entered", - "onceOnly": true - }, - { - "eventPattern": "room_entered:derek_office", - "targetKnot": "event_derek_office_entered", - "onceOnly": true - } - ] - }, - { - "id": "closing_debrief_trigger", - "displayName": "Agent 0x99", - "npcType": "phone", - "storyPath": "scenarios/m01_first_contact/ink/m01_closing_debrief.json", - "avatar": "assets/npc/avatars/npc_helper.png", - "phoneId": "player_phone", - "currentKnot": "start", - "eventMappings": [ - { - "eventPattern": "global_variable_changed:derek_confronted", - "targetKnot": "start", - "condition": "value === true", - "onceOnly": true - } - ] - } - ] + } } diff --git a/scripts/scenario-schema.json b/scripts/scenario-schema.json index d4ce266..3afe2d1 100644 --- a/scripts/scenario-schema.json +++ b/scripts/scenario-schema.json @@ -163,7 +163,7 @@ }, "difficulty": { "type": "string", - "enum": ["easy", "medium", "hard"] + "enum": ["easy", "medium", "hard", "tutorial"] }, "door_sign": { "type": "string" }, "objects": { @@ -189,8 +189,8 @@ "position": { "type": "object", "properties": { - "x": { "type": "integer" }, - "y": { "type": "integer" } + "x": { "type": "number" }, + "y": { "type": "number" } }, "required": ["x", "y"] }, @@ -285,8 +285,11 @@ "pin-cracker", "vm-launcher", "flag-station", - "text_file" - ] + "text_file", + "id_badge", + "rfid_cloner" + ], + "description": "Item type. Custom types (like 'id_badge', 'rfid_cloner') are valid for #give_item tags in Ink scripts." }, "id": { "type": "string" }, "name": { "type": "string" }, @@ -320,7 +323,7 @@ "card_id": { "type": "string" }, "difficulty": { "type": "string", - "enum": ["easy", "medium", "hard"] + "enum": ["easy", "medium", "hard", "tutorial"] }, "passwordHint": { "type": "string" }, "showHint": { "type": "boolean" }, diff --git a/scripts/validate_scenario.rb b/scripts/validate_scenario.rb index d6003eb..dfe1327 100755 --- a/scripts/validate_scenario.rb +++ b/scripts/validate_scenario.rb @@ -12,6 +12,7 @@ require 'erb' require 'json' require 'optparse' require 'pathname' +require 'set' # Try to load json-schema gem, provide helpful error if missing begin @@ -103,6 +104,388 @@ rescue JSON::ParserError => e raise "Invalid JSON schema: #{e.message}" end +# Check for common issues and structural problems +def check_common_issues(json_data) + issues = [] + start_room_id = json_data['startRoom'] + + # Valid directions for room connections + valid_directions = %w[north south east west] + + # Track features for suggestions + has_vm_launcher = false + has_flag_station = false + has_pc_with_files = false + has_phone_npc_with_messages = false + has_phone_npc_with_events = false + has_opening_cutscene = false + has_closing_debrief = false + has_person_npcs = false + has_npc_with_waypoints = false + has_phone_contacts = false + phone_npcs_without_messages = [] + lock_types_used = Set.new + has_rfid_lock = false + has_bluetooth_lock = false + has_pin_lock = false + has_password_lock = false + has_key_lock = false + has_security_tools = false + has_container_with_contents = false + has_readable_items = false + + # Check rooms + if json_data['rooms'] + json_data['rooms'].each do |room_id, room| + # Check for invalid room connection directions (diagonal directions) + if room['connections'] + room['connections'].each do |direction, target| + unless valid_directions.include?(direction) + issues << "❌ INVALID: Room '#{room_id}' uses invalid direction '#{direction}' - only north, south, east, west are valid (not northeast, southeast, etc.)" + end + + # Check reverse connections if target is a single room + if target.is_a?(String) && json_data['rooms'][target] + reverse_dir = case direction + when 'north' then 'south' + when 'south' then 'north' + when 'east' then 'west' + when 'west' then 'east' + end + target_room = json_data['rooms'][target] + if target_room['connections'] + has_reverse = target_room['connections'].any? do |dir, targets| + (dir == reverse_dir) && (targets == room_id || (targets.is_a?(Array) && targets.include?(room_id))) + end + unless has_reverse + issues << "⚠ WARNING: Room '#{room_id}' connects #{direction} to '#{target}', but '#{target}' doesn't connect #{reverse_dir} back - bidirectional connections recommended" + end + end + end + end + end + + # Check room objects + if room['objects'] + room['objects'].each_with_index do |obj, idx| + path = "rooms/#{room_id}/objects[#{idx}]" + + # Check for incorrect VM launcher configuration (type: "pc" with vmAccess) + if obj['type'] == 'pc' && obj['vmAccess'] + issues << "❌ INVALID: '#{path}' uses type: 'pc' with vmAccess - should use type: 'vm-launcher' instead. See scenarios/secgen_vm_lab/scenario.json.erb for example" + end + + # Track VM launchers + if obj['type'] == 'vm-launcher' + has_vm_launcher = true + unless obj['vm'] + issues << "⚠ WARNING: '#{path}' (vm-launcher) missing 'vm' object - use ERB helper vm_object()" + end + unless obj.key?('hacktivityMode') + issues << "⚠ WARNING: '#{path}' (vm-launcher) missing 'hacktivityMode' field" + end + end + + # Track flag stations + if obj['type'] == 'flag-station' + has_flag_station = true + unless obj['acceptsVms'] && !obj['acceptsVms'].empty? + issues << "⚠ WARNING: '#{path}' (flag-station) missing or empty 'acceptsVms' array" + end + unless obj['flags'] + issues << "⚠ WARNING: '#{path}' (flag-station) missing 'flags' array - use ERB helper flags_for_vm()" + end + end + + # Check for PC containers with files + if obj['type'] == 'pc' && obj['contents'] && obj['contents'].any? { |item| item['type'] == 'text_file' || item['readable'] } + has_pc_with_files = true + end + + # Track containers with contents (safes, suitcases, etc.) + if (obj['type'] == 'safe' || obj['type'] == 'suitcase') && obj['contents'] && !obj['contents'].empty? + has_container_with_contents = true + end + + # Track readable items (notes, documents) + if obj['readable'] || (obj['type'] == 'notes' && obj['text']) + has_readable_items = true + end + + # Track security tools + if ['fingerprint_kit', 'pin-cracker', 'bluetooth_scanner', 'rfid_cloner'].include?(obj['type']) + has_security_tools = true + end + + # Track lock types + if obj['locked'] && obj['lockType'] + lock_types_used.add(obj['lockType']) + case obj['lockType'] + when 'rfid' + has_rfid_lock = true + when 'bluetooth' + has_bluetooth_lock = true + when 'pin' + has_pin_lock = true + when 'password' + has_password_lock = true + when 'key' + has_key_lock = true + # Check for key locks without keyPins (REQUIRED, not recommended) + unless obj['keyPins'] + issues << "❌ INVALID: '#{path}' has lockType: 'key' but missing required 'keyPins' array - key locks must specify keyPins array for lockpicking minigame" + end + end + end + + # Check for key items without keyPins (REQUIRED, not recommended) + if obj['type'] == 'key' && !obj['keyPins'] + issues << "❌ INVALID: '#{path}' (key item) missing required 'keyPins' array - key items must specify keyPins array for lockpicking" + end + + # Check for items with id field (should use type field for #give_item tags) + if obj['itemsHeld'] + obj['itemsHeld'].each_with_index do |item, item_idx| + if item['id'] + issues << "❌ INVALID: '#{path}/itemsHeld[#{item_idx}]' has 'id' field - items should NOT have 'id' field. Use 'type' field to match #give_item tag parameter" + end + end + end + end + end + + # Track room lock types + if room['locked'] && room['lockType'] + lock_types_used.add(room['lockType']) + case room['lockType'] + when 'rfid' + has_rfid_lock = true + when 'bluetooth' + has_bluetooth_lock = true + when 'pin' + has_pin_lock = true + when 'password' + has_password_lock = true + when 'key' + has_key_lock = true + # Check for key locks without keyPins (REQUIRED, not recommended) + unless room['keyPins'] + issues << "❌ INVALID: 'rooms/#{room_id}' has lockType: 'key' but missing required 'keyPins' array - key locks must specify keyPins array for lockpicking minigame" + end + end + end + + # Check NPCs in rooms + if room['npcs'] + room['npcs'].each_with_index do |npc, idx| + path = "rooms/#{room_id}/npcs[#{idx}]" + + # Track person NPCs + if npc['npcType'] == 'person' || (!npc['npcType'] && npc['position']) + has_person_npcs = true + + # Check for waypoints in behavior.patrol + if npc['behavior'] && npc['behavior']['patrol'] + patrol = npc['behavior']['patrol'] + # Check for single-room waypoints + if patrol['waypoints'] && !patrol['waypoints'].empty? + has_npc_with_waypoints = true + end + # Check for multi-room route waypoints + if patrol['route'] && patrol['route'].is_a?(Array) && patrol['route'].any? { |segment| segment['waypoints'] && !segment['waypoints'].empty? } + has_npc_with_waypoints = true + end + end + end + + # Check for opening cutscene in starting room + if room_id == start_room_id && npc['timedConversation'] + has_opening_cutscene = true + if npc['timedConversation']['delay'] != 0 + issues << "⚠ WARNING: '#{path}' timedConversation delay is #{npc['timedConversation']['delay']} - opening cutscenes typically use delay: 0" + end + end + + # Track phone NPCs (phone contacts) + if npc['npcType'] == 'phone' + has_phone_contacts = true + + # Validate phone NPC structure - should have phoneId + unless npc['phoneId'] + issues << "❌ INVALID: '#{path}' (phone NPC) missing required 'phoneId' field - phone NPCs must specify which phone they appear on (e.g., 'player_phone')" + end + + # Validate phone NPC structure - should have storyPath + unless npc['storyPath'] + issues << "❌ INVALID: '#{path}' (phone NPC) missing required 'storyPath' field - phone NPCs must have a path to their Ink story JSON file" + end + + # Validate phone NPC structure - should NOT have position (phone NPCs don't have positions) + if npc['position'] + issues << "⚠ WARNING: '#{path}' (phone NPC) has 'position' field - phone NPCs should NOT have position (they're not in-world sprites). Remove the position field." + end + + # Validate phone NPC structure - should NOT have spriteSheet (phone NPCs don't have sprites) + if npc['spriteSheet'] + issues << "⚠ WARNING: '#{path}' (phone NPC) has 'spriteSheet' field - phone NPCs should NOT have spriteSheet (they're not in-world sprites). Remove the spriteSheet field." + end + + # Track phone NPCs with messages in rooms + if npc['timedMessages'] && !npc['timedMessages'].empty? + has_phone_npc_with_messages = true + else + # Track phone NPCs without timed messages + phone_npcs_without_messages << "#{path} (#{npc['displayName'] || npc['id']})" + end + + # Track phone NPCs with event mappings in rooms + if npc['eventMappings'] && !npc['eventMappings'].empty? + has_phone_npc_with_events = true + end + end + + # Check for items with id field in NPC itemsHeld + if npc['itemsHeld'] + npc['itemsHeld'].each_with_index do |item, item_idx| + if item['id'] + issues << "❌ INVALID: '#{path}/itemsHeld[#{item_idx}]' has 'id' field - items should NOT have 'id' field. Use 'type' field to match #give_item tag parameter (e.g., type: 'id_badge' matches #give_item:id_badge)" + end + + # Track security tools in NPC itemsHeld + if ['fingerprint_kit', 'pin-cracker', 'bluetooth_scanner', 'rfid_cloner'].include?(item['type']) + has_security_tools = true + end + end + end + end + end + end + end + + # Check startItemsInInventory for security tools and readable items + if json_data['startItemsInInventory'] + json_data['startItemsInInventory'].each do |item| + # Track security tools + if ['fingerprint_kit', 'pin-cracker', 'bluetooth_scanner', 'rfid_cloner'].include?(item['type']) + has_security_tools = true + end + + # Track readable items + if item['readable'] || (item['type'] == 'notes' && item['text']) + has_readable_items = true + end + end + end + + # Check phoneNPCs section - this is the OLD/INCORRECT format + if json_data['phoneNPCs'] + json_data['phoneNPCs'].each_with_index do |npc, idx| + path = "phoneNPCs[#{idx}]" + + # Flag incorrect structure - phone NPCs should be in rooms, not phoneNPCs section + issues << "❌ INVALID: '#{path}' - Phone NPCs should be defined in 'rooms/{room_id}/npcs[]' arrays, NOT in a separate 'phoneNPCs' section. See scenarios/npc-sprite-test3/scenario.json.erb for correct format. Phone NPCs should be in the starting room (or room where phone is accessible) with npcType: 'phone'" + + # Track phone NPCs (phone contacts) - but note they're in wrong location + has_phone_contacts = true + + # Track phone NPCs with messages + if npc['timedMessages'] && !npc['timedMessages'].empty? + has_phone_npc_with_messages = true + else + # Track phone NPCs without timed messages + phone_npcs_without_messages << "#{path} (#{npc['displayName'] || npc['id']})" + end + + # Track phone NPCs with event mappings (for closing debriefs) + if npc['eventMappings'] && !npc['eventMappings'].any? { |m| m['eventPattern']&.include?('global_variable_changed') } + has_phone_npc_with_events = true + end + + # Check for closing debrief trigger + if npc['eventMappings'] + npc['eventMappings'].each do |mapping| + if mapping['eventPattern']&.include?('global_variable_changed') + has_closing_debrief = true + end + end + end + end + end + + # Feature suggestions + unless has_vm_launcher + issues << "💡 SUGGESTION: Consider adding VM launcher terminals (type: 'vm-launcher') - see scenarios/secgen_vm_lab/scenario.json.erb for example" + end + + unless has_flag_station + issues << "💡 SUGGESTION: Consider adding flag station terminals (type: 'flag-station') for VM flag submission - see scenarios/secgen_vm_lab/scenario.json.erb for example" + end + + unless has_pc_with_files + issues << "💡 SUGGESTION: Consider adding at least one PC container (type: 'pc') with files in 'contents' array and optional post-it notes - see scenarios/ceo_exfil/scenario.json.erb for example" + end + + unless has_phone_npc_with_messages || has_phone_npc_with_events + issues << "💡 SUGGESTION: Consider adding at least one phone NPC (in rooms or phoneNPCs section) with timedMessages or eventMappings - see scenarios/ceo_exfil/scenario.json.erb for example" + end + + unless has_opening_cutscene + issues << "💡 SUGGESTION: Consider adding opening briefing cutscene - NPC with timedConversation (delay: 0) in starting room - see scenarios/m01_first_contact/scenario.json.erb for example" + end + + unless has_closing_debrief + issues << "💡 SUGGESTION: Consider adding closing debrief trigger - phone NPC with eventMapping for global_variable_changed - see scenarios/m01_first_contact/scenario.json.erb for example" + end + + # Check for NPCs without waypoints + if has_person_npcs && !has_npc_with_waypoints + issues << "💡 SUGGESTION: Consider adding waypoints to at least one person NPC for more dynamic patrol behavior - see scenarios/test-npc-waypoints/scenario.json.erb for example. Add 'behavior.patrol.waypoints' array with {x, y} coordinates" + end + + # Check for phone contacts without timed messages + if has_phone_contacts && !phone_npcs_without_messages.empty? + npc_list = phone_npcs_without_messages.join(', ') + issues << "💡 SUGGESTION: Consider adding timedMessages to phone contacts for more engaging interactions - see scenarios/npc-sprite-test3/scenario.json.erb for example. Phone NPCs without timed messages: #{npc_list}" + end + + # Suggest variety in lock types + if lock_types_used.size < 2 + issues << "💡 SUGGESTION: Consider adding variety in lock types - scenarios typically use 2+ different lock mechanisms (key, pin, rfid, password). Currently using: #{lock_types_used.to_a.join(', ') || 'none'}. See scenarios/ceo_exfil/scenario.json.erb for examples" + end + + # Suggest RFID locks + unless has_rfid_lock + issues << "💡 SUGGESTION: Consider adding RFID locks for modern security scenarios - see scenarios/test-rfid/scenario.json.erb for examples" + end + + # Suggest PIN locks + unless has_pin_lock + issues << "💡 SUGGESTION: Consider adding PIN locks for numeric code challenges - see scenarios/ceo_exfil/scenario.json.erb for examples" + end + + # Suggest password locks + unless has_password_lock + issues << "💡 SUGGESTION: Consider adding password locks for computer/device access - see scenarios/ceo_exfil/scenario.json.erb for examples" + end + + # Suggest security tools + unless has_security_tools + issues << "💡 SUGGESTION: Consider adding security tools (fingerprint_kit, pin-cracker, bluetooth_scanner, rfid_cloner) for more interactive gameplay - see scenarios/ceo_exfil/scenario.json.erb for examples" + end + + # Suggest containers with contents + unless has_container_with_contents + issues << "💡 SUGGESTION: Consider adding containers (safes, suitcases) with contents for hidden items and rewards - see scenarios/ceo_exfil/scenario.json.erb for examples" + end + + # Suggest readable items + unless has_readable_items + issues << "💡 SUGGESTION: Consider adding readable items (notes, documents) for storytelling and clues - see scenarios/ceo_exfil/scenario.json.erb for examples" + end + + issues +end + # Check for recommended fields and return warnings def check_recommended_fields(json_data) warnings = [] @@ -130,27 +513,9 @@ def check_recommended_fields(json_data) path = "rooms/#{room_id}/objects[#{idx}]" warnings << "Missing recommended field: '#{path}/observations' - helps players understand what items are" unless obj.key?('observations') - # Check for locked objects without difficulty - if obj['locked'] && !obj['difficulty'] - warnings << "Missing recommended field: '#{path}/difficulty' - helps players gauge lock complexity" - end - - # Check for key locks without keyPins - if obj['lockType'] == 'key' && !obj['keyPins'] - warnings << "Missing recommended field: '#{path}/keyPins' - key locks should specify keyPins array for lockpicking minigame" - end - - # Check for key items without keyPins - if obj['type'] == 'key' && !obj['keyPins'] - warnings << "Missing recommended field: '#{path}/keyPins' - key items should specify keyPins array for lockpicking" - end end end - # Check locked rooms with key lockType without keyPins - if room['locked'] && room['lockType'] == 'key' && !room['keyPins'] - warnings << "Missing recommended field: 'rooms/#{room_id}/keyPins' - key locks should specify keyPins array for lockpicking minigame" - end # Check NPCs if room['npcs'] @@ -188,11 +553,6 @@ def check_recommended_fields(json_data) end end end - - # Check locked rooms without difficulty - if room['locked'] && !room['difficulty'] - warnings << "Missing recommended field: 'rooms/#{room_id}/difficulty' - helps players gauge lock complexity" - end end end @@ -292,6 +652,10 @@ def main puts "Validating against schema..." errors = validate_json(json_data, schema_path) + # Check for common issues and structural problems + puts "Checking for common issues..." + common_issues = check_common_issues(json_data) + # Check for recommended fields puts "Checking recommended fields..." warnings = check_recommended_fields(json_data) @@ -316,6 +680,20 @@ def main exit 1 end + # Report common issues + if common_issues.empty? + puts "✓ No common issues found." + puts + else + puts "⚠ Found #{common_issues.length} issue(s) and suggestion(s):" + puts + + common_issues.each_with_index do |issue, index| + puts "#{index + 1}. #{issue}" + end + puts + end + # Report warnings if warnings.empty? puts "✓ No missing recommended fields." diff --git a/story_design/SCENARIO_JSON_FORMAT_GUIDE.md b/story_design/SCENARIO_JSON_FORMAT_GUIDE.md index f4c9ce8..0791539 100644 --- a/story_design/SCENARIO_JSON_FORMAT_GUIDE.md +++ b/story_design/SCENARIO_JSON_FORMAT_GUIDE.md @@ -169,6 +169,39 @@ Great job! Tutorial complete. } ``` +**IMPORTANT: Valid Directions Only** + +Only **cardinal directions** are valid: +- ✅ `north`, `south`, `east`, `west` +- ❌ `northeast`, `northwest`, `southeast`, `southwest` (NOT VALID) + +**Bidirectional Connections Required:** + +If Room A connects north to Room B, then Room B MUST connect south back to Room A: + +```json +// Room A +"connections": { "north": "room_b" } + +// Room B (must connect back) +"connections": { "south": "room_a" } +``` + +**Common Mistake - Diagonal Directions:** +```json +// ❌ WRONG - diagonal directions not valid +"connections": { + "southeast": "break_room", // NOT VALID + "northwest": "storage_closet" // NOT VALID +} + +// ✅ CORRECT - use arrays for multiple rooms in same direction +"connections": { + "south": ["reception_area", "break_room"], + "north": ["derek_office", "storage_closet"] +} +``` + ### ❌ INCORRECT - Complex Array Format ```json @@ -301,6 +334,93 @@ Use `timedConversation` to auto-start dialogue when player enters room: - Cutscenes when entering specific rooms - Background can show different location (e.g., HQ for briefings) +### VM Launchers and Flag Stations + +**IMPORTANT:** For scenarios that integrate with SecGen VMs, use proper `vm-launcher` and `flag-station` types. + +**Reference Example:** `scenarios/secgen_vm_lab/scenario.json.erb` + +#### VM Launcher Configuration + +Use `type: "vm-launcher"` to create terminals that launch VMs: + +```json +{ + "type": "vm-launcher", + "id": "vm_launcher_intro_linux", + "name": "VM Access Terminal", + "takeable": false, + "observations": "Terminal providing access to compromised infrastructure", + "hacktivityMode": <%= vm_context && vm_context['hacktivity_mode'] ? 'true' : 'false' %>, + "vm": <%= vm_object('intro_to_linux_security_lab', { + "id": 1, + "title": "Target Server", + "ip": "192.168.100.50", + "enable_console": true + }) %> +} +``` + +**Key fields:** +- `type: "vm-launcher"` - Required object type +- `hacktivityMode` - ERB expression for Hacktivity integration +- `vm` - ERB helper `vm_object(vm_name, config)` specifies which VM to launch + +#### Flag Station Configuration + +Use `type: "flag-station"` for terminals that accept VM flags: + +```json +{ + "type": "flag-station", + "id": "flag_station_dropsite", + "name": "SAFETYNET Drop-Site Terminal", + "takeable": false, + "observations": "Secure terminal for submitting VM flags", + "acceptsVms": ["intro_to_linux_security_lab"], + "flags": <%= flags_for_vm('intro_to_linux_security_lab', [ + 'flag{ssh_brute_force_success}', + 'flag{linux_navigation_complete}', + 'flag{privilege_escalation_success}' + ]) %>, + "flagRewards": [ + { + "type": "emit_event", + "event_name": "ssh_flag_submitted", + "description": "SSH access flag submitted" + }, + { + "type": "give_item", + "item_name": "Server Access Card", + "description": "Unlocked new access" + }, + { + "type": "unlock_door", + "target_room": "secure_area", + "description": "Door unlocked" + } + ] +} +``` + +**Key fields:** +- `type: "flag-station"` - Required object type +- `acceptsVms` - Array of VM names this station accepts flags from +- `flags` - ERB helper `flags_for_vm(vm_name, flag_array)` configures accepted flags +- `flagRewards` - Array of rewards given for each flag (in order) + +**Reward types:** +- `emit_event` - Triggers game event (can trigger Ink via phone NPC event mappings) +- `give_item` - Adds item to player inventory +- `unlock_door` - Unlocks a specific room +- `reveal_secret` - Shows hidden information + +**Common Mistakes:** +- ❌ Using `type: "pc"` with `vmAccess: true` - use `type: "vm-launcher"` instead +- ❌ Creating Ink dialogue for flag submission - use `type: "flag-station"` instead +- ❌ Hardcoding flags without ERB helpers - use `flags_for_vm()` helper +- ❌ Forgetting `acceptsVms` array - station won't accept any flags + ### ❌ INCORRECT - Top-Level NPCs ```json diff --git a/story_design/story_dev_prompts/05_room_layout_design.md b/story_design/story_dev_prompts/05_room_layout_design.md index 12a0ece..e4ba9fd 100644 --- a/story_design/story_dev_prompts/05_room_layout_design.md +++ b/story_design/story_dev_prompts/05_room_layout_design.md @@ -404,6 +404,118 @@ Map out how rooms unlock over time as player completes objectives: - Contains: Final evidence needed for confrontation ``` +--- + +## ⚠️ CRITICAL: Lock Type Variety and Progression + +**Problem:** Using the same lock type (e.g., all key locks) makes gameplay repetitive and boring. + +**Solution:** Mix lock types and order them strategically. + +### Lock Type Ordering Rules + +**RULE 1: Keys BEFORE Lockpick** + +Once players obtain a lockpick, they can bypass all key-based locks. Therefore: +- ✅ Use **key-based locks for critical path progression BEFORE** lockpick is obtained +- ✅ Place lockpick as reward AFTER key-based puzzle chain +- ❌ DON'T give lockpick early then expect keys to matter + +**Example (Good):** +1. Storage safe (PIN 1337) → Derek's office key +2. Derek's office (key) → access Derek's office +3. Derek's filing cabinet (PIN 0419) → evidence +4. Talk to Kevin after gathering evidence → get lockpick +5. Now lockpick bypasses future key locks (but already used keys) + +**Example (Bad):** +1. Talk to Kevin → get lockpick immediately ❌ +2. Storage closet (key) → player ignores, uses lockpick instead ❌ +3. Keys become useless, puzzle bypassed ❌ + +**RULE 2: Vary Lock Types** + +Mix different lock mechanisms for engagement: +- 🔓 **Lockpick** - Physical skill, tutorial early +- 🔢 **PIN codes** - Discover hints, decode messages, read notes +- 🔑 **Keys** - Find in containers, other rooms (NOT same room as lock!) +- 📱 **RFID/Keycards** - Clone from NPCs, social engineering +- 🔐 **Passwords** - Gather from notes, password hints from NPCs + +**Aim for 3+ different lock types per scenario.** + +**RULE 3: Keys Not In Same Room As Lock** + +Keys should require problem-solving: +- ✅ Key in safe in different room (requires PIN/lockpick to access) +- ✅ Key held by NPC (requires social engineering) +- ✅ Key in container that requires different puzzle +- ❌ Key sitting on desk next to locked door + +**RULE 4: Progressive Difficulty** + +Order puzzles from easy to hard: +1. **Easy:** Hint nearby (sticky note with PIN next to safe) +2. **Medium:** Hint in different room (maintenance checklist mentions storage safe PIN) +3. **Hard:** Multi-step (decode Base64 message → discover PIN for safe) +4. **Expert:** Chain multiple systems (VM challenge → flag → hint → decode → PIN) + +### Lock Progression Template + +```markdown +## Lock Variety Analysis + +**Lock Types Used:** +- [ ] Lockpick (physical) +- [ ] PIN codes (cognitive) +- [ ] Keys (exploration) +- [ ] RFID/Keycards (social) +- [ ] Passwords (investigation) + +**Lock Progression Order:** + +1. **[Lock Name]** (Type: PIN) + - Location: Main office filing cabinet + - Unlock Method: Sticky note with hint nearby + - Difficulty: Easy + - Rewards: LORE fragment + - Blocks Critical Path: No + +2. **[Lock Name]** (Type: PIN) + - Location: Storage safe + - Unlock Method: Maintenance checklist in main office + - Difficulty: Medium + - Rewards: Derek's office key + - Blocks Critical Path: Yes + +3. **[Lock Name]** (Type: Key) + - Location: Derek's office door + - Unlock Method: Key from storage safe + - Difficulty: Easy (have key) + - Rewards: Access to Derek's office + - Blocks Critical Path: Yes + - **Used BEFORE lockpick obtained** ✅ + +4. **[Lockpick Obtained]** + - Source: Kevin (after influence >= 8) + - Now bypasses future key locks + +**Critical Path Locks:** 2 → 3 → (other progression) +**Optional Locks:** 1 (provides LORE but not blocking) +``` + +### Validation Checklist + +- [ ] At least 3 different lock types used +- [ ] Keys used BEFORE lockpick is obtainable +- [ ] Keys are NOT in same room as their locks +- [ ] PIN codes have discoverable hints +- [ ] Locks ordered easy → medium → hard +- [ ] Lockpick comes AFTER key-based progression +- [ ] No "same-y" gameplay (all locks using one method) + +--- + ### Step 6: Design Backtracking Moments Identify required backtracking (non-linear exploration): diff --git a/story_design/story_dev_prompts/07_ink_scripting.md b/story_design/story_dev_prompts/07_ink_scripting.md index 7667d55..44e6f27 100644 --- a/story_design/story_dev_prompts/07_ink_scripting.md +++ b/story_design/story_dev_prompts/07_ink_scripting.md @@ -16,6 +16,56 @@ You are an Ink narrative scripter for Break Escape. Your tasks: 4. Integrate narrative with game systems 5. Ensure all Ink is technically correct and testable +--- + +## ⚠️ CRITICAL: Dialogue Pacing Rule + +**Keep dialogue snappy and interactive!** + +**THE RULE: Maximum 3 lines of dialogue from a single character before presenting player choices** + +```ink +// ❌ BAD - Too much monologue +=== bad_example === +Sarah: Hi! You must be the IT contractor. I'm Sarah, the receptionist. +Sarah: Let me get you checked in. +Sarah: We've been having some network issues lately. +Sarah: The IT manager will want to talk to you about that. +Sarah: His office is down the hall on the left. +-> hub + +// ✅ GOOD - Snappy with player engagement +=== good_example === +Sarah: Hi! You must be the IT contractor. I'm Sarah. +Sarah: Let me get you checked in. + ++ [Thanks. I'm here to audit your network security] + Sarah: Oh good! Kevin mentioned you'd be coming. + -> receive_badge ++ [Just point me to IT and I'll get started] + Sarah: Sure thing. Let me get your badge first. + -> receive_badge +``` + +**Why this matters:** +- Keeps players engaged and active +- Prevents dialogue fatigue +- Maintains pacing and momentum +- Makes conversations feel interactive, not like reading a script + +**Exceptions:** +- Opening/closing cutscenes may have slightly longer monologues (max 5 lines) +- Dramatic reveals or critical story moments (max 4 lines) +- Even in exceptions, break up with internal choices or "press to continue" moments + +**Best practices:** +- 1-2 lines is ideal for most dialogue +- 3 lines is the maximum before requiring a choice +- Use choices to create rhythm and player agency +- NPCs should respond to player choices, not just talk at them + +--- + ## Required Input From previous stages: diff --git a/story_design/story_dev_prompts/09_scenario_assembly.md b/story_design/story_dev_prompts/09_scenario_assembly.md index 2151d50..1bc7f94 100644 --- a/story_design/story_dev_prompts/09_scenario_assembly.md +++ b/story_design/story_dev_prompts/09_scenario_assembly.md @@ -82,6 +82,24 @@ From all previous stages: - These should have `#exit_conversation` tag before END - Regular NPC dialogue should return to hub instead of using END +3. **Validate scenario structure** - Run the validation script: + ```bash + ruby scripts/validate_scenario.rb scenarios/[scenario_name]/scenario.json.erb + ``` + + This will: + - Render and validate the ERB template + - Check against the scenario schema + - Identify common structural issues + - Provide suggestions for improvements + + **Fix all INVALID errors before proceeding!** Suggestions are optional but recommended. + + **Common validation errors to fix:** + - Phone NPCs in separate `phoneNPCs` section (should be in room `npcs` arrays) + - Missing `keyPins` arrays for key locks (needed for lockpicking minigame) + - Invalid room connection directions (only north/south/east/west valid) + ## Understanding scenario.json.erb ### What is ERB?