1
0

feat: complete actor handoff and piloted aircraft presence

This commit is contained in:
2026-08-11 22:17:06 -07:00
parent 655c383061
commit 3326d2e6d0
20 changed files with 907 additions and 41 deletions
+27 -3
View File
@@ -37,6 +37,7 @@ export interface RealtimeServiceOptions {
rateWindowMs?: number;
actorMotionLimits?: MotionLimits;
vehicleMotionLimits?: MotionLimits;
aircraftMotionLimits?: MotionLimits;
}
export interface RealtimeJoinInput {
@@ -103,6 +104,7 @@ interface ResolvedOptions {
rateWindowMs: number;
actorMotionLimits: MotionLimits;
vehicleMotionLimits: MotionLimits;
aircraftMotionLimits: MotionLimits;
}
export interface RealtimeService {
@@ -176,6 +178,13 @@ function resolveOptions(value: RealtimeServiceOptions): ResolvedOptions {
positionSlackM: 4,
maximumDeltaSeconds: 2,
},
aircraftMotionLimits: value.aircraftMotionLimits ?? {
maximumHorizontalSpeedMps: 125,
maximumVerticalSpeedMps: 45,
maximumTurnRateDegPerSec: 180,
positionSlackM: 6,
maximumDeltaSeconds: 2,
},
};
}
@@ -208,7 +217,9 @@ function clonePose(pose: EntityPoseSnapshot): EntityPoseSnapshot {
}
function entityKey(pose: EntityPoseSnapshot): string {
return pose.entity === "actor" ? `actor:${pose.actorId}` : `vehicle:${pose.vehicleId}`;
if (pose.entity === "actor") return `actor:${pose.actorId}`;
if (pose.entity === "vehicle") return `vehicle:${pose.vehicleId}`;
return `aircraft:${pose.aircraftId}`;
}
function poseInterestKey(pose: EntityPoseSnapshot): string | null {
@@ -569,8 +580,16 @@ export function createRealtimeService(options: RealtimeServiceOptions = {}): Rea
}
if (
(snapshot.entity === "actor" && snapshot.actorId !== session.actorId) ||
(snapshot.entity === "vehicle" && snapshot.driverActorId !== session.actorId)
(snapshot.entity === "vehicle" && snapshot.driverActorId !== session.actorId) ||
(snapshot.entity === "aircraft" && snapshot.pilotActorId !== session.actorId)
) return { ok: false, code: "ownership", message: "Realtime entity is not owned by this session." };
if (snapshot.entity === "aircraft" && snapshot.pose.space !== "geographic") {
return {
ok: false,
code: "interest",
message: "Aircraft poses require an outdoor geographic interest.",
};
}
const localKey = poseInterestKey(snapshot);
if (localKey !== null && !session.interestKeys.has(localKey)) {
return { ok: false, code: "interest", message: "Realtime pose is outside the joined interest." };
@@ -585,10 +604,15 @@ export function createRealtimeService(options: RealtimeServiceOptions = {}): Rea
const key = entityKey(snapshot);
const previous = session.proposedPoses.get(key);
if (previous) {
const motionLimits = snapshot.entity === "vehicle"
? settings.vehicleMotionLimits
: snapshot.entity === "aircraft"
? settings.aircraftMotionLimits
: settings.actorMotionLimits;
const motion = validateMotion(
previous,
snapshot,
snapshot.entity === "vehicle" ? settings.vehicleMotionLimits : settings.actorMotionLimits,
motionLimits,
);
if (!motion.ok) {
return { ok: false, code: "motion", message: `Realtime motion rejected: ${motion.reason}.` };