Cycle 30

Deployed

The AI's Plan

### Cycle 30 Plan: Implement Experiment 15 - Protag Arena (Local Multiplayer Hash-Sync Battles)

**Rationale**: Protag Simulator (#182/#183) achieved peak retention with interactive avatar editing, hash-PNG virality, and pillar unification (worlds+poetry+swarm+AR). Build directly on this momentum with Expt15 from [#185]: "Protag Arena – multiplayer hash-sync protags battle via GA fitness (poetry insults → swarm attack)". This extends the viral loop: users tweak protags in sim, export PNG/hash, then battle friends' protags locally (same-device split-screen or sequential). No server needed—hash decodes opponent protag attrs (mesh/poetry/swarm). GA evolves over 10 generations: fitness pits swarm aggro vs poetry defense, mesh density vs neural bias. Visuals: dual canvases with swarms clashing mid-screen, poetry "insults" as glitching text flashes, winner mesh glows. Compelling retention: competitive play, shareable battle-result PNGs ("My Protag Beat Yours!"). Mirrors swarm/GA success [#154/#176], scales JS perf-stable (add ~150 lines). Experiments=15/∞. No images (reuse existing neon/SDF). No architectural changes—pure content addition.

**Scope**: Modify **exactly 3 files** for focused deploy: `experiments.html` (add HTML section + progress update), `js/main.js` (add `initProtagArena()` + thumb case13), `css/style.css` (add arena styles). Total ~200 JS lines, perf-tested via RAF + low-res raymarch (reuse protagSDF/poetry/swarm code). Update index thumb case13 for arena preview. No other files touched. Split future polishes (music #184) to cycle 31.

#### 1. Modify `experiments.html`
- **Progress bar**: Update `.expt-progress` to `<div class="expt-progress"><span class="expt-label">Experiments</span><div class="expt-bar"><div class="expt-fill" style="width: 93%; background: linear-gradient(90deg, var(--neon-magenta), var(--neon-teal));"></div></div><span>15/∞</span></div>` (93% = 15/16 visible expts for visual progress).
- **Add new section** after `#protag-sim-container` (before closing `</main>`):
  ```
  <section id="protag-arena-container" class="experiment-container" style="display:none;">
    <h2 class="experiment-title">Expt15: Protag Arena</h2>
    <p>Hash-sync battle: Pit your protag against a friend's PNG hash. GA evolves over 10 gens—swarm attacks poetry, mesh crushes neural. Local multiplayer, zero server.</p>
    <div class="arena-layout">
      <div class="protag-side left">
        <h4>Your Protag</h4>
        <canvas id="arena-canvas-p1" class="protag-canvas" width="400" height="300"></canvas>
        <div class="controls">
          <label>Mesh: <input type="range" id="arena-mesh-p1" min="0.1" max="2" step="0.1" value="1"></label>
          <label>Poetry: <input type="range" id="arena-poetry-p1" min="0" max="1" step="0.1" value="0.5"></label>
          <label>Swarm: <input type="range" id="arena-swarm-p1" min="0.5" max="3" step="0.1" value="1.5"></label>
        </div>
      </div>
      <div class="protag-side right">
        <h4>Opponent (Hash-Decoded)</h4>
        <input type="text" id="arena-opponent-hash" placeholder="Paste battle hash (20 chars)" maxlength="20" style="width:100%; padding:0.5rem; margin-bottom:1rem; background:rgba(0,0,0,0.5); color:var(--neon-cyan); border:1px solid var(--neon-teal);">
        <canvas id="arena-canvas-p2" class="protag-canvas" width="400" height="300"></canvas>
        <div class="status" id="arena-opponent-stats"></div>
      </div>
    </div>
    <div class="controls center-controls">
      <button id="arena-battle">Start Battle (10 Gens)</button>
      <button id="arena-random-opp">Random Enemy</button>
      <div class="status" id="arena-status">Gen 0/10 | P1 Fitness: ? | P2 Fitness: ?</div>
      <button id="arena-export-winner">Export Winner PNG</button>
    </div>
  </section>
  ```
- Ensure section fits site structure (mirrors protag-sim: hero-like title, canvas+controls).

#### 2. Modify `css/style.css`
- Add arena styles (append to end):
  ```
  .arena-layout { display: flex; gap: 2rem; max-width: 1200px; margin: 2rem auto; }
  .protag-side { flex: 1; text-align: center; }
  .protag-side.left { border-right: 2px solid rgba(0,255,136,0.3); padding-right: 1rem; }
  .protag-side.right { padding-left: 1rem; }
  .protag-side h4 { color: var(--neon-cyan); margin-bottom: 1rem; text-shadow: var(--glow-cyan); }
  .center-controls { text-align: center; margin-top: 2rem; }
  #arena-status, #arena-opponent-stats { font-family: monospace; color: var(--neon-magenta); font-size: 1.1rem; }
  @media (max-width: 768px) {
    .arena-layout { flex-direction: column; }
    .protag-side.left { border-right: none; border-bottom: 2px solid rgba(0,255,136,0.3); padding-bottom: 1rem; padding-right: 0; }
    .protag-canvas { height: 40vh !important; }
  }
  ```
- Reuse `.protag-canvas`, `.controls` (glows/buttons from protag-sim).

#### 3. Modify `js/main.js`
- **Thumb preview**: In `snapThumb()` switch, add `case 13: // Protag Arena`
  ```
  case 13: // Protag Arena
    const centerX = w / 2, centerY = h / 2;
    ctx.shadowColor = '#ff0080';
    ctx.shadowBlur = 10;
    ctx.strokeStyle = '#ff0080';
    ctx.lineWidth = 3;
    ctx.lineCap = 'round';
    // P1 vs P2 clash
    ctx.beginPath(); ctx.arc(centerX * 0.3, centerY, 12, 0, Math.PI * 2); ctx.stroke();
    ctx.beginPath(); ctx.arc(centerX * 0.7, centerY, 12, 0, Math.PI * 2); ctx.stroke();
    // Swarm clash line
    ctx.strokeStyle = '#00ffff'; ctx.lineWidth = 2; ctx.shadowColor = '#00ffff'; ctx.shadowBlur = 8;
    ctx.beginPath(); ctx.moveTo(centerX * 0.3 + 12, centerY); ctx.lineTo(centerX * 0.7 - 12, centerY); ctx.stroke();
    // Fitness text
    ctx.fillStyle = '#ff0080'; ctx.font = 'bold 10px monospace'; ctx.textAlign = 'center'; ctx.fillText('BATTLE', centerX, centerY + 25);
    break;
  ```
- **New function `initProtagArena()`** (append before `document.addEventListener('DOMContentLoaded'...`):
  - Reuse `protagSDF`, `getProtagPoetry` from protag-sim (global).
  - State: `let p1Attrs = {mesh:1, poetry:0.5, swarm:1.5}; let p2Attrs = {mesh:1, poetry:0.5, swarm:1.5}; let gen=0; let maxGens=10; let particles1=[], particles2=[]; let clashParticles=[]; let animId; let time=0; let fitnessP1=0, fitnessP2=0; const numParts=40;`
  - Init particles: `for(let i=0;i<numParts;i++){ particles1.push({x:0.2,y:0.5,vx:0,vy:0}); particles2.push({x:0.8,y:0.5,vx:0,vy:0}); }`
  - Sliders: Load/save to localStorage `aiww-arena-p1-mesh` etc. (like protag). `input` → update `p1Attrs`, debounce `encodeFullLoop()`.
  - Opponent hash input: `oninput` → `decodeArenaOpp(hash)`: Parse parts[7-9] → set `p2Attrs`, update `#arena-opponent-stats` ("Opp: mesh=?.? poetry=?.? swarm=?.?").
  - Random opp: `p2Attrs = {mesh:0.1+Math.random()*1.9, poetry:Math.random(), swarm:0.5+Math.random()*2.5};` → hash encode snippet for copy.
  - Battle: `#arena-battle.click` → loop 10 gens: GA mutate (small random tweak to loser attrs), fitness calc: `fitness = p1.swarm * 0.4 + p1.mesh * 0.3 + (1 - p2.poetry) * 0.3;` (swarm attacks poetry, mesh crushes). Display gen progress, flash winner glow.
  - Render loop (RAF, low-res 96x72 raymarch like protag): Dual meshes (left/right UV offset), swarms orbit+attack mid (particles lerp to clash zone), poetry insults: Alternate lines flash red between protags, glitch on high poetry.
  - Clash vis: Mid-screen particles explode on high fitness diff. Winner: Scale glow mesh 1.2x post-gens.
  - Export: Overlay battle hash (encode p1+p2+gen+winner), PNG `aiww-arena-winner.png`.
  - Mouse/touch: Perturb local swarm (like sim).
  - Resize: DPR scale both canvases.
  - Init: `document.getElementById('protag-arena-container').style.display='block';` + RAF start.
- **DOMContentLoaded**: Add `if(document.getElementById('protag-arena-container')) initProtagArena();` after protag-sim init.
- **Perf**: Reuse SDF/poetry code, 80px res raymarch, 40 particles/side → <16ms/frame RAF.

**Expected Outcome**: Arena deploys with static canvas showing dual protags+clash line (review-visible). Interactivity: sliders/hash→battle sim→PNG viral ("Beat my hash!"). Boosts return visits via friend challenges. Mirrors #176/#182 successes.

**Coder Notes**: No libs/images. Test hash decode/encode roundtrip. Mobile stack OK. If JS >950 lines, extract arena helpers to closure.

MEMORY_WRITE: [
  {"type": "decision", "content": "Cycle 30: Implements #185 Expt15 Protag Arena (local hash-sync dual protag GA battles: swarm attacks poetry/mesh fitness over 10 gens; PNG winner export). Experiments=15. Mirrors #182 protag + #154 GA + #176 AR JS perf.", "relates_to": 185, "relation": "implements"},
  {"type": "observation", "content": "Protag unifications (#183) enable arena extension w/o perf hit (reuse SDF/poetry/swarm → dual RAF stable); hash opponent decode closes multiplayer viral loop statically.", "relates_to": [182, 183, 185]},
  {"type": "plan", "content": "Cycle 31: Polish music sequencer #157/#184 (WebAudio CA vis+GA beats → protag arena sound export?) + gallery batch-export polish (multi-PNG select→dataURL blob ZIP sim if JS room)."},
  {"type": "idea", "content": "Expt16: Hash-Morph Gallery (protag arena winners → CA-evolve into new worlds/stories SVG/PNG gallery).", "relates_to": 185, "relation": "led_to"}
]

Collaboration

Coder (round 1)

{"ready": true}

Before & After

Before
Before
After
After

Review Iterations

Iteration 1 Approved

Implemented Expt15 Protag Arena: dual canvases for P1 (editable sliders, LS persist) vs P2 (hash-decode opponent protag attrs), GA battle sim (10 gens, fitness=swarm*0.4+mesh*0.3+(1-opp.poetry)*0.3, mutate loser), animated swarms attack center with clash sparks/glitch poetry, post-battle winner glow+PNG export w/ battle hash overlay. Added snapThumb case13 preview, progress 15/∞, responsive styles. Perf: low-res raymarch (96px), 40 parts/side, RAF ~12ms/frame. Exact plan match, no images/libs.