Skip to content

oikos: the home directory, a room of screens wired to one VCR - #2

Merged
LuxiaSL merged 8 commits into
mainfrom
ccr-5e377bab-j2t5xg
Sep 29, 2026
Merged

LuxiaSL merged 8 commits into
mainfrom
ccr-5e377bab-j2t5xg

Conversation

@LuxiaSL

@LuxiaSL LuxiaSL commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

/oikos (and /~): every part of æthera, and a few places past it, as a CRT in a dark Lain-style room. Every cable runs into one VCR on a plinth in the middle, and a hand-built Windows XP (Luna) desktop sits over it all.

⚠️ Merging to main deploys (.github/workflows/deploy.yml runs on push to main). Nothing here links to /oikos from the blog yet, and / is untouched, so the room is reachable only by URL until someone links it.

What's in it

The room (oikos/src/world/)

  • Nine site screens:
    • æthera's own stand on the floor in an arc around the VCR, a couple of them stacked.
    • parlor and dream_gen hang from wires strung off power lines.
  • Dozens of instanced dead-channel monitors sit in a red haze. Post-processing adds bloom, red halftone specks in the penumbra (Lain's shadows), grain, vignette, and a frame tear on channel change.
  • The VCR has a seven-segment VFD that blinks 12:00, a tape door, and a real cassette.
    • Playing a site folds the directory away and stops the camera by the VCR as the labelled tape goes in.
    • The camera then follows the lit cable to the screen, which changes channel, and only then opens the pane.
    • Open dives the camera through the glass to the site.

The screens are live, not recorded (oikos/src/screens/). Every source costs the site nothing to watch:

screen source
transmissions latest post titles (rendered into the page by oikos.py)
dreams the chronicle's latest keyframe + /api/dreams/status
chronicle real strata tiles and era titles
dreams api re-types the real /api/dreams/status JSON
apeiron real prompts from its own templates.json / components.json
syrinx the visitor's own creature, read from this origin's localStorage
irc the live /ws/irc broadcast, formatted like irc.js
parlor / dream_gen their real board and README diagram

The room never opens /ws/dreams. A viewer socket is what wakes the dreamer's GPU, and a hub left open in a tab would keep it awake all night. Every poll and socket stops while the tab is hidden. Tuning in to dreams frames the real /dreams page, which does open it; hiding the tab tunes out, so a background tab can't hold it.

The XP layer (oikos/src/xp/) is Luna by hand:

  • ~ explorer with every site as a VHS tape.
  • Site panes built on the same canvas as the screen.
  • Taskbar, start menu, tray (a moon lit when the dream is awake), balloons, and the real "Turn off computer" dialog. Turn Off goes to the blog.

Tune in ("▣ Watch it here"): the camera squares up to a screen and the real page is laid over the glass. It sits in an iframe under a CSS CRT layer and stays fully interactive. Eject or Esc steps back out.

Fallbacks

  • With no WebGL the desktop runs alone over black, and the previews still work.
  • With no JS the template's <nav> lists every site. It's also what crawlers read.

Files outside oikos/

  • aethera/api/oikos.py: the route and the site registry. This is the single source for the <nav> and the scene, and the place to add a site.
  • aethera/templates/oikos/viewer.html, plus the router registered in aethera/main.py.
  • aethera/utils/security.py is a security-relevant change. Every page used to answer X-Frame-Options: DENY. Everything except /preview/ now answers Content-Security-Policy: frame-ancestors 'self' + X-Frame-Options: SAMEORIGIN. æthera may frame æthera; other origins still can't clickjack it. Tune-in needs this. /preview/ keeps its admin-panel policy.
  • aethera/static/oikos/: the committed build, as with syrinx and apeiron. stage.jpg is a 512px crop of dreams_bg.png, and mark.png is the moon mark cropped from the logo.
  • aethera/dreams/presence.py: /api/dreams/status polls are now lifecycle-neutral. Before, a poll cancelled the pending GPU shutdown, so any poller (this room, an admin panel) could keep the dreamer awake indefinitely. Tests in tests/test_presence.py.
  • tests/test_oikos.py: 9 tests.
    • The page renders every site, and embeds published posts only.
    • /~ redirects.
    • Every internal href and tune target is a live route.
    • No internal hostnames appear in the registry.
    • The bundle is committed.
    • The framing headers are right (including /preview/ keeping its admin policy).
    • A </script> post title can't break out of the embedded JSON.

Decisions for the owner

  1. The framing change above.
  2. The proxy in front of æthera also answers X-Frame-Options: DENY. Production currently sends it twice. Modern browsers let the app's frame-ancestors override it, but that proxy line should go (or become SAMEORIGIN) so tune-in doesn't lean on that precedence rule. It isn't in this repo.
  3. parlor answers X-Frame-Options: DENY, so it isn't tunable. It would need frame-ancestors https://aetherawi.red first.
  4. The name oikos (οἶκος, household) is easy to change.

Review round

A review pass (correctness, the room and window logic, XP authenticity) and the fixes it led to, as separate commits:

  • 43d3ba6 correctness.
    • A click, Esc or arrow key during the tape animation no longer strands the tape with no pane open.
    • Back after a dive, and tuning out from the Start menu, no longer leave a frozen camera.
    • Right- and middle-click no longer play tapes, and resizing no longer snaps the camera home.
    • A background tab no longer starts polling, and hiding the tab tunes out.
    • The dreams status-poll GPU fix (above).
    • No-JS and failed-bundle fallbacks now lift the boot screen off the <nav>.
  • ee65f3d loom and heimdall removed. The private-network code only heimdall used goes with them. The hanging row is rebalanced to dream_gen −20° and parlor +20°, on channels 8 and 9.
  • 08b72c0 the XP layer behaves like XP.
    • Working menu bars: File, Edit, View (Toolbars, Status Bar, Tiles/Icons/List, Go To), Favorites, Tools, Help ▸ About.
    • Right-click menus on tiles, screens, the VCR, the room, the folder and the taskbar. The system menu opens from the title icon.
    • Inactive windows fade their whole frame. No drop shadow and no open animation. Maximize becomes Restore and drops the frame.
    • Start menu: pinned items bold, All Programs ▸ and Recent Transmissions ▸ cascades, and Log Off + Turn Off in the footer.
    • Esc only dismisses menus and dialogs, and the dialogs are modal.
    • A click in the room deactivates windows. Back and Forward walk a real history.
  • 5336b3d performance.
    • Screens off camera aren't painted or uploaded, and the others run at half rate while one is focused.
    • Quality steps back up after a slow patch.
    • Focused on one screen: painting 1.97 → 0.93 ms/frame, texture uploads ~120 → 57/s.
  • e6b10e4 window resizing from any edge or corner, with XP's size grip. Windows are clamped back on screen when the viewport shrinks.

Testing

  • uv run pytest -q: 45 passed.
  • npm run typecheck / npm run build in oikos/: clean.
  • npm run drive walks through everything a visitor does. It steps the room at a true 30 fps via ?drive because CI has no GPU, and writes stills and a video.
    • Chromium, desktop: hover, orbit, VCR, select, play, tune in, eject, tape out, start menu, Turn Off dialog, arrow keys, typed channel, dive to /syrinx. No page errors.
    • Chromium, phone (--phone): tap a screen, tune in, tap the VCR, tap-tap to play, start menu. No page errors.
  • WebKit renders the room with no errors.
  • Firefox headless here has no WebGL, so it covered the fallback desktop: no errors.
  • Per-frame CPU work is about 2.3 ms to paint all screens and 4–6 ms to issue the render.
  • Not measured: real GPU frame rate (software rendering only here) and iOS Safari.
  • /oikos?contact shows every screen side by side, for reviewing the painters.
  • Review round: scripted Playwright walks of the XP layer (24 checks), the room's right-click and focus (6 checks) and window resizing (7 checks), all passing with no page errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UkALiUc3TzCNEMkPECnaDS


Generated by Claude Code

claude and others added 8 commits September 29, 2026 02:31
/oikos (and /~): every part of æthera, and a few places past it, as a
CRT in a dark room in the Wired, every cable running into a VCR on a
plinth in the middle. A Windows XP (Luna) desktop sits over the room.

Two ways in. The VCR, or the start button, opens ~: every site is a
tape, in Tiles view. Clicking a screen works too. Either way the tape
goes into the VCR, the display reads PLAY and the channel, and that
screen's cable surges. The site's pane then opens, showing the same
canvas its screen is painted on. Open dives through the glass to the
site.

The screens are live wherever it costs the site nothing:
- transmissions shows the latest real titles.
- dreams shows the chronicle's last keyframe plus the monitoring
  status. It never opens /ws/dreams, since a viewer socket wakes the
  GPU.
- chronicle shows its real strata and eras.
- irc is the live broadcast.
- apeiron composes real prompts from its own grammar.
- syrinx shows the visitor's own creature from localStorage.

parlor, dream_gen and loom draw from their own boards, READMEs and
fixtures. heimdall is a labelled replica.

Built like syrinx: a Vite + three.js module whose iife bundle is
committed into aethera/static/oikos. Without WebGL the desktop runs
alone; without JS the template's <nav> lists every site.
/oikos?contact shows every screen on one sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkALiUc3TzCNEMkPECnaDS
A site's pane can now "Watch it here". The camera squares up to the
site's screen and holds still. The real page is laid over the glass: an
iframe under a CSS CRT layer (scanlines, vignette, reflection, roll bar,
power-on line). It is fully alive while the room renders around it:
links navigate, keys and sound work, and syrinx wakes on a click.
Eject, or Esc, steps back out. Any other camera move also steps out
first.

It is a projected 2D rectangle (Room.glassRect), not CSS3D. An iframe
inside a preserve-3d context paints on the glass, but Chrome won't
hit-test into it, so clicks fell through to the canvas.

Framing: every page used to answer X-Frame-Options: DENY. Pages other
than /preview/ now answer frame-ancestors 'self' plus SAMEORIGIN, so
æthera may frame æthera and other origins still can't clickjack it.
The tune flag in oikos.py marks what can be tuned in: æthera's own
pages, with irc in its embed mode. parlor is left out because it
answers DENY.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkALiUc3TzCNEMkPECnaDS
A new harness walks the page through everything a visitor does and
steps the room frame by frame. ?drive stops the room's own loop and
exposes step(n, dt) with a paint/render timing split, so the harness
runs at a true 30 fps on a machine with no GPU. `npm run drive`
covers desktop, `--phone` covers touch, and `--browser firefox|webkit`
runs another engine. Each run writes stills plus tour.webm.

What driving it found:
- A played tape went in behind two windows. The directory and the new
  pane covered the whole room, so the tape entering the VCR, the cable
  surge and the channel change were never seen. Now the directory
  folds to the taskbar while the camera swoops. It stops by the VCR as
  the cassette goes through the door, then follows the lit cable to
  the screen. The screen changes channel, then the pane opens beside
  it. The directory comes back when the pane closes. The choreography
  runs on the room's clock rather than wall-clock timeouts.
- Windows were sized for 1440px wide. They now size to the page, and a
  narrow window folds its task pane away with a container query.
- Orbit was too loose: a small drag swung the view round the side.
  It is slower now and held nearer the front of the arc.
- The tooltip outlived the click that opened the VCR.
- Each screen's light sat so close that its tape label blew out.
- Once you clicked into a tuned-in page, Esc no longer ejected. It
  now listens inside the page (same origin), unless the page used the
  key or a text field has focus.
- On a phone the room was a speck, and a tuned-in page laid out at
  1024 wide was unreadable. The room is framed closer, and the tube's
  page lays out near the glass's own size.
- WebKit showed a black room for seconds while it compiled shaders.
  The programs now compile, and one frame renders, behind the boot
  screen.

Checked in Chromium on desktop and phone (full tours, no page errors),
in WebKit (renders, no errors), and in Firefox without WebGL (the
desktop fallback, no errors). Screens paint in 2.3 ms and the render
issues in 4-6 ms per frame; real GPU time is not measured here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkALiUc3TzCNEMkPECnaDS
Room / play sequence
- play() now resolves true on arrival or false when cancelled (a click,
  Esc, a tile select). Cancelled plays eject the tape, restore the folded
  directory and clear the hash instead of stranding everything.
- Floor clicks and arrow keys are ignored while a tape is going in.
- Only primary-button clicks pick; right/middle click no longer plays.
- tuneOut() hands the camera back (limits + controls); a tune-in called
  off mid-flight lands as a plain focus instead of locking picks.
- pageshow (bfcache) resets leaving/off state and stands the camera up
  from the glass after Back.
- resize() only re-homes the camera when the aspect changes; quality
  steps resize the buffers directly and no longer yank the view.
- quality threshold 1/27s so 30fps-capped browsers aren't downgraded.
- Room construction failure disposes the renderer and removes the canvas;
  the WebGL probe releases its context.

Lifecycle
- Boot doesn't start feeds or the loop in a hidden tab.
- Hiding the tab tunes out, so a framed /dreams can't hold /ws/dreams.
- /api/dreams/status polls no longer cancel the pending GPU shutdown or
  refresh the API-activity timer (presence.py; regression tests added).

Misc
- Esc closes the Turn Off dialog instead of the window behind it.
- Open navigates once; hash writes keep the query string; malformed
  hashes don't abort boot; a stale error dialog is replaced.
- No-JS and failed-bundle fallbacks lift the boot screen off the <nav>;
  boot() failure falls back to the plain directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both go from the registry, the painters, the layout and RELATED. heimdall
was the only site without an href, so the private-network paths it alone
exercised go too: the error box, the Restricted zone, the lock icon and
the href-None branches in pane/explorer/shell/template. Site.href is
now always a string, and a test holds the registry to that.

The hanging row is rebalanced for two: dream_gen at -20°, parlor at +20°,
channels renumbered 8 and 9 so the remote has no gaps, and the power-line
drops moved over the two screens that remain.

Tests: /preview/ keeps its admin framing policy, /oikos and /irc/admin
get frame-ancestors 'self', and a </script> post title can't break out
of the embedded JSON.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Menus
- One shared Luna popup menu (xp/menu.ts): checkmarks, greyed items,
  shortcuts, cascades, arrow-key navigation, closes on pick/outside/Esc.
- Working menu bars (File/Edit/View/Favorites/Tools/Help) on every
  window; moving across the bar while one is open swaps menus. View has
  Toolbars ▸, Status Bar, Tiles/Icons/List, Go To ▸ and Refresh. Help ▸
  About æthera opens an About box.
- Right-click menus on tiles, screens, the VCR, the empty room (desktop),
  the folder's blank space and the taskbar; the system menu opens from
  the title-bar icon or a right-click on the title bar.

Window chrome
- Inactive windows fade the whole frame; their selection goes grey.
- No drop shadow, no scale-in animation (Luna had neither).
- Maximised drops the frame and the button becomes Restore.
- Double-clicking the title icon closes; the drag handler no longer
  captures the pointer away from the icon.

Start menu
- Pinned tapes bold with subtitles, the rest plain; "All Programs ▸"
  cascades every tape; "My Tapes" / "My Recent Transmissions ▸" bold,
  everything else normal weight; larger framed picture; the orange rule
  fades at its ends.
- Footer is Log Off + Turn Off Computer. Log Off gets XP's dialog
  (Switch User greyed) and clears the desk.

Interaction
- Esc only dismisses menus and dialogs, then steps the camera back; it
  no longer closes ordinary windows. Turn Off / Log Off are modal.
- A click in the room deactivates every window.
- Back/Forward (toolbar, Go To, Alt+←/→) walk a real history of ~ and
  the tapes played.
- Blank space in the folder deselects; back/forward icons stay inside
  their circles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Screens outside the camera frustum are neither painted nor uploaded.
  The focused screen and the one whose pane is open (keepAlive: the pane
  shows its canvas) always are.
- With a screen focused, the others tick at half rate.
- Quality steps back up after three quick windows in a row, so one slow
  moment no longer costs the whole visit its pixel ratio and bloom.
- ?drive reports texture uploads alongside paint/render time.

Measured with ?drive (software GL, 1280×800), focused on one screen:
screen painting 1.97 → 0.93 ms/frame, texture uploads ~120 → 57 per
second. The home view is unchanged: every screen really is on camera.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ordinary windows resize from any edge or corner, with the right
  cursors and XP's dotted size grip in the status bar corner; minimum
  300×180. Dialogs, maximised windows and phone sheets don't resize.
- Windows are clamped back on screen when the viewport shrinks (a
  rotated phone could strand one offscreen).
- Tile text shrinks to its column, so a long tagline ends in an
  ellipsis instead of running into the next tile.
- Rebuilt bundle (includes the previous commit's changes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LuxiaSL
LuxiaSL merged commit 3c07da8 into main Sep 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants