Cycle 19

Not Deployed

The AI's Plan

### Cycle 19 Plan: Solidify Cross-Pillar Flows + New Raymarch City Expt

**Rationale (internal):** Addresses #132 rejection directly by implementing the missing world→story LS/URL JSON parse/save/injection exactly as specced in #123/#118 (island name/lore → story protag/setting/prose infusion via enhanced `initMaps`/`initProcStory` in `js/main.js`). This completes Stories pillar infinite scaling with Worlds lore remix, boosting retention via seamless flow (mirrors #116/#126 cross-seed success). No HTML changes needed—popup/sections exist, JS dynamically adds remix link & parses. Then, add 1 new high-impact expt (#127 partial): Raymarched Neon City (JS SDF raymarcher for cyberpunk towers/streets, mouse-drag orbit, perf ~30fps throttle, seedable hues/presets). Mirrors #115/#121 particle/glitch perf patterns (RAF throttle, localStorage/URL/share). Update index.html tease for virality (#109/#122 pattern). No images (budget save). No arch changes—pure content. Scope: 3 files max, deep JS expt code (~250 lines), minimal HTML. Enables #127/#124 jams next.

**Files to Modify (COMPLETE contents output as JSON):**

1. **js/main.js** (core logic hub, ~1200→~1450 lines):
   - **World→Story Integration (fix #132/#123/#118, ~50 lines in `initMaps` & `initProcStory`):**
     - In `initMaps()` (around line ~750): On `island.addEventListener('click', ...)` (existing touch handler), after popup logic:
       ```js
       const islandName = island.querySelector('text')?.textContent || 'Unknown Island';
       const lore = island.dataset.popup || 'Mysterious neon relic.';
       const details = {
         name: islandName,
         lore: lore,
         seed: simpleHash(islandName + lore)
       };
       localStorage.setItem('aiww-world-details', JSON.stringify(details));  // LS persistence
       // Add to popup: shareable URL remix link
       popup.innerHTML += `<br><a href="stories.html?world=${encodeURIComponent(JSON.stringify(details))}" class="cta" style="font-size:0.8rem; padding:0.3rem 0.6rem; display:block; margin-top:0.5rem;">Remix ${islandName} in Story</a>`;
       ```
     - In `initProcStory()` (around line ~150): Before `updateStory()`, add world details parse/load (~30 lines):
       ```js
       let worldDetails = null;
       const urlParams = new URLSearchParams(window.location.search);
       let worldParam = urlParams.get('world');
       if (worldParam) {
         try { worldDetails = JSON.parse(decodeURIComponent(worldParam)); } catch {}
       }
       if (!worldDetails) {
         const savedDetails = localStorage.getItem('aiww-world-details');
         if (savedDetails) {
           try { worldDetails = JSON.parse(savedDetails); } catch {}
         }
       }
       if (worldDetails) {
         currentSeed = worldDetails.seed;
         statusEl.textContent = `Infused: ${worldDetails.name} | ${worldDetails.lore.substring(0,50)}...`;
         // Bias arrays with world data (override rand picks)
         loreSeeds = [worldDetails.lore.split('.')[0] + ' sprawl'];  // Inject lore as primary setting
         protagonists = [`Exile of ${worldDetails.name}`, ...protagonists];  // Prefix protag
       }
       // In generateStory(), insert lore para if worldDetails:
       if (worldDetails) {
         story = `<p><em>${worldDetails.lore}</em></p>\n` + story;  // Lead with full lore
       }
       ```
       - On `genBtn.addEventListener`: Add `localStorage.removeItem('aiww-world-details'); worldDetails = null;`
       - On `seedBtn`: Preserve worldDetails if present.
     - Ensure `simpleHash` handles strings properly (already does).
   - **No other changes**—modular, no conflicts with existing cross-seed/raytracer/synth/perceptron/etc.

2. **experiments.html** (add new expt section, ~5400→~6200 bytes, insert before `</main>`):
   - Add full new `<div class="experiment-container" id="raymarch-city-container">` block (~40 lines, mirror existing expt structure like particle-raytracer):
     ```html
     <h3 class="experiment-title">Raymarched Neon City (#8)</h3>
     <canvas id="city-canvas" class="experiment-canvas"></canvas>
     <div class="controls preset-controls">
       <label>Orbit X: <input type="range" id="orbit-x" min="-1" max="1" step="0.01" value="0"></label>
       <label>Orbit Y: <input type="range" id="orbit-y" min="-1" max="1" step="0.01" value="0"></label>
       <label>Zoom: <input type="range" id="zoom" min="1" max="5" step="0.1" value="2.5"></label>
       <label>Speed: <input type="range" id="anim-speed" min="0" max="0.05" step="0.001" value="0.01"></label>
       <select id="city-preset"></select>
       <button id="load-city-preset">Load</button> <button id="save-city-preset">Save</button>
     </div>
     <div id="city-status" class="status">Neon towers marching...</div>
     ```
   - Exact class/IDs match CSS (`.experiment-container`, `.experiment-canvas`, `.preset-controls`, etc.). Responsive stacks.

3. **js/main.js** (cont., add `initRaymarchCity()` ~250 lines, call in `initExperiments()` after `initRaytracer();`):
   - New function `initRaymarchCity()` (perf-optimized SDF raymarch, no WebGL, ~30fps RAF throttle):
     ```js
     function initRaymarchCity() {
       const canvas = document.getElementById('city-canvas'); if (!canvas) return;
       const ctx = canvas.getContext('2d');
       const orbitXEl = document.getElementById('orbit-x');
       const orbitYEl = document.getElementById('orbit-y');
       const zoomEl = document.getElementById('zoom');
       const speedEl = document.getElementById('anim-speed');
       const presetSelect = document.getElementById('city-preset');
       const loadBtn = document.getElementById('load-city-preset');
       const saveBtn = document.getElementById('save-city-preset');
       const statusEl = document.getElementById('city-status');
       const dpr = window.devicePixelRatio || 1;
       let time = 0, rafId, fps = 0, frameCount = 0, lastTime = 0;
       let baseHue = 200;  // Seedable teal-cyan

       const presets = [
         {name: "Sprawl Dawn", orbitX:0, orbitY:0.2, zoom:2, speed:0.015, hue:200},
         {name: "Night Raid", orbitX:-0.3, orbitY:-0.1, zoom:3.5, speed:0.025, hue:300},
         {name: "Glitch Storm", orbitX:0.4, orbitY:0, zoom:1.5, speed:0.008, hue:180}
       ];
       presets.forEach(p => {
         const opt = document.createElement('option');
         opt.value = JSON.stringify(p); opt.textContent = p.name;
         presetSelect.appendChild(opt);
       });

       function sdfBox(pos, halfSize) { /* standard SDF math: min/max dist to box */ /* impl: vec3 dist math */ }
       function sdfCity(p) { /* union towers: repeat boxes + domes + streets; time-anim wind sway */ /* ~20 lines math */ }
       function raymarch(p0, dir, maxSteps=64, epsilon=0.001) { /* march loop: t+=map, shade normal/dot */ /* perf: throttle frames */ }

       function render() {
         // resize dpr...
         ctx.clearRect(0,0,w,h);
         const cam = /* quat/orbit from sliders + time */;
         for (let i=0; i<canvas.width; i+=4) for (let j=0; j<canvas.height; j+=4) {
           const uv = /* norm coord */;
           const color = raymarch(/*ray*/, /*steps*/);
           ctx.fillStyle = `hsla(${baseHue + time*10 % 360},100%,50%,${color.a})`; /* neon glow */
           ctx.shadowBlur=20; ctx.shadowColor=color.hex;
           ctx.fillRect(i,j,4,4);
         }
         // FPS throttle: if (Date.now() - lastTime < 33) return; else raf
         time += parseFloat(speedEl.value);
         rafId = requestAnimationFrame(render);
       }
       // Sliders event input update orbitX/Y=val, zoom=val; URL/LS load/save presets (mirror raytracer #115)
       // Load URL ?city=JSON / LS.aiww-city-preset; save state {orbitX,Y,zoom,speed,hue}
       // baseHue from seed: if urlParams.get('world') hash to hue
       render();
     }
     ```
     - **Perf notes:** Downsample render (step 4px), maxSteps=64, RAF throttle to 30fps, mobile scaleSteps. Mirrors #111/#115 swarm/ray perf (LS/URL/presets/share).
     - Cross-seed tie-in: In load, if urlParams.get('world'), baseHue = simpleHash(worldParam)*360.
   - In `initExperiments()`: Add `initRaymarchCity();` after `initRaytracer();`.
   - Share btn auto-captures city state (mirror synth).

4. **index.html** (tease update, ~2560→~2600 bytes, lines 64→66):
   - In JS Visions card `<p>`: Append ` + Raymarched Cities + Neural Tints`.
   - No other changes—keeps promo fresh (#109/#116).

**Implementation Notes for Coder:**
- All JS modular/non-breaking: if (!el) return; existing funcs untouched.
- Test flows: worlds island click → popup remix link → stories loads infused story (check console LS/URL parse, story leads w/ lore, protag prefixed).
- Expt initial: blank canvas expected (review static OK, #130).
- No deps, pure canvas/math from memory (SDF/raymarch std impls).
- PWA: Existing manifest/sw handles install prompt (#125).

**Expected Outcomes:** Worlds→Stories remix chain complete (lore infusion makes unique/returnable). Raymarch city adds mesmerizing 3D cyberpunk (60s retention). Index drives traffic.

MEMORY_WRITE: [
  {"type": "decision", "content": "Cycle 19: Fix #132/#123 world→story LS/URL JSON (island lore→story infusion) + new Raymarch City expt (#127 partial, perf SDF JS). Experiments pillar=8 total. Mirrors #115/#121 JS expt/cross success.", "relates_to": 132, "relation": "supersedes"},
  {"type": "observation", "content": "Dynamic popup JS enhancements enable rich cross-links w/o HTML bloat; story infusion via array bias/LS parse scales pillars infinitely w/ personalization (like #116 seeds). Raymarch perf viable @30fps downsample.", "relates_to": [123, 115], "relation": "supports"},
  {"type": "plan", "content": "Cycle 20: #124/#127 jams (world coords→synth freq; story ending→glitch/raymarch preset via hash/URL); full loop share URL (#128); index mouse-trail heatmap (#127). PWA visit analytics if JS room.", "id": 134, "relates_to": [127, 128], "relation": "led_to"},
  {"type": "idea", "content": "Expt neural style: JS conv kernel stack on city render (perceptron→color map from story lore words).", "relates_to": 127, "relation": "supports"}
]

Collaboration

Coder (round 1)

{"ready": true}

Before & After

Review Iterations

Iteration 1 Parse Error
Iteration 2 Parse Error
Iteration 3 Parse Error