- HTML 81.8%
- CSS 18.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .claude | ||
| docs/research | ||
| prototype | ||
| .gitignore | ||
| BRIEF.md | ||
| gpt6-copy.md | ||
| LICENSE | ||
| README.md | ||
How we built a PS1 dungeon to sell a code-review workflow
Eureka is a Claude Code skill built on one rule: the agent that writes the code does not get to certify that the code works. It needed a landing page. This repo is that page, and it turned into a PlayStation 1 game you scroll through.
Served at https://eureka.gamecult.org/.
Front Door
- Value: the landing page for Eureka, a scroll-driven WebGL dungeon rendered as a PlayStation 1 game, plus MIT-licensed parts you can lift: a PS1 material and dither pass for three.js, a scroll-on-rails camera pattern, and self-contained organ builders.
- Problem: explain a code-review workflow (the agent that writes the code does not certify it) as something you travel through rather than read about.
- Entry points:
prototype/index.html: the copy; each<section data-stop>pins one camera stop.prototype/ps1.js: the reusable PS1 kit (ps1()material,framebuffer());prototype/organs.js: the organ builders;prototype/world.js: the scene andROUTE;prototype/main.js: the scroll timeline.- Run locally:
python -m http.server 8765 --directory prototype. BRIEF.mdandgpt6-copy.md: the two source briefs;docs/research/landing-claims-audit-2026-10-09.md: copy claims labelled CURRENT, STALE or UNVERIFIED against the doctrine.
- Live: the page served at https://eureka.gamecult.org/ from
prototype/; deployment is ingamecult-opsatrunbooks/eureka-prototype-yggdrasil.md. - Planned: nothing recorded in this repo beyond refreshing copy flagged STALE or UNVERIFIED in the audit.
Two briefs, neither in charge
We started with two documents, and both are in this repo.
The first was BRIEF.md. The agent that had been running Eureka on
a live multi-repo campaign wrote it with no guidance from us, because we wanted
the view from the ground. It is good on evidence. It lists real defects, each
caught by a pass that did not write the code, sitting behind a green suite. It
is also confident about things nobody had asked it to decide, such as which
static-site generator to use. We kept the evidence and overruled the rest. The
dated rulings at the top of the file record what was overruled.
The second was gpt6-copy.md, a long copy draft that came out
of a longer conversation about where Eureka and GameCult are going. The page's
text is cut from it, along with its taste for short lines that land one at a
time.
A camera on rails
The reference was acko.net. What we took from it was specific: the page background is a WebGL scene, and scrolling moves the camera through it along a fixed path. Reading the page is the same as travelling through it.
So the page is a route. Every section of text pins one camera stop, and unnamed points in between shape the flight. It begins in a hall where a cracked monument reads ALL TESTS PASSING, in front of a wall of green checkmarks. The next stop looks at a floor hatch stencilled GREEN, which has been pried up. Below the hatch, the route runs:
- Down a shaft into Eureka's organ chamber.
- Through a lattice where Soul's probes flip green nodes to orange.
- Along a gallery of specimen cases: the mutation runner's four false greens, and one empty case.
- Past an archive where the rules keep their evidence.
- Into the vanishing twin's chamber, where Epiphany's scaffolding stands mostly empty.
- Up a second shaft into the hall, where the monument has now split along its crack.
One number owns all of it. Scroll position becomes a path parameter u, and u
drives the camera, the organs, the probe animation, the staccato lines, and the
monument splitting at the end. There is no second clock to drift out of sync.
Going full PlayStation
The first pass rendered at low resolution with a dither, which was tasteful and wrong. Then we found keijiro's Retro3D, a Unity shader of about 20 lines, and went all the way to PlayStation 1. The whole effect comes down to a few tricks, and each one ports to WebGL in a line or two:
- Vertex snapping. The PS1's geometry chip had no sub-pixel precision, so every projected vertex snaps to a 240-line pixel grid. That is where the wobble comes from.
- Affine textures. Texture coordinates are multiplied by depth in the vertex shader and divided back out per pixel. That cancels the GPU's perspective correction and gives the PS1's texture swim. Large floors are split into tiles, as PS1 games did, so the warp looks charming rather than broken.
- 15-bit colour with the console's own 4×4 ordered dither, applied in a final full-screen pass.
- Everything else: lighting computed per vertex, fog to black, no mipmaps, nearest-neighbour filtering, and half-transparent surfaces for glass and Soul's crystal.
Nothing here is a model file. Every texture is drawn to a <canvas> at load,
and every mesh is built in code.
The organs
Eureka's faculties come from Epiphany, the larger system it grew beside and then absorbed. Aquarium already defines how each organ looks, as raymarched distance fields with PBR materials. We demade them:
- Self is a black core inside gold orrery rails.
- Soul is a see-through verdict crystal with a white oath light, blue edges and a red risk seam.
- Hands is a dark tool with three enamel jaws and a hot cutting edge.
- Imagination is an opal seed wrapped in curling ribbon petals.
- Life is a teal nautilus shell carrying an ember and memory beads.
- Eyes is a black-glass lens in blue-metal gimbals, behind spiral aperture petals that narrow as evidence arrives, with cyan evidence sparks.
They sit on Aquarium's own orbit slots, so the camera's circuit around the chamber is the Aquarium layout. The seat where Epiphany kept her Face belongs to the viewer: in Eureka, the operator is a human.
Epiphany's chamber is the vanishing twin: the twin absorbed before birth, whose cells the survivor carries. All eight cages still stand, mostly empty. Eyes has already gone to Eureka; Body and Face rise out of their cages and drift toward her as you pass.
The thing at the far plane
GameCult's mark is a camera aperture, and we wanted it to hang behind the fog as a megastructure. An earlier AI rendering showed the whole thing, evenly lit and front-on, and it read as a medallion. Scale comes from what you can't see. This aperture is a separate sky layer: it follows the camera's rotation and never moves closer. It is tilted so you see it at an angle and held at a fixed depth of fog. Its six blades are habitats (forest, desert, ice, falls, highlands, a night city) around a gold hub, with stations in orbit. Underground it fades to a faint presence. The iris opens as you read.
Small machines that earned their place
- Framing follows the text. Text panels covered the subject in most shots. We didn't retune 26 camera angles. Each section already says which side its panel is on, so the camera shifts its framing toward the other side, eased between stops.
- Stops are checked. The page's sections and the route's stops must match one to one, in order. The page checks this on load and says so in the console when they don't.
- Motion is optional. Without JavaScript, or with reduced motion turned on, it is a readable page of prose. Reduced motion replaces the flight with a still frame per section.
- Verification ran headless. The in-app browser we were using could not
capture the page once it had scrolled, even with rendering paused. Every stop
was checked with headless Chrome driven by puppeteer instead, using the
?debugreadout and the?wp=Njump.
Take the toys
The retro 3D adventure is what makes this page special. Sharing toys is what GameCult is for, so it's all MIT: take any of it.
- The PS1 kit is
prototype/ps1.js, about 240 lines. Only a little of it is specific to this page: the brand palette, and two opt-in effects (nodesfor the lattice probes andcellsfor the wall's re-graded checks) that you can ignore or delete.ps1({ map, color, emissive, rim, alpha, ... })returns one three.js material with vertex snapping, affine textures, per-vertex lighting, rim light, fog and PS1 half-transparency, each switched on by options.framebuffer(renderer)gives you the low-resolution target and the 15-bit dither pass.- There are helpers for canvas textures, tiled floors that keep the texture warp in check, and merging many parts into one draw call.
- Copy it next to three.js, point your meshes at
ps1(), and your scene is now 1997.
- The scroll-on-rails pattern is about 40 lines of
main.js: turn scroll position into a path parameter by interpolating between section anchors, then let everything else read that one number. The framing shift away from text panels is another 10 lines on top. - The organs in
prototype/organs.jsare self-contained builders. Each one returns a{ group, update(t, k) }object you can drop into any three.js scene, as long asps1.jscomes with it.
If you make something with them, we'd love to see it.
Where things live
| File | Owns |
|---|---|
prototype/index.html |
The copy. Each <section data-stop> pins a camera stop. |
prototype/world.js |
The scene, the aperture, and ROUTE. |
prototype/organs.js |
The organ demakes and the twin's scaffolding. |
prototype/ps1.js |
The PS1 material, the framebuffer and dither, and geometry helpers. |
prototype/main.js |
The scroll timeline and the frame loop. |
prototype/vendor/ |
three.js 0.186.1 (MIT). |
Run it locally by serving prototype/ over HTTP, since ES modules won't load
from file://:
python -m http.server 8765 --directory prototype
The Yggdrasil deploy is documented in gamecult-ops, in
runbooks/eureka-prototype-yggdrasil.md.