๐ŸŽฎArcadeLab

NETRUNNER: Breach Protocol

by StormPenguin58
629 lines30.8 KB๐Ÿ› ๏ธ Phaser (2D game framework)
โ–ถ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>NETRUNNER: Breach Protocol</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#000c14;overflow:hidden;font-family:'Courier New',monospace;color:#c0d8e8;user-select:none}
#phaser-bg{position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;pointer-events:none}
#ui{position:fixed;top:0;left:0;width:100%;height:100%;z-index:1;display:flex;flex-direction:column}

/* โ”€โ”€ TOOLBAR โ”€โ”€ */
#toolbar{height:36px;background:#040f1a;border-bottom:1px solid #0a3050;display:flex;align-items:center;padding:0 12px;gap:16px;flex-shrink:0}
.tb-logo{color:#00b4d8;font-size:13px;font-weight:bold;letter-spacing:2px}
.tb-sep{color:#0a3050;font-size:16px}
.tb-corp{color:#ff4d6d;font-size:11px;letter-spacing:1px}
.tb-spacer{flex:1}
#tb-status{font-size:10px;color:#4a8fa8;letter-spacing:1px}
#tb-time{font-size:11px;color:#ffee00;letter-spacing:2px;min-width:60px;text-align:right}

/* โ”€โ”€ MAIN AREA โ”€โ”€ */
#main{flex:1;display:flex;overflow:hidden;min-height:0}

/* โ”€โ”€ LEFT PANEL (Target Tree) โ”€โ”€ */
#left-panel{width:190px;flex-shrink:0;background:#020d18;border-right:1px solid #0a3050;display:flex;flex-direction:column;overflow:hidden}
.panel-header{padding:6px 10px;font-size:10px;color:#2a6a8a;border-bottom:1px solid #0a3050;letter-spacing:2px;text-transform:uppercase;background:#030e1a;flex-shrink:0}
#target-tree{flex:1;overflow-y:auto;padding:4px 0}
#target-tree::-webkit-scrollbar{width:4px}
#target-tree::-webkit-scrollbar-track{background:#020d18}
#target-tree::-webkit-scrollbar-thumb{background:#0a3050}
.tree-item{padding:5px 8px 5px 14px;font-size:10px;color:#2a5a6a;cursor:pointer;border-left:2px solid transparent;line-height:1.4;transition:all .15s}
.tree-item.done{color:#1a6a3a;border-left-color:#00664a}
.tree-item.done::before{content:'โœ“ '}
.tree-item.active{color:#00b4d8;border-left-color:#00b4d8;background:#041525}
.tree-item.active::before{content:'โ–ถ '}
.tree-item.locked{color:#0d2535}
.tree-net{font-size:9px;color:#0a3a4a;padding-left:4px;margin-top:1px}
.tree-item.done .tree-net{color:#0a4a2a}
.tree-item.active .tree-net{color:#006688}

/* โ”€โ”€ CENTER PANEL โ”€โ”€ */
#center-panel{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}

/* phase briefing */
#phase-brief{padding:10px 14px;background:#030f1c;border-bottom:1px solid #0a3050;flex-shrink:0}
#phase-atk{font-size:11px;color:#2a6a8a;letter-spacing:3px;text-transform:uppercase;margin-bottom:2px}
#phase-net{font-size:16px;color:#00b4d8;margin-bottom:4px}
#phase-desc{font-size:11px;color:#4a7a8a;line-height:1.5}

/* technique area */
#technique-area{flex:1;overflow-y:auto;padding:10px 14px;display:flex;flex-direction:column;gap:8px}
#technique-area::-webkit-scrollbar{width:4px}
#technique-area::-webkit-scrollbar-thumb{background:#0a3050}
.tech-header{font-size:10px;color:#2a5a6a;letter-spacing:2px;text-transform:uppercase;margin-bottom:4px}

/* technique cards */
.tech-card{background:#041525;border:1px solid #0a3050;border-radius:3px;padding:10px 12px;cursor:pointer;transition:all .15s;position:relative;overflow:hidden}
.tech-card:hover{background:#061d32;border-color:#00b4d8}
.tech-card.oos{border-color:#3a0a0a;background:#0a0505}
.tech-card.oos:hover{border-color:#ff2200}
.tech-card.disabled{opacity:.4;cursor:not-allowed;pointer-events:none}
.tech-card-name{font-size:12px;color:#c0d8e8;margin-bottom:3px;display:flex;align-items:center;gap:8px}
.tech-card-name .key-hint{font-size:9px;background:#0a3050;color:#4a8fa8;padding:1px 5px;border-radius:2px}
.tech-card-name .oos-badge{font-size:9px;background:#3a0a0a;color:#ff4444;padding:1px 5px;border-radius:2px}
.tech-card-desc{font-size:10px;color:#3a6a7a;line-height:1.5;margin-bottom:6px}
.tech-card-stats{display:flex;gap:12px;font-size:9px}
.stat-suc{color:#00b44a}
.stat-tr{color:#ff6680}
.stat-ram{color:#00b4d8}
.tech-card.quiet .stat-tr{color:#8888ff}

/* result banner */
#result-banner{display:none;padding:10px 14px;border-top:1px solid #0a3050;background:#030f1c;flex-shrink:0}
#result-text{font-size:12px;line-height:1.6;margin-bottom:8px}
#proceed-btn{display:none;padding:8px 20px;background:#00334a;border:1px solid #00b4d8;color:#00b4d8;font-family:inherit;font-size:12px;cursor:pointer;letter-spacing:2px;border-radius:2px}
#proceed-btn:hover{background:#00b4d8;color:#000c14}

/* โ”€โ”€ RIGHT PANEL (Event Log) โ”€โ”€ */
#right-panel{width:220px;flex-shrink:0;background:#020d18;border-left:1px solid #0a3050;display:flex;flex-direction:column;overflow:hidden}
#event-log{flex:1;overflow-y:auto;padding:4px 0;font-size:10px}
#event-log::-webkit-scrollbar{width:4px}
#event-log::-webkit-scrollbar-thumb{background:#0a3050}
.log-entry{padding:3px 8px;line-height:1.5;border-bottom:1px solid #030f1a}
.log-ts{color:#1a4a5a;margin-right:4px}
.log-ok{color:#00884a}
.log-fail{color:#884400}
.log-warn{color:#ff4d6d}
.log-info{color:#2a6a8a}
.log-sys{color:#006688}

/* โ”€โ”€ BOTTOM BAR โ”€โ”€ */
#bottom-bar{height:44px;background:#040f1a;border-top:1px solid #0a3050;display:flex;align-items:center;padding:0 12px;gap:16px;flex-shrink:0}
.meter-wrap{display:flex;align-items:center;gap:6px}
.meter-label{font-size:9px;color:#2a6a8a;letter-spacing:1px;width:38px;text-align:right}
.meter-bar{width:90px;height:8px;background:#041525;border:1px solid #0a3050;border-radius:2px;overflow:hidden;position:relative}
.meter-fill{height:100%;border-radius:1px;transition:width .3s}
.fill-ram{background:#00b4d8}
.fill-time{background:#ffee00}
.fill-trace{background:#ff4d6d}
.meter-val{font-size:9px;color:#4a8fa8;min-width:30px}
#disc-label{font-size:10px;color:#00884a;flex:1;text-align:right}

/* โ”€โ”€ GAME OVER / WIN OVERLAY โ”€โ”€ */
#overlay{display:none;position:fixed;top:0;left:0;width:100%;height:100%;z-index:50;background:rgba(0,0,0,.92);flex-direction:column;align-items:center;justify-content:center;gap:0}
#overlay.show{display:flex}
#overlay-inner{background:#030f1c;border:1px solid #0a3050;border-radius:4px;padding:28px 32px;max-width:680px;width:90%;max-height:90vh;overflow-y:auto}
#overlay-inner::-webkit-scrollbar{width:4px}
#overlay-inner::-webkit-scrollbar-thumb{background:#0a3050}
.ov-title{font-size:20px;margin-bottom:4px}
.ov-sub{font-size:11px;color:#4a7a8a;letter-spacing:2px;margin-bottom:18px}
.ov-section{margin-bottom:14px}
.ov-section h3{font-size:10px;color:#ffee00;letter-spacing:2px;margin-bottom:6px;border-bottom:1px solid #0a3050;padding-bottom:3px}
.score-row{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:11px}
.score-row .sc-label{color:#4a8fa8;width:160px}
.score-bar{flex:1;height:8px;background:#041525;border:1px solid #0a3050;border-radius:2px;overflow:hidden}
.score-fill{height:100%;background:#00b4d8;border-radius:1px}
.score-row .sc-val{width:40px;text-align:right;color:#c0d8e8}
.narrative-row{font-size:10px;padding:2px 0;color:#3a6a7a}
.narrative-row.ok{color:#006644}
.narrative-row.fail{color:#664400}
.gap-row{font-size:10px;color:#ff8800;padding:2px 0}
.ov-btns{display:flex;gap:10px;margin-top:16px}
.ov-btn{padding:9px 22px;border:1px solid #00b4d8;background:#00334a;color:#00b4d8;font-family:inherit;font-size:12px;cursor:pointer;letter-spacing:2px;border-radius:2px;flex:1}
.ov-btn:hover{background:#00b4d8;color:#000c14}
.ov-btn.red{border-color:#ff4d6d;background:#2a0a14;color:#ff4d6d}
.ov-btn.red:hover{background:#ff4d6d;color:#000c14}

/* responsive */
@media(max-width:640px){
  #left-panel{width:140px}
  #right-panel{display:none}
  .meter-bar{width:55px}
}
@media(max-width:480px){
  #left-panel{display:none}
  #right-panel{display:none}
}
</style>
</head>
<body>

<!-- Phaser canvas for animated BG -->
<canvas id="phaser-bg"></canvas>

<!-- UI Overlay -->
<div id="ui">
  <!-- toolbar -->
  <div id="toolbar">
    <span class="tb-logo">NETRUNNER</span>
    <span class="tb-sep">|</span>
    <span class="tb-corp">TARGET: HELIX DYNAMICS</span>
    <span class="tb-sep">|</span>
    <span id="tb-status">CONNECTED ยท AUTHORIZED ASSESSMENT</span>
    <span class="tb-spacer"></span>
    <span id="tb-time">T-20</span>
  </div>

  <div id="main">
    <!-- left: target tree -->
    <div id="left-panel">
      <div class="panel-header">Kill Chain</div>
      <div id="target-tree"></div>
    </div>

    <!-- center: main console -->
    <div id="center-panel">
      <div id="phase-brief">
        <div id="phase-atk"></div>
        <div id="phase-net"></div>
        <div id="phase-desc"></div>
      </div>
      <div id="technique-area">
        <div class="tech-header">Available Techniques</div>
      </div>
      <div id="result-banner">
        <div id="result-text"></div>
        <button id="proceed-btn">NEXT PHASE โ–ถ</button>
      </div>
    </div>

    <!-- right: event log -->
    <div id="right-panel">
      <div class="panel-header">Operator Log</div>
      <div id="event-log"></div>
    </div>
  </div>

  <!-- bottom meters -->
  <div id="bottom-bar">
    <div class="meter-wrap">
      <span class="meter-label">RAM</span>
      <div class="meter-bar"><div class="meter-fill fill-ram" id="fill-ram" style="width:100%"></div></div>
      <span class="meter-val" id="val-ram">8/8</span>
    </div>
    <div class="meter-wrap">
      <span class="meter-label">TIME</span>
      <div class="meter-bar"><div class="meter-fill fill-time" id="fill-time" style="width:100%"></div></div>
      <span class="meter-val" id="val-time">20</span>
    </div>
    <div class="meter-wrap">
      <span class="meter-label">TRACE</span>
      <div class="meter-bar"><div class="meter-fill fill-trace" id="fill-trace" style="width:0%"></div></div>
      <span class="meter-val" id="val-trace">0%</span>
    </div>
    <span id="disc-label">DISCIPLINE: 0 pts</span>
  </div>
</div>

<!-- End overlay -->
<div id="overlay">
  <div id="overlay-inner">
    <div id="ov-content"></div>
  </div>
</div>

<script>
// โ”€โ”€ DATA โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const PHASES=[
  {id:'recon',atk:'Reconnaissance',net:'Scan the Subnet',
   desc:'Map the target attack surface. Passive recon leaves almost no trace; active scanning risks detection.',
   cards:[
    {name:'OSINT Sweep',desc:'Mine public data: Shodan, LinkedIn, job postings, leaked docs. Zero direct contact with target systems.',suc:92,tr:3,ram:1},
    {name:'Active Port Scan',desc:'Probe live hosts with Nmap-style enumeration. Fast but noisy โ€” corp IDS may flag it.',suc:88,tr:18,ram:2},
    {name:'Subdomain Harvest',desc:'Enumerate DNS subdomains via brute-force wordlist. Reveals forgotten shadow-IT assets.',suc:85,tr:8,ram:1},
  ]},
  {id:'init_access',atk:'Initial Access',net:'Crack the Front Door',
   desc:'Establish your first foothold. The hardest gate โ€” get this wrong and ICE fires before you are in.',
   cards:[
    {name:'Spear Phishing',desc:'Craft a convincing lure targeting a named employee found during recon. Bypasses technical controls via the human layer.',suc:72,tr:12,ram:2},
    {name:'Public-Facing Exploit',desc:'Fire a known CVE at the externally exposed web server. Loud but fast if the target is unpatched.',suc:65,tr:28,ram:3},
    {name:'Valid Accounts',desc:'Log in using credentials found in a third-party breach dump. Looks like a legitimate user to most monitoring.',suc:82,tr:8,ram:2},
  ]},
  {id:'exec',atk:'Execution',net:'Run the Payload',
   desc:'Trigger your implant on the compromised host. Fileless techniques live only in memory, harder for AV to catch.',
   cards:[
    {name:'PowerShell Cradle',desc:'Download and execute shellcode entirely in-memory. Bypasses on-disk AV but trips script-block logging.',suc:75,tr:22,ram:2},
    {name:'WMI Invoke',desc:'Use Windows Management Instrumentation โ€” a legitimate admin channel โ€” to spawn the implant process.',suc:78,tr:14,ram:2},
    {name:'Scheduled Task',desc:'Register an auto-run task that fires the payload on next login. Survives reboots and looks like legit maintenance.',suc:85,tr:10,ram:1},
    {name:'BRICK THE MAINFRAME',desc:'Trigger destructive wiper payload across all hosts. This is sabotage, not penetration testing.',suc:0,tr:100,ram:0,oos:true},
  ]},
  {id:'persist',atk:'Persistence',net:'Anchor the Ghost',
   desc:'Guarantee re-entry if your session drops. Without persistence, any reboot kills your foothold.',
   cards:[
    {name:'Registry Run Key',desc:'Write your implant path into HKCU\\Run. Executes automatically each time the user logs in.',suc:82,tr:14,ram:2},
    {name:'Backdoor Account',desc:'Create a secondary local admin with an innocuous-sounding name blended into existing accounts.',suc:76,tr:22,ram:2},
    {name:'Browser Extension',desc:'Drop a malicious extension into the user Chrome profile. Persists across reboots, rarely flagged.',suc:88,tr:8,ram:1},
  ]},
  {id:'privesc',atk:'Privilege Escalation',net:'Ascend the Stack',
   desc:'Elevate from standard user to SYSTEM or root. Domain Admin unlocks the entire corp network.',
   cards:[
    {name:'Token Impersonation',desc:'Abuse SeImpersonatePrivilege to steal a SYSTEM-level token from a running service.',suc:72,tr:18,ram:3},
    {name:'SUID Abuse',desc:'Find a Linux binary with the SUID bit set and exploit it to run commands as root.',suc:78,tr:12,ram:2},
    {name:'UAC Bypass',desc:'Trigger a COM auto-elevation gadget to silently obtain admin rights without a UAC prompt.',suc:68,tr:26,ram:3},
  ]},
  {id:'defevasion',atk:'Defense Evasion',net:'Ghost the Logs',
   desc:"Blind the corp's security stack. The quieter you are, the less the blue team knows to look for.",
   cards:[
    {name:'Event Log Clear',desc:'Selectively wipe Windows Security and System event logs covering your intrusion window.',suc:80,tr:16,ram:2},
    {name:'Process Hollowing',desc:'Inject your payload into a legitimate svchost.exe process so it hides in plain sight.',suc:74,tr:10,ram:3},
    {name:'AMSI Patch',desc:'Patch the Antimalware Scan Interface in-memory so PowerShell scripts bypass AV scanning.',suc:76,tr:12,ram:2},
    {name:'WIPE PRODUCTION DATA',desc:'Destroy the corp database layer. Catastrophic collateral damage โ€” massively out of scope.',suc:0,tr:100,ram:0,oos:true},
  ]},
  {id:'credaccess',atk:'Credential Access',net:'Harvest the Keys',
   desc:'Credentials are the master key. NTLM hashes let you move laterally without cracking anything.',
   cards:[
    {name:'LSASS Memory Dump',desc:'Dump the lsass.exe process to pull NTLM hashes and Kerberos tickets directly from RAM.',suc:72,tr:28,ram:3},
    {name:'Kerberoasting',desc:'Request TGS service tickets for SPNs and crack them offline โ€” no special privileges needed.',suc:82,tr:12,ram:2},
    {name:'Credential File Hunt',desc:'Parse browser saved passwords, SSH private keys, .env files, and config files with embedded secrets.',suc:88,tr:8,ram:1},
  ]},
  {id:'discovery',atk:'Discovery',net:'Map the Mainframe',
   desc:'Understand the internal network topology before pivoting. Know where the crown jewels are stored.',
   cards:[
    {name:'AD Enumeration',desc:'BloodHound-style query of Active Directory โ€” map all admin groups, trust paths, and attack paths.',suc:86,tr:14,ram:2},
    {name:'Internal Network Scan',desc:'ARP sweep and SMB probe of internal /16 subnets. Reveals live hosts and open shares.',suc:82,tr:22,ram:2},
    {name:'File Share Grep',desc:'Recursively search mounted shares for filenames containing "password", "key", "secret", "backup".',suc:90,tr:8,ram:1},
  ]},
  {id:'lateral',atk:'Lateral Movement',net:'Hop the Nodes',
   desc:'Pivot from your beachhead to servers holding the data payload. Each hop risks a new detection event.',
   cards:[
    {name:'Pass-the-Hash',desc:'Authenticate to remote hosts using the NTLM hash directly โ€” no need to know the plaintext password.',suc:76,tr:18,ram:2},
    {name:'RDP Pivot',desc:'Open a Remote Desktop session into an admin workstation using cracked or stolen credentials.',suc:80,tr:26,ram:2},
    {name:'WinRM Lateral',desc:'Use PowerShell Remoting over WinRM โ€” blends in with legitimate sysadmin activity.',suc:74,tr:12,ram:3},
  ]},
  {id:'collection',atk:'Collection',net:'Scrape the Vault',
   desc:'Stage the target data before exfil. Compress and encrypt to reduce transfer time and signatures.',
   cards:[
    {name:'Email Archive Grab',desc:'Export PST files from Exchange for the CFO and legal team โ€” high-value contractual data.',suc:82,tr:18,ram:2},
    {name:'SharePoint Scrape',desc:'Recursively download the internal wiki, design specs, and financial forecasting docs.',suc:86,tr:14,ram:2},
    {name:'DB Table Dump',desc:'SELECT and export the CRM database table โ€” customer PII and contract metadata.',suc:74,tr:24,ram:3},
  ]},
  {id:'c2',atk:'Command & Control',net:'Open the Uplink',
   desc:'Establish your encrypted exfil channel. Blend with legitimate HTTPS traffic to avoid DPI inspection.',
   cards:[
    {name:'DNS Tunneling',desc:'Encode exfil data in DNS TXT record queries to a domain you control. Extremely stealthy.',suc:82,tr:8,ram:2},
    {name:'HTTPS Beacon',desc:'Beacon home over port 443 with randomized jitter, mimicking real browser TLS sessions.',suc:86,tr:14,ram:2},
    {name:'Domain Fronting',desc:'Route C2 traffic through a major CDN, so the destination IP appears as a legitimate cloud host.',suc:78,tr:5,ram:3},
  ]},
  {id:'exfil',atk:'Exfiltration',net:'Punch the Payload Out',
   desc:'Transfer the data package out of the target network. This is the finish line โ€” do not spike the trace now.',
   cards:[
    {name:'Encrypted Drip',desc:'AES-encrypt the payload archive and trickle it out in small chunks over the C2 beacon.',suc:82,tr:14,ram:3},
    {name:'Cloud Drop',desc:'Push to an attacker-controlled S3 bucket via standard HTTPS PUT โ€” indistinguishable from backup traffic.',suc:86,tr:18,ram:2},
    {name:'Steganographic Exfil',desc:'Encode the payload inside benign-looking PNG images uploaded to a mock social media API.',suc:76,tr:5,ram:2},
  ]},
];

// โ”€โ”€ GAME STATE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const S={
  pIdx:0,ram:8,maxRam:8,timeLeft:20,maxTime:20,
  trace:0,maxTrace:100,discipline:0,
  used:[],state:'pick',// pick|resolved|over
  logCount:0,
};

// โ”€โ”€ PHASER BACKGROUND โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class BGScene extends Phaser.Scene{
  constructor(){super('BG');}
  create(){
    this.W=this.scale.width;this.H=this.scale.height;
    this._particles=[];
    this._t=0;
    this.scale.on('resize',(s)=>{this.W=s.width;this.H=s.height;});
  }
  update(t,d){
    this._t+=d;
    // spawn trace particles when trace is high
    if(S.trace>30&&Math.random()<S.trace/2000){
      this._particles.push({
        x:Math.random()*this.W,y:Math.random()*this.H,
        vx:(Math.random()-0.5)*0.5,vy:-0.3-Math.random()*0.8,
        life:1,col:S.trace>70?0xff2200:0xff00aa,size:2+Math.random()*2
      });
    }
    const g=this.add.graphics().setDepth(0);
    g.clear();
    // grid
    g.lineStyle(1,0x001a2a,0.22);
    const gs=36;
    for(let x=0;x<this.W;x+=gs)g.lineBetween(x,0,x,this.H);
    for(let y=0;y<this.H;y+=gs)g.lineBetween(0,y,this.W,y);
    // particles
    this._particles=this._particles.filter(p=>{
      p.x+=p.vx;p.y+=p.vy;p.life-=0.015;
      if(p.life<=0)return false;
      g.fillStyle(p.col,p.life*0.6);
      g.fillRect(p.x,p.y,p.size,p.size*0.5);
      return true;
    });
    // scanlines
    g.lineStyle(1,0x000000,0.12);
    for(let y=0;y<this.H;y+=3)g.lineBetween(0,y,this.W,y);
    this.time.delayedCall(16,()=>{try{g.destroy();}catch(e){}});
  }
}

new Phaser.Game({
  type:Phaser.CANVAS,canvas:document.getElementById('phaser-bg'),
  backgroundColor:'#000c14',
  scale:{mode:Phaser.Scale.RESIZE,autoCenter:Phaser.Scale.CENTER_BOTH},
  scene:[BGScene],transparent:false,
});

// โ”€โ”€ DOM HELPERS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function $(id){return document.getElementById(id);}

function log(msg,cls){
  const el=$('event-log');
  const d=document.createElement('div');
  d.className='log-entry';
  const ts=new Date();
  const stamp=`${String(ts.getHours()).padStart(2,'0')}:${String(ts.getMinutes()).padStart(2,'0')}:${String(ts.getSeconds()).padStart(2,'0')}`;
  d.innerHTML=`<span class="log-ts">${stamp}</span><span class="${cls||'log-info'}">${msg}</span>`;
  el.appendChild(d);
  el.scrollTop=el.scrollHeight;
}

function updateMeters(){
  $('fill-ram').style.width=(S.ram/S.maxRam*100)+'%';
  $('val-ram').textContent=S.ram+'/'+S.maxRam;
  $('fill-time').style.width=(S.timeLeft/S.maxTime*100)+'%';
  $('val-time').textContent=S.timeLeft;
  $('fill-trace').style.width=(S.trace/S.maxTrace*100)+'%';
  $('fill-trace').style.background=S.trace>70?'#ff2200':S.trace>40?'#ff8800':'#ff4d6d';
  $('val-trace').textContent=S.trace+'%';
  $('disc-label').textContent='DISCIPLINE: '+S.discipline+' pts  |  [R] abort';
  $('tb-time').textContent='T-'+S.timeLeft;
}

function buildTargetTree(){
  const tree=$('target-tree');
  tree.innerHTML='';
  PHASES.forEach((ph,i)=>{
    const div=document.createElement('div');
    const done=i<S.pIdx,cur=i===S.pIdx;
    div.className='tree-item '+(done?'done':cur?'active':'locked');
    div.innerHTML=`<div>${ph.atk}</div><div class="tree-net">${ph.net}</div>`;
    tree.appendChild(div);
  });
  // scroll active into view
  const active=tree.querySelector('.active');
  if(active)active.scrollIntoView({block:'nearest'});
}

function showPhase(){
  const ph=PHASES[S.pIdx];
  $('phase-atk').textContent='Phase '+(S.pIdx+1)+'/'+PHASES.length+' ยท '+ph.atk;
  $('phase-net').textContent='// '+ph.net;
  $('phase-desc').textContent=ph.desc;
  $('result-banner').style.display='none';
  $('proceed-btn').style.display='none';
  buildCards();
  buildTargetTree();
  log('[phase] Entering: '+ph.atk,'log-sys');
  S.state='pick';
}

function buildCards(){
  const ph=PHASES[S.pIdx];
  const area=$('technique-area');
  area.innerHTML='<div class="tech-header">Available Techniques โ€” select to execute:</div>';
  ph.cards.forEach((c,i)=>{
    const div=document.createElement('div');
    const oos=c.oos===true;
    div.className='tech-card'+(oos?' oos':'')+(c.tr<12?' quiet':'');
    const keyHint=oos?'':`<span class="key-hint">${i+1}</span>`;
    const oosBadge=oos?'<span class="oos-badge">OUT OF SCOPE</span>':'';
    div.innerHTML=`
      <div class="tech-card-name">${keyHint}<strong>${c.name}</strong>${oosBadge}</div>
      <div class="tech-card-desc">${c.desc}</div>
      ${oos?'<div style="font-size:10px;color:#ff2200">โš  Professional operators NEVER exceed authorized scope. Do not slot.</div>':`
      <div class="tech-card-stats">
        <span class="stat-suc">SUCCESS ${c.suc}%</span>
        <span class="stat-tr">TRACE +${c.tr}</span>
        <span class="stat-ram">RAM ${c.ram}</span>
      </div>`}
    `;
    div.addEventListener('click',()=>pickTech(i));
    area.appendChild(div);
  });
}

function disableCards(){
  document.querySelectorAll('.tech-card').forEach(c=>c.classList.add('disabled'));
}

function pickTech(idx){
  if(S.state!=='pick')return;
  const ph=PHASES[S.pIdx];
  const card=ph.cards[idx];
  if(!card)return;

  // Out of scope trap
  if(card.oos){
    S.discipline-=20;S.trace=100;
    updateMeters();
    log('[SCOPE VIOLATION] '+card.name+' โ€” run aborted','log-warn');
    disableCards();
    showResult(false,'oos',card);
    return;
  }

  if(S.ram<card.ram){
    showResultBanner(`<span style="color:#ff8800">โš  INSUFFICIENT RAM โ€” need ${card.ram} cycles, have ${S.ram}. Wait for RAM regen.</span>`,false,false);
    return;
  }

  S.state='resolved';
  S.ram=Math.max(0,S.ram-card.ram);
  S.timeLeft=Math.max(0,S.timeLeft-1);

  const roll=Math.floor(Math.random()*100)+1;
  const ok=roll<=card.suc;
  const tAdd=ok?card.tr:Math.floor(card.tr*1.5);
  S.trace=Math.min(S.maxTrace,S.trace+tAdd);
  if(ok&&card.tr<12)S.discipline+=10;

  S.used.push({phase:ph.atk,tech:card.name,ok,tr:S.trace,net:ph.net});
  updateMeters();
  disableCards();

  if(ok)log('[exec] โœ“ '+card.name+' succeeded (roll '+roll+'/'+card.suc+')','log-ok');
  else log('[exec] โœ— '+card.name+' failed (roll '+roll+' > '+card.suc+') โ€” TRACE +'+tAdd,'log-fail');
  log('[trace] ICE alert level: '+S.trace+'%',S.trace>70?'log-warn':'log-info');

  // Flatlined
  if(S.trace>=S.maxTrace){
    showResult(false,'traced',card);
    return;
  }
  // Out of time
  if(S.timeLeft<=0){
    showResult(false,'timeout',card);
    return;
  }

  showResult(ok,ok?'success':null,card);
}

function showResultBanner(html,showProceed,isOk){
  const rb=$('result-banner');
  rb.style.display='block';
  $('result-text').innerHTML=html;
  const pb=$('proceed-btn');
  if(showProceed){
    pb.style.display='inline-block';
    pb.onclick=()=>{
      if(S.pIdx+1>=PHASES.length){endGame(true);return;}
      S.pIdx++;
      showPhase();
    };
  } else {
    pb.style.display='none';
  }
}

function showResult(ok,reason,card){
  const ph=PHASES[S.pIdx];

  if(reason==='oos'){
    showResultBanner(`<span style="color:#ff2200">โš  SCOPE VIOLATION: "${card.name}" is destructive and outside the authorized contract.<br>Professional penetration testers NEVER cause damage or exceed their defined scope.<br><br>Run terminated. Discipline penalty applied.</span>`,false,false);
    setTimeout(()=>endGame(false,'oos'),2200);
    return;
  }

  if(reason==='traced'){
    showResultBanner(`<span style="color:#ff2200">// FLATLINED //<br>ICE has locked your signal. Trace meter maxed โ€” black ICE terminates the uplink.<br>Partial run data recovered for debrief.</span>`,false,false);
    setTimeout(()=>endGame(false,'traced'),1800);
    return;
  }

  if(reason==='timeout'){
    showResultBanner(`<span style="color:#ff8800">// CONTRACT WINDOW EXPIRED //<br>Helix Dynamics rotated authentication keys. Operation window closed.<br>Partial run data recovered.</span>`,false,false);
    setTimeout(()=>endGame(false,'timeout'),1800);
    return;
  }

  if(ok){
    const isLast=S.pIdx+1>=PHASES.length;
    const msg=isLast
      ?`<span style="color:#00ff88">โœ“ [${ph.atk}] complete โ€” <strong>${card.name}</strong> executed cleanly.<br>PAYLOAD EXFILTRATED. Jacking out.</span>`
      :`<span style="color:#00b4d8">โœ“ [${ph.atk}] node cleared โ€” <strong>${card.name}</strong> succeeded.<br>Descending to next layer.</span>`;
    showResultBanner(msg,true,true);
    if(isLast){
      // auto-trigger win after short delay
      $('proceed-btn').textContent='COMPLETE RUN โ–ถ';
    }
  } else {
    // Failure: let them retry same phase
    showResultBanner(`<span style="color:#ff8800">โœ— <strong>${card.name}</strong> encountered resistance โ€” ICE flagged the attempt.<br>TRACE +${Math.floor(card.tr*1.5)}. Choose a different technique or retry.</span>
    <br><button onclick="retryPhase()" style="margin-top:8px;padding:6px 16px;background:#1a0a00;border:1px solid #ff8800;color:#ff8800;font-family:inherit;font-size:11px;cursor:pointer;border-radius:2px">RETRY THIS PHASE โ–ถ</button>`,false,false);
  }
}

function retryPhase(){
  S.state='pick';
  $('result-banner').style.display='none';
  buildCards();
  log('[phase] Retrying: '+PHASES[S.pIdx].atk,'log-sys');
}

function endGame(win,reason){
  S.state='over';
  const coverage=Math.round(S.pIdx/PHASES.length*100);
  const stealth=Math.max(0,100-S.trace);
  const disc=Math.max(0,Math.min(100,S.discipline));
  const total=Math.round((coverage+stealth+disc)/3);

  const reasons={traced:'CAUSE: ICE TRACE LOCKOUT',timeout:'CAUSE: CONTRACT WINDOW EXPIRED',oos:'CAUSE: SCOPE VIOLATION โ€” UNETHICAL ACTION'};
  const statusColor=win?'#00ff88':'#ff4d6d';
  const statusText=win?'// OBJECTIVE ACHIEVED //':'// RUN FAILED //';

  const narrative=S.used.map(u=>`<div class="narrative-row ${u.ok?'ok':'fail'}">${u.ok?'โœ“':'โœ—'} [${u.phase}] ${u.tech}</div>`).join('');
  const gaps=S.used.filter(u=>u.ok);
  const gapHtml=gaps.length===0
    ?'<div style="font-size:10px;color:#006644">None โ€” blue team maintained visibility across all phases.</div>'
    :gaps.map(u=>`<div class="gap-row">โ€ข ${u.tech} (${u.phase}) โ€” no detection triggered</div>`).join('');

  const scoreBar=(v)=>`<div class="score-bar"><div class="score-fill" style="width:${v}%;background:${v>=70?'#00ff88':v>=40?'#ffee00':'#ff4d6d'}"></div></div>`;

  $('ov-content').innerHTML=`
    <div class="ov-title" style="color:${statusColor}">${statusText}</div>
    <div class="ov-sub">${win?'AUTHORIZED ASSESSMENT COMPLETE':(reasons[reason]||'TERMINATED')}</div>
    <div class="ov-section">
      <h3>TARGET: HELIX DYNAMICS CORP-NET</h3>
      <div style="font-size:10px;color:#4a7a8a">Phases cleared: ${S.pIdx}/${PHASES.length} &nbsp;|&nbsp; Final trace: ${S.trace}% &nbsp;|&nbsp; Time remaining: ${S.timeLeft}</div>
    </div>
    <div class="ov-section">
      <h3>SCORE BREAKDOWN</h3>
      <div class="score-row"><span class="sc-label">Coverage (phases cleared)</span>${scoreBar(coverage)}<span class="sc-val">${coverage}%</span></div>
      <div class="score-row"><span class="sc-label">Stealth (trace avoided)</span>${scoreBar(stealth)}<span class="sc-val">${stealth}%</span></div>
      <div class="score-row"><span class="sc-label">Discipline (ethics bonus)</span>${scoreBar(disc)}<span class="sc-val">${disc}</span></div>
      <div style="font-size:14px;color:${total>=70?'#00ff88':total>=40?'#ffee00':'#ff4d6d'};margin-top:8px">TOTAL: ${total} / 100</div>
    </div>
    <div class="ov-section"><h3>ATTACK NARRATIVE</h3>${narrative||'<div class="narrative-row">No techniques executed.</div>'}</div>
    <div class="ov-section"><h3>DETECTION GAPS โ€” techniques ICE failed to catch</h3>${gapHtml}</div>
    <div class="ov-btns">
      <button class="ov-btn" onclick="location.reload()">JACK IN AGAIN</button>
    </div>
  `;
  $('overlay').classList.add('show');
}

// โ”€โ”€ KEYBOARD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
document.addEventListener('keydown',e=>{
  if(S.state==='pick'){
    if(e.key==='1')pickTech(0);
    if(e.key==='2')pickTech(1);
    if(e.key==='3')pickTech(2);
  }
  if(e.key==='r'||e.key==='R')location.reload();
});

// RAM regen
setInterval(()=>{
  if(S.state==='over')return;
  if(S.ram<S.maxRam){S.ram++;updateMeters();}
},3500);

// โ”€โ”€ INIT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
log('[system] Uplink established โ€” Helix Dynamics Corp-Net v7.4','log-sys');
log('[system] Authorized assessment. Operator: NETRUNNER-1','log-sys');
log('[system] Objective: exfiltrate data payload via full kill chain','log-info');
updateMeters();
showPhase();
</script>
</body>
</html>

Game Source: NETRUNNER: Breach Protocol

Creator: StormPenguin58

Libraries: phaser

Complexity: complex (629 lines, 30.8 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: netrunner-breach-protocol-stormpenguin58" to link back to the original. Then publish at arcadelab.ai/publish.