// GODSEED — main page. Boot the planet, wire the HUD, then either: // live : GET /api/state → EventSource /api/stream (SSE protocol) // mock : ?mock=1 → web/mock/world.json + scripted looping wish feed // The deterministic engine owns the facts; this file only performs them. import { createPlanetApp } from "./planet/scene.js"; import { buildWishScript, ScriptRunner } from "./planet/replay.js"; import { latLonToDir, PLANET_R, ordinal, clamp, townCenter } from "./planet/util.js"; const params = new URLSearchParams(location.search); const MOCK = params.has("mock"); const STATIC = params.has("static"); const UPTO = params.has("upto") ? Math.max(0, parseInt(params.get("upto"), 10) || 0) : null; // ---------------------------------------------------------------- HUD const els = { epoch: document.getElementById("epoch"), voiceDot: document.getElementById("voiceDot"), voiceWish: document.getElementById("voiceWish"), stream: document.getElementById("voiceStream"), epitaph: document.getElementById("epitaph"), epitaphText: document.getElementById("epitaphText"), epitaphLabel: document.getElementById("epitaphLabel"), epitaphDescend: document.getElementById("epitaphDescend"), queueChip: document.getElementById("queueChip"), form: document.getElementById("wishForm"), input: document.getElementById("wishInput"), send: document.getElementById("wishSend"), hint: document.getElementById("altarHint"), status: document.getElementById("status"), behold: document.getElementById("beholdToggle"), }; const HINT_DEFAULT = "the god grants one wish at a time"; let hintTimer = null; function hint(text, { warn = false, revert = 6000 } = {}) { els.hint.textContent = text; els.hint.classList.toggle("is-warn", warn); clearTimeout(hintTimer); if (revert) { hintTimer = setTimeout(() => { els.hint.textContent = HINT_DEFAULT; els.hint.classList.remove("is-warn"); }, revert); } } const caret = document.createElement("span"); caret.className = "voice__caret"; function tickerClear() { els.stream.innerHTML = ""; } function tickerPush(text, tone = "thought") { caret.remove(); let inner = els.stream.querySelector(".voice__innertext"); if (!inner) { inner = document.createElement("span"); inner.className = "voice__innertext"; els.stream.appendChild(inner); } const span = document.createElement("span"); span.className = `tone-${tone}`; span.textContent = text; inner.appendChild(span); inner.appendChild(caret); while (inner.children.length > 420) inner.firstChild.remove(); } function tickerIdle() { caret.remove(); els.voiceDot.classList.remove("is-live"); } function tickerLive(wishText) { els.voiceDot.classList.add("is-live"); els.voiceWish.textContent = wishText ? `“${wishText}”` : ""; } let epitaphTimer = null; function showEpitaph(text, label = "the wish is granted") { els.epitaphText.textContent = text; els.epitaphLabel.textContent = label; els.epitaph.classList.add("is-shown"); clearTimeout(epitaphTimer); // hold long enough for the eye to find the "descend ↓" affordance epitaphTimer = setTimeout(() => { els.epitaph.classList.remove("is-shown"); if (els.epitaphDescend) els.epitaphDescend.hidden = true; }, 9000); } function setEpoch(epoch, granted = epoch) { els.epoch.textContent = `Epoch ${epoch} · ${granted} ${granted === 1 ? "wish" : "wishes"} granted`; } function setQueue(length, mine = null) { if (mine != null) { els.queueChip.hidden = false; els.queueChip.textContent = `your wish is ${ordinal(mine)} in line`; } else if (length > 0) { els.queueChip.hidden = false; els.queueChip.textContent = `${length} ${length === 1 ? "wish waits" : "wishes wait"}`; } else { els.queueChip.hidden = true; } } function fmtCall(call) { const a = call.args || {}; const bits = []; if (a.kind) bits.push(a.kind); if (a.palette) bits.push(a.palette); if (a.lat != null) bits.push(`${(+a.lat).toFixed(0)}°, ${(+a.lon).toFixed(0)}°`); if (a.path) bits.push(`${a.path.length} waypoints`); if (a.radius_deg != null) bits.push(`r ${(+a.radius_deg).toFixed(0)}°`); if (a.height != null) bits.push(`h ${a.height}`); if (a.depth != null) bits.push(`d ${a.depth}`); if (a.intensity != null) bits.push(`i ${a.intensity}`); if (a.moons != null) bits.push(`${a.moons} moon${a.moons === 1 ? "" : "s"}`); if (a.style) bits.push(a.style); return `\n⟡ ${call.tool} · ${bits.join(" · ")}\n`; } function callTarget(call) { const a = call.args || {}; if (call.tool === "set_sky" || call.tool === "set_weather") return null; if (call.tool === "inscribe_wish") return null; // delta will refine (stone seats by seed) if (a.path && a.path.length) return latLonToDir(a.path[0][0], a.path[0][1]).multiplyScalar(PLANET_R); if (a.lat != null && a.lon != null) return latLonToDir(a.lat, a.lon).multiplyScalar(PLANET_R); return null; } // The direction on the surface a feature landed at (for the descent target). // Sky/weather have no place, so they don't count toward "where to visit". function featureDir(feature) { const a = feature?.args || {}; if (feature?.tool === "set_sky" || feature?.tool === "set_weather") return null; if (a.path && a.path.length) return latLonToDir(a.path[0][0], a.path[0][1]); if (a.lat != null && a.lon != null) return latLonToDir(a.lat, a.lon); return null; } // "descend ↓" affordance on the epitaph card → swoop down over the wish site. function armDescent(dir) { if (!els.epitaphDescend) return; if (dir) { lastDescentDir = dir.clone(); els.epitaphDescend.hidden = false; } else if (!lastDescentDir) { els.epitaphDescend.hidden = true; } } // ---------------------------------------------------------------- boot let app; try { app = createPlanetApp(document.getElementById("scene"), { interactive: true }); } catch (err) { els.status.textContent = "this vessel cannot hold the world (WebGL unavailable)"; console.error(err); throw err; } // last surface direction a wish landed at — the descent target. let lastDescentDir = null; if (els.epitaphDescend) { els.epitaphDescend.addEventListener("click", () => { if (!lastDescentDir) return; app.rig.setTerrain?.(app.world.terrain); // clear live terrain analytically app.rig.descend(lastDescentDir.clone().multiplyScalar(PLANET_R)); }); } // Re-anchor the town heart from the current world (camera follows it as it // densifies). Called on every state load + world_delta. function syncTown() { app.rig.setTownCenter(app.world.features); } // "behold the world ↕" — pull back to the full planet; "↻ return to the town" — // drop back into the close town view. Reflects the rig's current mode. function updateBeholdLabel() { if (!els.behold) return; const world = app.rig.mode === "world"; els.behold.classList.toggle("is-world", world); els.behold.setAttribute("aria-pressed", world ? "true" : "false"); els.behold.querySelector(".behold__label").textContent = world ? "return to the town" : "behold the world"; } if (els.behold) { els.behold.addEventListener("click", () => { app.rig.beholdWorld(); updateBeholdLabel(); }); updateBeholdLabel(); } const featuresByWish = (features, upto) => { if (upto == null) return features; const order = []; const seen = new Set(); for (const f of features) { if (!seen.has(f.wish_id)) { seen.add(f.wish_id); order.push(f.wish_id); } } const keep = new Set(order.slice(0, upto + 1)); // genesis is index 0 return features.filter((f) => keep.has(f.wish_id)); }; function applyDevOverrides() { if (params.has("sky")) { app.world.sky.set(params.get("sky"), parseFloat(params.get("stars") ?? "0.8"), parseInt(params.get("moons") ?? "1", 10), { animate: false }); } if (params.has("weather")) { app.world.weather.set(params.get("weather"), parseFloat(params.get("wi") ?? "0.6"), { animate: false }); } } function countWishes(features) { const s = new Set(); for (const f of features) if (f.wish_id !== "genesis") s.add(f.wish_id); return s.size; } // ---------------------------------------------------------------- live mode async function bootLive() { let stateVersion = null; let readingPhase = true; async function loadState() { const res = await fetch("/api/state"); if (!res.ok) throw new Error(`state ${res.status}`); const state = await res.json(); stateVersion = state.world?.version ?? null; app.world.reset(state.world?.features ?? [], app.simTime); syncTown(); // anchor the town heart, then frame it (the default close view) const epoch = state.world?.epoch ?? countWishes(state.world?.features ?? []); setEpoch(epoch, epoch); setQueue(state.queue?.length ?? 0); return state; } try { await loadState(); els.status.textContent = "live · the god is listening"; } catch (err) { console.warn("no live backend:", err); els.status.textContent = "the god is dreaming · demo vision"; return bootMock(); // graceful fall-back to the mock world } // Browser-invoked granting (ZeroGPU): the GPU rite must run inside OUR // gradio request so the HF auth headers carry the quota. When the SSE says // it's our wish's turn, call the named gradio endpoint with the one-time token. const myWishes = new Set(); // your_turn can arrive BEFORE the POST response registers our wish_id (an // empty queue turns to a wish instantly) — buffer unmatched turns and replay // them the moment the wish registers. const pendingTurns = new Set(); let gradioClient = null; async function invokeGrant(wishId) { els.status.textContent = "the god turns to your wish…"; try { // Fetch our secret token from the cookie-authenticated route — it is // never broadcast over SSE, so only the wish's owner can begin the rite. const tr = await fetch(`/api/turn?wish_id=${encodeURIComponent(wishId)}`, { credentials: "same-origin", }); if (!tr.ok) { if (tr.status === 403) return; // not our turn (a stranger's wish) throw new Error(`turn ${tr.status}`); } const { token } = await tr.json(); if (!gradioClient) { const { Client } = await import("https://cdn.jsdelivr.net/npm/@gradio/client/+esm"); gradioClient = await Client.connect(window.location.origin); } const r = await gradioClient.predict("/grant", { wish_id: wishId, token }); const out = Array.isArray(r?.data) ? r.data[0] : r?.data; if (out && out.ok === false && out.reason) hint(out.reason, { warn: true }); } catch (err) { console.error("grant invocation failed", err); hint("the rite could not begin — sign in to hugging face, then wish again", { warn: true }); } } const es = new EventSource("/api/stream"); es.onmessage = (e) => { let m; try { m = JSON.parse(e.data); } catch { return; } switch (m.type) { case "your_turn": if (myWishes.has(m.wish_id)) invokeGrant(m.wish_id); else pendingTurns.add(m.wish_id); break; case "hello": if (stateVersion != null && m.world_version !== stateVersion) loadState().catch(() => {}); if (m.epoch != null) setEpoch(m.epoch, m.epoch); break; case "queue": setQueue(m.length ?? 0); if (m.current?.text_preview) tickerLive(m.current.text_preview); break; case "wish_started": tickerClear(); readingPhase = true; tickerLive(m.text); lastDescentDir = null; if (els.epitaphDescend) els.epitaphDescend.hidden = true; break; case "thought_token": tickerPush(m.text, readingPhase ? "reading" : "thought"); break; case "tool_call": { readingPhase = false; tickerPush(fmtCall(m), "call"); app.rig.gaze(callTarget(m), { hold: 5 }); break; } case "world_delta": { if (m.feature) { stateVersion = stateVersion == null ? null : stateVersion + 1; const cue = app.world.applyFeature(m.feature, { animate: true, t: app.simTime }); // a new building shifts the town's heart — re-anchor before framing so // the camera follows the town as it densifies (no-op for sky/weather). if (m.feature.tool === "place_structure" || m.feature.tool === "build_district") { syncTown(); } // gaze toward where the world is actually changing — the cue target is // the resolved surface point (refines inscriptions/structures by seed). // In TOWN mode gaze() keeps this WITHIN the town zoom (never the far limb). if (cue.target) app.rig.gaze(cue.target.clone().multiplyScalar(PLANET_R), { hold: 5.5 }); else if (cue.wide) app.rig.gaze(null, { hold: 4 }); const dir = cue.target || featureDir(m.feature); if (dir) lastDescentDir = dir.clone().normalize(); } break; } case "wish_granted": showEpitaph(m.epitaph || "it is made"); if (m.epoch != null) setEpoch(m.epoch, m.epoch); app.world.sky.flash(0.35); armDescent(lastDescentDir); // hold a beat on the wish site, THEN release to the wide shot if (lastDescentDir && !app.rig.descending) { app.rig.gaze(lastDescentDir.clone().multiplyScalar(PLANET_R), { hold: 3.6 }); } else { app.rig.release(); } tickerIdle(); els.status.textContent = "live · the god is listening"; break; case "wish_rejected": hint(m.reason || "the god declines this wish.", { warn: true }); tickerIdle(); break; case "heartbeat": break; } }; es.onerror = () => { els.status.textContent = "the link to the god flickers · reconnecting"; }; es.addEventListener("open", () => { els.status.textContent = "live · the god is listening"; }); // wish submission els.form.addEventListener("submit", async (e) => { e.preventDefault(); const text = els.input.value.trim(); if (!text) return; els.send.disabled = true; try { const res = await fetch("/api/wish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text }), }); const body = await res.json().catch(() => ({})); if (res.ok) { els.input.value = ""; if (body.wish_id) { myWishes.add(body.wish_id); if (pendingTurns.has(body.wish_id)) { pendingTurns.delete(body.wish_id); invokeGrant(body.wish_id); } } setQueue(body.position ?? 1, body.position ?? 1); hint("the god has heard you — stay; your presence completes the rite", { revert: 6000 }); } else { hint(body.reason || body.detail || "the god declines this wish.", { warn: true }); } } catch { hint("the offering was lost — try again", { warn: true }); } finally { els.send.disabled = false; } }); } // ---------------------------------------------------------------- mock mode let mockApi = {}; async function bootMock() { const [state, { FEED_WISH }] = await Promise.all([ fetch("./mock/world.json").then((r) => r.json()), import("./mock/feed.js"), ]); const baseFeatures = featuresByWish(state.world.features, UPTO); const baseEpoch = countWishes(baseFeatures); app.world.reset(baseFeatures, app.simTime); syncTown(); // anchor + frame the town heart (the default close view) applyDevOverrides(); setEpoch(baseEpoch, baseEpoch); els.status.textContent = MOCK ? "demo vision · ?mock=1" : "the god is dreaming · demo vision"; const looping = !STATIC && UPTO == null; // -- looping scripted feed: one full wish, replayed forever ------------ const runner = new ScriptRunner({ started: (e) => { tickerClear(); tickerLive(e.text); }, phase: () => {}, token: (e) => { const tone = e.text.startsWith("\n›") ? "obs" : phase === "reading" ? "reading" : "thought"; tickerPush(e.text, tone); }, call: (e) => { phase = "turn"; tickerPush(fmtCall(e.call), "call"); app.rig.gaze(callTarget(e.call), { hold: 4.5 }); }, delta: (e) => { const cue = app.world.applyFeature(e.feature, { animate: true, t: app.simTime }); if (e.feature.tool === "place_structure" || e.feature.tool === "build_district") { syncTown(); // the town heart follows each new building } if (cue.target) app.rig.gaze(cue.target.clone().multiplyScalar(PLANET_R), { hold: 4.5 }); else if (cue.wide) app.rig.gaze(null, { hold: 3.5 }); const dir = cue.target || featureDir(e.feature); if (dir) lastDescentDir = dir.clone().normalize(); }, granted: (e) => { showEpitaph(e.epitaph); setEpoch(baseEpoch + 1, baseEpoch + 1); app.world.sky.flash(0.35); armDescent(lastDescentDir); if (lastDescentDir && !app.rig.descending) { app.rig.gaze(lastDescentDir.clone().multiplyScalar(PLANET_R), { hold: 3.6 }); } else { app.rig.release(); } tickerIdle(); cooldown = 12; // dwell on the granted world (descend affordance) before reset }, }); let phase = "reading"; let cooldown = 7; // contemplative pause before the first / between loops let pendingWishText = null; function startFeed(customText) { phase = "reading"; lastDescentDir = null; if (els.epitaphDescend) els.epitaphDescend.hidden = true; const trace = customText ? { ...FEED_WISH.trace, text: customText } : FEED_WISH.trace; runner.start(buildWishScript(trace, FEED_WISH.features), app.simTime); } app.onTick((t, dt) => { runner.update(t); if (!looping || runner.active) return; cooldown -= dt; if (cooldown <= 0) { // reset to the base world, then perform the wish again — determinism on display if (app.world.features.length > baseFeatures.length) { app.world.reset(baseFeatures, t); syncTown(); // back to the base town heart before the wish runs again setEpoch(baseEpoch, baseEpoch); tickerClear(); tickerIdle(); els.voiceWish.textContent = ""; } startFeed(pendingWishText); if (pendingWishText) { setQueue(0); pendingWishText = null; } cooldown = 10; } }); if (looping) { els.form.addEventListener("submit", (e) => { e.preventDefault(); const text = els.input.value.trim(); if (!text) return; pendingWishText = text.slice(0, 140); els.input.value = ""; setQueue(0, 1); hint("demo vision — the god will dream your words", { revert: 7000 }); }); } mockApi.runFeedOnce = startFeed; mockApi.resetTo = (upto) => { runner.cancel(); const feats = featuresByWish(state.world.features, upto ?? null); app.world.reset(feats, app.simTime); setEpoch(countWishes(feats), countWishes(feats)); }; } // ---------------------------------------------------------------- start const ready = (MOCK ? bootMock() : bootLive()) .then(() => { window.__godseed.ready = true; }) .catch((err) => { els.status.textContent = "the vision failed to load"; console.error(err); }); app.start(); // status heartbeat (epoch + sky, quiet diagnostics) setInterval(() => { const s = app.stats(); // base = everything that isn't a previous " sky" suffix (the status // text varies between 1 and 2 segments; a fixed slice duplicated the suffix) const base = els.status.textContent .split(" · ") .filter((p) => !p.trim().endsWith(" sky")) .join(" · "); els.status.textContent = `${base} · ${s.sky} sky`; }, 4000); // ---------------------------------------------------------------- harness API window.__godseed = { ready: false, app, get world() { return app.world; }, get rig() { return app.rig; }, tick: (seconds) => app.tick(seconds), seek: (t) => app.seek(t), pause: (v) => app.pause(v), stats: () => app.stats(), gazeLatLon: (lat, lon, hold = 30) => { app.rig.userHold = 0; app.rig.gaze(latLonToDir(lat, lon).multiplyScalar(PLANET_R), { hold }); }, gazeWide: () => { app.rig.setMode("world"); updateBeholdLabel(); }, setMode: (mode) => { app.rig.setMode(mode); updateBeholdLabel(); }, beholdWorld: () => { const m = app.rig.beholdWorld(); updateBeholdLabel(); return m; }, townCenter: () => townCenter(app.world.features), descend: (lat, lon, duration = 13) => { app.rig.setTerrain?.(app.world.terrain); app.rig.descend(latLonToDir(lat, lon).multiplyScalar(PLANET_R), { duration }); }, descendLast: (duration = 13) => { if (!lastDescentDir) return false; app.rig.setTerrain?.(app.world.terrain); app.rig.descend(lastDescentDir.clone().multiplyScalar(PLANET_R), { duration }); return true; }, setSky: (p, stars = 0.8, moons = 1) => app.world.sky.set(p, stars, moons, { animate: false }), setWeather: (k, i = 0.6) => app.world.weather.set(k, i, { animate: false }), setCamera: (theta, phi, dist) => { Object.assign(app.rig, { theta, thetaT: theta, phi, phiT: phi, dist: dist ?? app.rig.dist, distT: dist ?? app.rig.distT, }); }, runFeedOnce: (text) => mockApi.runFeedOnce?.(text), resetTo: (n) => mockApi.resetTo?.(n), };