LSE is the third of the three, beside lumbridge-compute and lumbridge-bench: a 3D engine for walkable places. This first commit is the outside of the world — San Francisco — plus the seams the inside will attach to. The engine renders a City and a list of Markers and knows nothing else. It does not know markers are usually companies and it will never learn that "rejected" is red; that mapping lives in an adapter. Which is what lets one renderer serve a private map, a public one, and a self-hoster with no Lumbridge account, none of them a fork of the others. Three things were designed around the licence rather than discovered after it, because each one is a promise Apache 2.0 makes that is easy to break by accident. No trademarks in the repo — logos are fetched at runtime, and public/logos/ is gitignored. No OpenStreetMap-derived coordinates, which is why every coastline in cities/sf.ts was traced by hand: Nominatim output is ODbL, share-alike, and would attach to the whole pack. And no FlightRadar24 client — their terms forbid scraping and redistribution, so flights are an interface with a simulator and open community ADS-B behind it. The privacy constraint and the licence constraint turned out to want the same thing. Geocoded company positions and pipeline status both stay behind Workie's API; the open repo holds the city and the renderer. The tempting shortcut — commit an sf-companies.json — breaks both at once. Ported out of Workie, where a 3D city engine had no business living. Workie's /live is deleted rather than deprecated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
13 KiB
Lumbridge Simulate Engine — architecture
LSE is the third of the three: lumbridge-compute, lumbridge-bench,
lumbridge-simulate. A 3D engine for walkable places — a city from above, an
office from inside, one renderer and one asset library.
Ships at lumbridgecorp.com/live. Apache 2.0, self-hostable, Gitea.
1. Why this is its own repo, and why it is public
LSE began as /live inside Workie, the private career portal. That is now
deleted, not deprecated — a 3D city engine has no business living in a job
tracker, and Workie is a private repo besides.
You cannot cleanly Apache-2.0 a subdirectory of a private repo. The licence
attaches to a distribution, and a consumer has to be able to clone the thing the
licence covers. A folder inside lumbridgecorp-website or inside
lumbridge-runtime would mean every release is a manual extraction, and every
extraction is a chance to ship something private by accident. The point of this
project — a reusable asset library other people can build on and self-host — needs
a repo that is the open-source artefact.
So: lumbridge-public/lumbridge-simulate on the fleet's own Gitea. Not
GitHub. Self-hosting the forge is the same claim the engine makes: the whole
thing runs on hardware you own, and nothing about it requires somebody else's
platform to stay alive.
World 1 is this
This is the part that makes LSE load-bearing rather than a demo. The existing Lumbridge world is a Phaser 2D office, and its tileset is what stops it being open-sourceable. World 1 becomes the San Francisco city view plus the office interiors, rendered here, with original assets.
That reframes the whole repo: LSE is not a map that happens to live next to the platform, it is the platform's renderer. The city is the outside of the world and the offices are the inside of it, and a tenant moves between them.
2. Layout
lumbridge-simulate/
├── src/engine/ # renderer. Knows about terrain, blocks, streets, flights.
│ ├── types.ts # City, District, Hill, Marker, FlightSource
│ ├── world.ts # projection + cached heightfield, built per city
│ ├── terrain.ts # relief, coastline, parks
│ ├── blocks.ts # the built city, on a per-district street lattice
│ ├── structures.ts # bridges, streets, freeways
│ ├── markers.ts # pins. Takes Marker[], knows nothing about companies.
│ ├── flights.ts # FlightSource interface + a simulated implementation
│ └── scene.ts # lights, sky, camera flights, render loop
├── src/interiors/ # the inside of the world — floorplans, desks, presence
├── src/assets/ # the library. Original meshes and materials, Apache 2.0.
├── src/cities/ # data packs. Pure geography, no code.
│ ├── sf.ts # ~1000 lines of coastline, hills, districts, landmarks
│ └── la.ts # LA / OC / Riverside
├── src/adapters/ # where outside data plugs in
│ └── workie.ts # Workie API -> Marker[]
└── src/main.ts # the standalone demo app
The split that matters: engine never imports cities, and neither imports
adapters. A city is data handed to the engine. A marker is a {id, lat, lng, label, colorKey} handed to the engine. The engine has no idea Workie exists,
which is what makes it publishable, self-hostable by someone with no Lumbridge
account at all, and free of anyone's private data.
interiors and assets sit beside engine rather than inside it: an office
and a city share the projection, camera, lighting and render loop, and differ
only in what they put in the scene. That is why scene.ts owns the loop and
knows nothing about terrain specifically.
3. Three rules that keep Apache 2.0 honest
Apache 2.0 is a promise that everything in the repo is ours to give away. Three things would quietly break that promise, and all three are easy to walk into.
3.1 No trademarks in the repo
Company logos are trademarks, not code. Salesforce's logo is not ours to
relicense no matter how the file got here, and an Apache-2.0 repo containing
logos/salesforce.svg is making a claim it cannot back.
The NYC atlas ships 47 of these in public/logos/. We do not.
- Logos are fetched at runtime, client-side, and cached in the browser.
- The repo carries a fetch script and zero logo files.
public/logos/is.gitignored, and CI fails if anything lands there. NOTICEcarries the standard "trademarks are the property of their respective owners; their use here is nominative" line.
3.2 No OSM-derived coordinates in the repo
This is the subtle one, and it is the reason the geography in cities/sf.ts is
hand-traced rather than imported.
OpenStreetMap data — including anything that comes out of Nominatim geocoding — is ODbL. ODbL is share-alike: a "derivative database" has to be released under ODbL too. Committing a table of company lat/lngs geocoded from Nominatim into an Apache-2.0 repo mixes an incompatible share-alike obligation into a permissive one. That is exactly the kind of thing that makes a repo unusable for the people we want using it.
So:
- Geography (coastlines, hills, districts) is traced by hand from scratch. Original expression, ours, Apache 2.0. This is already true for SF.
- Elevation, when we want real terrain, comes from USGS/SRTM, which is US-government public domain. Not OSM.
- Geocoded company coordinates never enter this repo. They live in Workie's private database and arrive over the API at runtime.
Note where that lands: the licence constraint and the privacy constraint want
exactly the same thing. Company positions and pipeline status both stay behind
the API; the open-source repo holds the city and the renderer. That is a nice
result and it should be defended, because the tempting shortcut — "just commit a
sf-companies.json" — breaks both at once.
3.3 The engine takes no position on what a marker means
markers.ts renders Marker[]. A marker has a colorKey, not a status. The
mapping from "rejected" to red lives in the adapter, in the consuming app.
That is what lets the same engine serve the private Workie build (coloured by pipeline state), the public Lumbridge demo (coloured by sector), and whatever someone else builds from this repo, without any of them being a fork.
4. Flights: FlightRadar24 is the wrong source for this
Real aircraft over the Bay is a great idea. FR24 specifically is a problem, and it is worth being clear about why before any code is written against it.
FlightRadar24's terms prohibit scraping and prohibit redistributing their data; their commercial API is licensed per-seat and explicitly does not grant redistribution. An Apache-2.0 repo shipping an FR24 client is publishing instructions for violating a ToS, and any FR24 data that reached the browser could not be relicensed onward. Neither is fatal to a private deployment, but both are fatal to the open-source story that is the point of this repo.
The engine therefore takes a FlightSource interface, and the repo ships
only sources it can actually give away:
| source | licence | notes |
|---|---|---|
SimulatedFlights |
ours, Apache 2.0 | great-circle tracks on real SFO/OAK/SJC approach and departure paths. This is what ships first, and it is genuinely enough — the map wants convincing motion, not a spotter's log. |
adsb.lol / airplanes.live |
community, open terms | free, no key, real aircraft. The default "real data" adapter. |
| OpenSky Network | free, non-commercial | fine for a demo, awkward for a commercial product page. |
| your own receiver | no licence at all | an RTL-SDR + dump1090 on any fleet box in the Bay produces first-party ADS-B. Nothing to comply with, and it is very on-brand. |
| FlightRadar24 | commercial, no redistribution | if it is ever wanted, it is a private adapter in the Lumbridge deployment, not in this repo. |
Recommendation: build SimulatedFlights now, adsb.lol next, and treat a
receiver as the eventual real answer.
5. LA is not "SF but more data"
SF is 0.20° × 0.36°. At the 45 m cells the SF heightfield uses, that is 336k lattice points and a 1.0 s build.
LA + Orange County + Riverside is roughly 0.5° × 1.9° — about 14× the area.
The same approach would be 4.6M lattice points and a mesh nobody can load. This
is the one place where the engine as written does not simply extend, and it is
better to know that before writing cities/la.ts than after.
Two changes make it work, and both are cheap now:
- Cell size is per-city, not a constant. LA's basin gets coarse cells; nothing is lost, because LA's relief is mountains at the edges rather than SF's hills every four blocks.
- Cities declare
focusRegions— a handful of boxes (DTLA, Santa Monica, Culver, Irvine, Pasadena) rendered at fine resolution, with the basin between them coarse. This is the LOD story, and it is a per-city data declaration rather than engine machinery.
SF gets one focus region covering the whole city and behaves exactly as it does now. LA gets six. NYC, later, gets Manhattan plus the inner boroughs.
6. How Workie feeds it
Workie stays the system of record for companies. LSE never gets a database.
Workie GET /api/live/markers (private, tailnet) -> status colours
Workie GET /api/public/markers (public, allowlist) -> sector colours
|
v
adapters/workie.ts -> Marker[]
|
v
engine/markers.ts
The public endpoint goes through the same export-site.ts field allowlist
that already guards radar.karti.ai and work.karti.ai — the one that is
fail-closed and aborts a deploy rather than shipping an unknown field. No second
implementation of that gate. That was the argument for keeping /live inside
Workie originally, and it still holds — but only for the data path. The
renderer left; the gate stayed where it was.
Self-hosters get neither endpoint and do not need one: setMarkers() takes an
array, and where it comes from is the deployment's business.
lumbridgecorp.com/live is a static page, so it calls a small
lumbridge-simulate service behind the existing Caddy handle /api/* — the
same pattern lumbridge-intake.service already uses on cloud-2 — for flights
and for proxying the public marker feed.
7. Interiors — and why the asset library is the real product
The existing Lumbridge world is a Phaser 4 2D office, and Karti's read is right: its tileset is the thing that stops it being open-sourceable. Redrawing that in 3D with original assets is not a graphics exercise, it is a licensing unlock — it is what lets the walkable-office half of Lumbridge ship under Apache 2.0 at all.
That makes src/assets the highest-leverage part of this repo over time: an
original, consistent, Apache-2.0 library of desks, chairs, partitions, screens,
doors, lighting rigs, floor and wall materials. Built once, used by every
Lumbridge world and by anyone else who wants one.
Interiors share the engine's projection, camera and render loop, and swap the
city layer for a floorplan layer. Same Marker type — a desk with a person at
it is a marker with a different colorKey. The city view and the office view
are the outside and the inside of one world, and moving between them is a camera
transition, not a different application.
Reference point: the per-office spatial products in this space (Simile and
friends) are the shape to aim at. The difference LSE is going for is that this
one is Apache 2.0 and self-hostable — you can run your own world on your own
hardware, which is the same claim lumbridge-compute makes about compute.
This is the next phase after the city lands, and it is where src/assets starts
earning its keep.
8. Order of work
- Port SF out of Workie into
engine+cities/sf, parameterised by city; delete Workie's/live. (this commit) - Markers + simulated flights. A demo worth showing, with no data pipeline and no licence questions. (this commit)
- Mount at
lumbridgecorp.com/live, with a smalllumbridge-simulateservice behind Caddy for flights. - Interiors + the asset library — the office view, original assets, and World 1 moving off Phaser. This is the one that unlocks the platform.
- Workie adapter, once its geocoding pipeline lands. Workie gets an API, not a renderer.
cities/la— LA / OC / Riverside. NeedsfocusRegionsfrom §5 first.- NYC.
9. Open questions
- Package name.
@lumbridge/simulateimplies an npm publish; consuming straight from Gitea is simpler until someone outside asks for a registry. - Licence for the asset library specifically. Apache 2.0 covers code cleanly; art is sometimes better served by CC0 or CC-BY so it can be reused outside software. Worth deciding before the first mesh lands, because relicensing art after contributors exist is painful.
- How much of World 1 moves at once. The Phaser world is live and has real
tenants; the city view can ship at
/livewell before any office does.