1
0

feat: add authenticated realtime presence

This commit is contained in:
2026-08-11 20:22:12 -07:00
parent 16dc85a6f8
commit 92ebf8abb5
20 changed files with 3746 additions and 1 deletions
+44
View File
@@ -78,6 +78,10 @@ import type {
AircraftActionSnapshot,
AircraftControllerSnapshot,
} from "../aircraft/controller.ts";
import type { ScenePeers, ScenePeersOptions } from "../realtime/scenePeers.ts";
import type { EntityPoseSnapshot } from "../realtime/types.ts";
export type CityRealtimePeersOptions = Omit<ScenePeersOptions, "project" | "groundAt">;
export interface SceneOptions {
city: City;
@@ -99,6 +103,8 @@ export interface SceneOptions {
actorAnchor?: { lat: number; lng: number };
/** Optional possessed fixed-wing aircraft; the city supplies its projection and terrain. */
aircraft?: Omit<SceneAircraftOptions, "project" | "groundAt">;
/** Optional remote authoritative entities. Nothing is imported or allocated when absent. */
realtimePeers?: CityRealtimePeersOptions;
flights?: FlightSource;
/**
* Element sets to propagate, if this deployment has any.
@@ -205,6 +211,11 @@ export interface SceneHandle {
aircraftState(): Readonly<AircraftControllerSnapshot> | null;
setAircraftActive(active: boolean): void;
aircraftActive(): boolean;
/** Canonical remote snapshots only; a no-op when realtime peers were not configured. */
upsertRemoteSnapshot(snapshot: EntityPoseSnapshot): boolean;
removeRemoteEntity(id: string): boolean;
clearRemoteEntities(): void;
remoteEntityCount(): number;
setMarkers(markers: Marker[]): void;
/** Take this city off the stage and release everything it built. */
dispose(): void;
@@ -401,6 +412,23 @@ export async function createScene(
})
: null;
if (sceneAircraft) scene.add(sceneAircraft.root);
// Dynamic on purpose: the offline/default entry must not download four peer
// prototypes merely because `createScene` can optionally host them.
let realtimePeers: ScenePeers | null = null;
if (options.realtimePeers) {
const { createScenePeers } = await import("../realtime/scenePeers.ts");
realtimePeers = createScenePeers({
...options.realtimePeers,
project: (lat, lng) => world.project(lat, lng),
groundAt: (lat, lng) => world.groundAt(lat, lng),
geographicSceneUnitsPerMetre:
options.realtimePeers.geographicSceneUnitsPerMetre ?? 1 / world.metresPerUnit,
geographicVisualScale:
options.realtimePeers.geographicVisualScale ??
(city.id === "california" ? 0.025 : 1 / world.metresPerUnit),
});
scene.add(realtimePeers.root);
}
let flightLayer: FlightLayer | null = null;
let flightTimer = 0;
@@ -521,6 +549,7 @@ export async function createScene(
if (actorPlaying && sceneActor) kit.setPose(sceneActor.followPose());
sceneAircraft?.tick(dt);
if (aircraftPlaying && sceneAircraft) kit.setPose(sceneAircraft.followPose());
realtimePeers?.tick(Date.now());
roadTraffic?.tick(dt);
clouds.tick(dt);
if (options.flights && flightLayer) {
@@ -572,6 +601,7 @@ export async function createScene(
markerLayer.dispose();
roadTraffic?.dispose();
sceneAircraft?.dispose();
realtimePeers?.dispose();
kit.dispose();
scene.traverse((obj) => {
const mesh = obj as THREE.Mesh;
@@ -644,12 +674,26 @@ export async function createScene(
kit.camera.updateProjectionMatrix();
},
aircraftActive: () => sceneAircraft?.active() ?? false,
upsertRemoteSnapshot: (snapshot) => {
if (snapshot.pose.space === "local") {
// Local office coordinates have no meaning on a geographic board.
// Local city coordinates are accepted only for this detailed board;
// California actors use geographic poses.
const expected = city.id === "sf" ? "bay-area" : city.id === "socal" ? "socal" : null;
if (snapshot.pose.cell.kind !== "city" || snapshot.pose.cell.cityId !== expected) return false;
}
return realtimePeers?.upsert(snapshot) ?? false;
},
removeRemoteEntity: (id) => realtimePeers?.remove(id) ?? false,
clearRemoteEntities: () => { realtimePeers?.clear(); },
remoteEntityCount: () => realtimePeers?.count() ?? 0,
setMarkers(markers) {
markerLayer.setMarkers(markers);
},
dispose() {
sceneActor?.dispose();
sceneAircraft?.dispose();
realtimePeers?.dispose();
/**
* Off the stage, then released — and the stage itself is left running.
*