Cycle 19
Not DeployedThe 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