feat: complete actor handoff and piloted aircraft presence
This commit is contained in:
@@ -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}.` };
|
||||
|
||||
Reference in New Issue
Block a user