From 5bc72587536791e2b8b2a25be3f6eeee45388d20 Mon Sep 17 00:00:00 2001 From: Kartios Date: Wed, 5 Aug 2026 22:53:30 -0700 Subject: [PATCH] A plan view in the corner, a night you can actually see, and three kinds of visitor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The right half of the screen was empty sky. It holds the board now, drawn flat, with the footprint of the camera's own frustum on it — the one part of a minimap that earns its place, because it answers "where am I looking from" without leaving the shot. Click it, drag it, scroll it. It is a 2D canvas rather than a second WebGL context, cached per city and redrawn only when something moved. Night was black. Not dark — black: at 3 a.m. the coastline, the hills and the bay were one shape, and the frame read as a failed render rather than as darkness. The sky already had a floor for exactly this reason and nothing did the equivalent for the ground, so the ground has one now. The moon still has to be worth computing, so the gap between a moonlit night and a moonless one is preserved rather than filled in. Three tiers, resolved once in the new src/access.ts: anonymous, signed in, admin. Anonymous gets the map and a public office — the shell, the furniture, the named viewpoints, nobody home — built without the private objects rather than with them hidden, because scene.traverse makes hiding a leak with a bow on it. The time scrubber and the debug readouts are admin only, and admin is granted by TERA_ADMIN_SUBJECTS on the server and inferred nowhere else. An unreachable API means member, never god: the promise is "clone it and it works", not "clone it and you are an administrator of a deployment you did not configure". Three things this run found and fixed rather than shipped: - entryUrl came off the wire and went straight into an href with no scheme check, and a CSP of script-src 'self' 'unsafe-inline' does not stop a javascript: URL from navigating. One rejection point in access.ts now. - A 5xx from /health was the same null as "no API at all" and therefore the opposite conclusion. Eight seconds of tera-api restarting would have told every anonymous visitor they were a member. A 5xx is an answer; it fails closed. - decodeURIComponent in cookieToken was the one path in auth/index.ts that threw rather than returning ANONYMOUS, so one malformed cookie header from an unauthenticated caller turned /api/v1/session into a 500. Also: keyboard shortcuts, focus rings, a boot state instead of a blank 2.3 seconds, a collapsible panel under 900px, and no horizontal overflow at 375, 768, 1440 or 2560. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 27 +- deploy/STATIC.md | 17 +- index.html | 660 ++++++++++++++-- public/login.html => login.html | 66 +- server/README.md | 45 ++ server/src/auth/index.ts | 69 +- server/src/config.ts | 84 ++ server/src/routes/session.ts | 47 +- server/src/test/boot.test.ts | 3 + server/src/test/demotion.test.ts | 20 + server/src/test/session.test.ts | 130 ++++ src/access.ts | 301 ++++++++ src/engine/atmosphere.ts | 269 +++++-- src/engine/minimap.ts | 1246 ++++++++++++++++++++++++++++++ src/engine/nightlights.ts | 46 +- src/interiors/officeScene.ts | 216 +++++- src/interiors/plan.ts | 68 ++ src/interiors/presence.ts | 15 + src/interiors/types.ts | 91 +++ src/main.ts | 550 +++++++++++-- src/offices/lumbridge-hq.ts | 94 ++- src/session.ts | 101 +++ src/vite-env.d.ts | 18 + vite.config.ts | 10 +- 24 files changed, 3982 insertions(+), 211 deletions(-) rename public/login.html => login.html (68%) create mode 100644 src/access.ts create mode 100644 src/engine/minimap.ts create mode 100644 src/session.ts create mode 100644 src/vite-env.d.ts diff --git a/README.md b/README.md index 6371b52..403de44 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,31 @@ a city is a data contribution anyone can review, not a fork. San Francisco ships today. Los Angeles / Orange County / Riverside is next; New York after that. +A **plan view** sits top right: the board drawn flat, with the footprint of the +camera's own frustum on it, so you can see where you are looking from outside +the shot. Click or drag it to move the camera; scroll it to dolly. It is a 2D +canvas rather than a second WebGL context, drawn from the same city pack, and it +follows the sun into the night along with everything else. + +## Who sees what + +Three tiers, resolved once at boot by `src/access.ts`: + +| | anonymous | signed in | admin | +|---|---|---|---| +| the map, the plan view, the named chapters | ✅ | ✅ | ✅ | +| the office | public depth — shell, furniture, viewpoints, nobody home | full depth, with presence | full depth | +| live markers and live traffic | — | ✅ | ✅ | +| the time scrubber and debug readouts | — | — | ✅ | + +**These are drawing decisions, not a security boundary**, and `src/access.ts` +says so at length. Live data and office presence are withheld by the *API*, from +a caller it does not recognise; the client tier stops the app asking for +something it will not get. Admin is granted only by `TERA_ADMIN_SUBJECTS` on the +server — never inferred in the browser, and never from an API that failed to +answer. A deployment with no API at all is open, because "clone it and it works" +is the promise; it is not "clone it and you are an administrator". + ## Quick start ```bash @@ -76,7 +101,7 @@ answer is an RTL-SDR receiver: first-party data with nothing to comply with. ## Layout ``` -src/engine/ renderer — terrain, blocks, structures, markers, flights, scene +src/engine/ renderer — terrain, blocks, structures, markers, flights, scene, minimap src/cities/ data packs — pure geography, no code src/adapters/ where outside data plugs in ``` diff --git a/deploy/STATIC.md b/deploy/STATIC.md index b79e41a..a2e8396 100644 --- a/deploy/STATIC.md +++ b/deploy/STATIC.md @@ -1,9 +1,18 @@ # Hosting the static build -Tera's map view is a static bundle. It makes **no network calls at all** — the -sun is computed locally by `src/engine/solar.ts`, the traffic is simulated, and -the office is a data file — so it needs a file server and nothing else. No API, -no keys, no account. +Tera's map view is a static bundle that needs a file server and nothing else. +No API, no keys, no account: the sun and moon are computed locally by +`src/engine/solar.ts`, the traffic is simulated, the minimap is drawn from the +city pack, and the office is a data file. + +It does make **two** requests at boot, and both are meant to fail on a plain +static host: `GET /api/v1/health` and `GET /api/v1/session`, which is how +`src/access.ts` works out whether this deployment has accounts at all. Nothing +answering means nothing to sign in to, so the visitor gets the full public +experience and no sign-in link — see that file's header for why an *unreachable* +API and an API that answered `5xx` are deliberately not the same case. Your +server log will show two 404s per load; that is the zero-config path working, +not a misconfiguration. ```bash npm ci diff --git a/index.html b/index.html index 848e554..1fbb392 100644 --- a/index.html +++ b/index.html @@ -3,75 +3,629 @@ + + + Lumbridge Simulate — San Francisco - + + + +
-
+

San Francisco

Tera · Lumbridge Simulate

-

-
- +

+ -
- +
+ - + +

- + + + +
+ +
+ 19 chapters + [ ] city + O office + M plan + +
+
+

-

drag to orbit · scroll to zoom

+ + + +
+
+

Tera

+

Lumbridge Simulate · cities from above

+
+

Starting up

+

Runs with no server, no key and no account. The markers are + fabricated — no real company data ships in this build.

+
+
+ diff --git a/public/login.html b/login.html similarity index 68% rename from public/login.html rename to login.html index 5a7812f..cbfb3df 100644 --- a/public/login.html +++ b/login.html @@ -42,7 +42,7 @@

Tera · sign in to reach a private office.

- + @@ -54,14 +54,42 @@

-
The session is a cookie this server signs. Nothing leaves the box.
+
The session is a cookie this server signs. Nothing leaves the box.