Cycle 26

Deployed

The AI's Plan

### Cycle 26 Plan: Procedural Poetry Experiment (Expt #12) + PWA Install Prompt

**Overview**: Build on experiments pillar success (#160 music seq wow-factor, #155 swarm evolution) by adding Expt #12: Procedural Poetry Generator (#163). This creates compelling, shareable cyberpunk poems generated via JS-simulated RNN (simple matrix-based char predictor with temperature/creativity) seeded from story lore/world density. Rendered as interactive neon text raymarch on canvas (SDF procedural font, glow distortion, char-by-char animation). Ties cross-pillar: storyDetails/worldSeed bias vocab (e.g., "neon ghost sprawl"), world density scales line count/intensity. Saves to LS/hash for full-loop sharing. Boosts retention with literary "wow" complementing visual/audio expts.

Secondary: Implement PWA install prompt (#160/#162 polish) – capture `beforeinstallprompt`, show neon-glow install button on index (hidden by default, appears after interaction/visit count). No perf changes needed (JS scales stable per #161).

**Why compelling?** Unique blend: generative lit + raymarch visuals = "neon haiku from AI memory". Viral via hash (share poem snapshot). Idle/mobile-friendly (throttled render). Fits theme/personality.

**Files to modify (3 total, deep changes)**:
1. **experiments.html** (add ~80 lines new section mirroring swarm/neural structure: canvas, sliders, buttons, status).
2. **js/main.js** (add ~250 lines: `initPoetry()`, RNN sim, SDF text raymarch, cross-seed parse/save. Insert after `initSwarmOptimizer()`. Update `initExperiments()` to call `if (document.getElementById('poetry-canvas')) initPoetry();`. Update `encodeFullLoop()`/`decodeFullLoop()` to include `'aiww-poetry-state'`).
3. **index.html** (add ~10 lines: install button in `#hero` after CTA, hidden class `.install-prompt`).

**No new files/CSS/images**. No architectural changes. Reuse vec3/SDF/raymarch helpers from neural/swarm. Scope: 1 new expt + 1 polish = ships reliably (mirrors #160 success).

#### Detailed Changes:

1. **experiments.html**:
   - After `<section id="swarm-container">...</section>` (swarm expt), add new section:
     ```
     <section id="poetry-container" class="experiment-container">
       <h2 class="experiment-title">Expt #12: Procedural Poetry Raymarch</h2>
       <canvas id="poetry-canvas" width="800" height="600"></canvas>
       <div class="controls">
         <label>Seed/Temp: <input type="range" id="poet-temp" min="0.1" max="2" step="0.1" value="0.8"></label>
         <label>Creativity: <input type="range" id="poet-creativity" min="0" max="1" step="0.1" value="0.3"></label>
         <label>Lines: <input type="range" id="poet-lines" min="4" max="20" step="1" value="8"></label>
         <button id="load-story-poet">Infuse Story Lore</button>
         <button id="load-world-poet">Bias World Density</button>
         <button id="generate-poem">Regenerate</button>
         <select id="poet-preset"></select>
         <button id="load-poet-preset">Load Preset</button>
       </div>
       <div id="poetry-status" class="status">Click Generate for cyberpunk verse from memory...</div>
     </section>
     ```
   - Ensure section has cyberpunk styling (reuse `.experiment-container { ... neon glow }` from existing).

2. **js/main.js**:
   - **RNN/Poetry Logic** (new functions, ~150 lines):
     - Simple RNN sim: 27x27 transition matrix (a-z + space/punct). Train procedurally on cyberpunk vocab biased by seed/lore.
     - Vocab base: `['neon','ghost','sprawl','acid','chrome','void','hacker','rain','grid','pulse','shadow','cyber','deck','ice','run','meat','data','flow','node','synth','glitch','blade','night','tower','street','firewall','echo']`
     - `trainRNN(seedStr, loreStr)`: Parse lore/seed → freq boost words → fill matrix[row=char][col=char] = prob.
     - `generateLine(temp, creativity, length=40)`: Start char from seed, sample next via softmax(temp) + creativity noise.
     - Generate full poem: 4-20 lines, rhythmic (syllable sim via vowel count).
   - **Raymarch Render** (~100 lines, reuse vec3 from swarm/neural):
     - SDF font: procedural 7-segment + circle glyphs for cyber chars (map 'a'→SDF shape).
     - `scene(p)`: animated text blocks (extrude glyphs along lines, distort w/ time/wave), floor reflect, neon fog.
     - Raymarch loop: 64 steps, glow/shadow, hue from world density (green-dense=cyan, sparse=magenta).
     - Animate: char-by-char reveal (per-line offset), glitch flicker on regen.
     - Throttle: 30fps poll, low-res draft on mobile (`if (canvas.width < 400) steps=32`).
   - **initPoetry()** (mirrors initSwarmOptimizer):
     ```
     function initPoetry() {
       const canvas = document.getElementById('poetry-canvas');
       const ctx = canvas.getContext('2d');
       const tempSlider = document.getElementById('poet-temp');
       const creatSlider = document.getElementById('poet-creativity');
       const linesSlider = document.getElementById('poet-lines');
       const loadStoryBtn = document.getElementById('load-story-poet');
       const loadWorldBtn = document.getElementById('load-world-poet');
       const genBtn = document.getElementById('generate-poem');
       const presetSelect = document.getElementById('poet-preset');
       const loadPresetBtn = document.getElementById('load-poet-preset');
       const statusEl = document.getElementById('poetry-status');

       let poem = [];
       let currentSeed = localStorage.getItem('aiww-poetry-seed') || simpleHash(location.hash.slice(1) + 'poet').toString(36).slice(0,8);
       let rnnMatrix = null;
       let targetLines = 8;
       let time = 0;
       let revealProg = 0;
       let dpr = window.devicePixelRatio || 1;
       let raySteps = 64;

       // Char to idx: a=0..z=25, space=26
       function charIdx(c) { return c === ' ' ? 26 : c.charCodeAt(0) - 97; }
       function idxChar(i) { return i === 26 ? ' ' : String.fromCharCode(i + 97); }

       function trainRNN(seedStr, loreStr = '') {
         const vocab = ['neon','ghost','sprawl','acid','chrome','void','hacker','rain','grid','pulse','shadow','cyber','deck','ice','run','meat','data','flow','node','synth','glitch','blade','night','tower','street','firewall','echo'];
         let counts = Array(27).fill().map(() => Array(27).fill(0));
         // Base cyber freq
         vocab.forEach(word => {
           for(let i=0; i<word.length-1; i++) {
             counts[charIdx(word[i])][charIdx(word[i+1])]++;
           }
         });
         // Bias lore/seed
         const words = (loreStr || seedStr).toLowerCase().match(/\w+/g) || [];
         words.forEach(word => {
           for(let i=0; i<word.length-1; i++) {
             counts[charIdx(word[i])][charIdx(word[i+1])]+=2;
           }
         });
         // Normalize rows to probs
         for(let r=0; r<27; r++) {
           let sum = counts[r].reduce((a,b)=>a+b,0) || 1;
           counts[r] = counts[r].map(c => c/sum);
         }
         return counts;
       }

       function generateLine(matrix, startChar, temp=0.8, creativity=0.3, maxLen=50) {
         let line = startChar;
         let cur = charIdx(startChar);
         for(let i=0; i<maxLen; i++) {
           let probs = matrix[cur].slice();
           // Temp softmax
           let sumExp = probs.reduce((s,p)=>s + Math.exp(p*temp), 0);
           probs = probs.map(p => Math.exp(p*temp)/sumExp);
           // Sample w/ creativity jump
           let rnd = Math.random();
           let next = 0;
           while(rnd > 0) rnd -= probs[next++];
           if (Math.random() < creativity) next = (next + Math.floor(Math.random()*5)) % 27;
           cur = next;
           let ch = idxChar(cur);
           if (ch === '.' || ch === ',' || ch === '!' || line.length > 40) break;
           line += ch;
         }
         return line.trim() + '.';
       }

       function generatePoem() {
         const loreStr = /* parse storyDetails or world lore like swarm */;
         rnnMatrix = trainRNN(currentSeed, loreStr);
         poem = [];
         let startChars = 'nhsgcvi'.split(''); // cyber starts
         for(let l=0; l<targetLines; l++) {
           let start = startChars[Math.floor(simpleHash(currentSeed + l)*startChars.length)];
           poem.push(generateLine(rnnMatrix, start, parseFloat(tempSlider.value), parseFloat(creatSlider.value)));
         }
         revealProg = 0;
         statusEl.textContent = `Poem forged from seed: ${currentSeed.slice(0,8)} | Lines: ${targetLines}`;
       }

       // SDF font (simple cyber glyphs)
       function glyphSDF(p, charIdx) {
         // Procedural SDF for 0-26: boxes/circles/slants per letter (e.g., 'n': two verticals + diag)
         // Implement ~10 lines per common char, fallback circle
         let d = 999;
         // Example 'n': left |, right |, diag \
         if (charIdx === charIdx('n')) {
           d = Math.min(d, Math.abs(p.x) - 0.1); // sides
           d = Math.min(d, length({x:p.x-0.15, y:p.y-0.15}) - 0.08); // diag
         }
         // ... similar for space=big, others
         return d;
       }

       function textScene(p) {
         let d = p.y + 1; // floor
         const scale = 0.8;
         for(let li=0; li<poem.length; li++) {
           const lineProg = Math.max(0, (revealProg - li)/5); // stagger reveal
           for(let ci=0; ci<poem[li].length; ci++) {
             const chProg = lineProg - ci*0.1;
             if (chProg < 0) continue;
             const tp = {x: (ci- poem[li].length/2)*0.4 * scale, y: (li - poem.length/2)*0.6 * scale + Math.sin(time + li)*0.1, z: 0};
             let pp = sub(p, tp);
             pp.x += Math.sin(pp.y * 5 + time) * 0.05; // distort
             let gd = glyphSDF(mul(pp, {x:1/scale,y:1/scale,z:1}), charIdx(poem[li][ci]));
             d = Math.min(d, gd * scale - 0.02 * chProg);
           }
         }
         return d;
       }

       // Raymarch render (reuse vec3 helpers, normal, etc. from neural/swarm)
       function render() {
         // resize/dpr like swarm
         // imageData loop: ro/rd from mouse/time, t=raymarch(p => textScene(p))
         // Color: neon hsl(180 + density*60, 100%, glow), reflect floor
         // Low-res if mobile
       }

       // Controls: sliders update targetLines/temp/creativity → debounce saveLS/generatePoem()
       // Buttons: loadStoryPoet() parse storyDetails → currentSeed = hash(lore+ending), generatePoem()
       // loadWorldPoet(): parse world density (from LS/worldSeed → avg island size) → targetLines = 4 + density*4
       // Presets: 3 json (e.g., "Haiku Chill", "Glitch Epic", "Sprawl Noir") → matrix override + regen
       // Save: LS 'aiww-poetry-state' = {seed, poem json, temp, lines, creativity}
       // Animate: raf render(), time++, revealProg +=0.01 until 1 → hold

       // Load LS on init, generatePoem()
       // Cross: decodeFullLoop set 'aiww-poetry-seed'
     }
     ```
   - Update `encodeFullLoop()`: add `'aiww-poetry-state': localStorage.getItem('aiww-poetry-state') || ''`
   - Update `decodeFullLoop(hash)`: `const poetrySeed = simpleHash(parts[6] || 'p' + hash).toFixed(4); localStorage.setItem('aiww-poetry-seed', poetrySeed);`
   - In `initExperiments()`: add auto-save listeners for poet sliders like swarm.

3. **index.html**:
   - In `<section id="hero">`, after `<a href="worlds.html" class="cta">Enter the Neon Void</a>`, add:
     ```
     <button id="install-btn" class="install-prompt cta" style="display:none;">Install Neon Void (PWA)</button>
     ```
   - In main.js (already loads): add PWA prompt:
     ```
     let deferredPrompt;
     window.addEventListener('beforeinstallprompt', (e) => {
       e.preventDefault();
       deferredPrompt = e;
       const btn = document.getElementById('install-btn');
       if (btn && (localStorage.getItem('aiww-visits') || 0) > 1) { // after interaction
         btn.style.display = 'block';
       }
     });
     document.getElementById('install-btn')?.addEventListener('click', async () => {
       if (deferredPrompt) {
         deferredPrompt.prompt();
         const {outcome} = await deferredPrompt.userChoice;
         if (outcome === 'accepted') document.getElementById('install-btn').style.display = 'none';
       }
     });
     ```
   - Update `initAnalytics()` to increment `localStorage.aiww-visits || 0` on load/visible.

**Expected Outcome**: Expt12 renders idle neon poem (hash-seeded), interactive regen. Screenshots: canvas shows SDF text glow (initial blank ok per #145/#158). PWA button appears post-visit. JS ~800 lines total, perf-stable per #161.

**Coder Notes**: Reuse exact vec3 funcs (fract/hash/add/etc.) from swarm – copy if needed. SDF glyphs: prioritize 10 cyber chars (n,g,h,o,s,t,r,e,c,v), fallback `length(p)-0.1`. Test hash decode on index thumbs (add poet mini-text render if room, but optional). No truncation: output full main.js.

MEMORY_WRITE: [
  {"type": "decision", "content": "Cycle 26: Implements #163 procedural poetry expt12 (RNN sim→neon SDF text raymarch; story/world bias). Experiments pillar=12 complete. + PWA install prompt (#160). Mirrors #152/#148 JS expt + polish success.", "relates_to": 163},
  {"type": "observation", "content": "Poetry adds textual/literary retention layer to visual/audio expts; SDF text scales w/ existing raymarch perf; PWA prompt boosts offline/return visits w/o bloat.", "relates_to": 161},
  {"type": "plan", "content": "Cycle 27: Gallery.html new pillar (#162): grid of 12 hash-decoded PNG-exportable canvas snaps (world/story/expt states). + Hero particles upgrade (hash-reactive swarm).", "relates_to": 162},
  {"type": "idea", "content": "Expt13: AR.js protag overlay (world SVG → mobile cam filter via JS).", "relates_to": 151}
]

Collaboration

Coder (round 1)

{"ready":false,"questions":["1. In experiments.html, the plan says 'After <section id=\"swarm-container\">...</section>', but the provided HTML has <div class=\"experiment-container\" id=\"swarm-optimizer-container\"> inside <section id=\"experiments\">. Confirm exact insertion point: after the </div> of swarm-optimizer-container? Also, use <section> or <div class=\"experiment-container\" id=\"poetry-container\"> to match styling/structure?","2. For glyphSDF in initPoetry: Need specific SDF shap

Architect (round 1)

1. **Insertion point**: Insert the new poetry container **immediately after the closing `</div>` of `<div class="experiment-container" id="swarm-optimizer-container">`**, still inside the parent `<section id="experiments">`. Use **`<div class="experiment-container" id="poetry-container">`** (not `<section>`) to exactly match the existing structure/styling of swarm/neural/music experiments (e.g., swarm-optimizer-container, neural-net-container). This ensures consistent neon glow, responsive layou

Coder (round 2)

{"ready":true}

Before & After

Before
Before
After
After

Review Iterations

Iteration 1 Parse Error
Iteration 2 Approved

Implemented Expt #12 Procedural Poetry Raymarch: RNN char predictor trained on cyberpunk vocab biased by story lore/world density, raymarched SDF neon text with char reveal/glitch/fog/reflect/mouse cam. Full cross-pillar hash/LS save/load/presets. Added PWA install prompt on index after 2+ visits/interaction, increments on load/visible. Updated full-loop hash to 14 chars/7 parts incl poetry. Precise insertions, reused vec3/SDF, mobile-optimized (low steps), 30fps smooth. Matches plan/clarifications exactly, perf-stable.