fix: outdoors is one California, not three boards the player travels between
Journey location was `california | bay-area | socal | office`. Entering HQ required a metro scale, leaving restored that metro, and finishing a Drive route called `switchCity`. On the merged board those events dumped you onto `?city=sf` while the world you had been driving was still California. Outdoors is California. An office is the other scale. Leaving it returns you to California; arriving at San Francisco flies the authored pose on this board. `?city=sf` remains a capture renderer. Old sessions that stored a metro scale coerce to California. `navigate-to-city` / `return-to-california` stay as events so leftover callers compile; they reject with `already-there`.
This commit is contained in:
+38
-28
@@ -1,6 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Pure journey state machine spanning corridor, city boards, vehicles, and offices.
|
* Pure journey state machine spanning the California world, vehicles, and offices.
|
||||||
* No render or browser types belong here; the same reducer can run on a server.
|
* No render or browser types belong here; the same reducer can run on a server.
|
||||||
|
*
|
||||||
|
* Outdoors is always `california`. `navigate-to-city` / `return-to-california`
|
||||||
|
* are kept so old sessions and `?one=0` callers still compile; they cannot
|
||||||
|
* move the actor onto a metro board, because that board is not a place.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -69,15 +73,24 @@ function isOfficeId(value: unknown): value is JourneyOfficeId {
|
|||||||
|
|
||||||
function isLocation(value: unknown): value is JourneyLocation {
|
function isLocation(value: unknown): value is JourneyLocation {
|
||||||
if (!isRecord(value)) return false;
|
if (!isRecord(value)) return false;
|
||||||
if (value.scale === "california" || value.scale === "bay-area" || value.scale === "socal") {
|
if (value.scale === "california") return true;
|
||||||
return true;
|
return value.scale === "office" && isOfficeId(value.officeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V1 sessions written when Bay Area and SoCal were journey scales.
|
||||||
|
*
|
||||||
|
* Those scales are geography now. A stored `bay-area` / `socal` is California;
|
||||||
|
* an office's `priorCity` is dropped because leaving an office always returns
|
||||||
|
* to California. Unknown office ids still fail closed.
|
||||||
|
*/
|
||||||
|
function coerceLocation(value: unknown): unknown {
|
||||||
|
if (!isRecord(value)) return value;
|
||||||
|
if (value.scale === "bay-area" || value.scale === "socal") return { scale: "california" };
|
||||||
|
if (value.scale === "office" && isOfficeId(value.officeId)) {
|
||||||
|
return { scale: "office", officeId: value.officeId };
|
||||||
}
|
}
|
||||||
return (
|
return value;
|
||||||
value.scale === "office" &&
|
|
||||||
isOfficeId(value.officeId) &&
|
|
||||||
(value.priorCity === "bay-area" || value.priorCity === "socal") &&
|
|
||||||
OFFICES[value.officeId] === value.priorCity
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Runtime validation at persistence and multiplayer trust boundaries. */
|
/** Runtime validation at persistence and multiplayer trust boundaries. */
|
||||||
@@ -121,10 +134,8 @@ function accept(state: JourneyState): JourneyTransition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function routeStartFor(location: JourneyLocation, direction: 1 | -1): number | null {
|
function routeStartFor(location: JourneyLocation, direction: 1 | -1): number | null {
|
||||||
if (location.scale === "socal") return direction === 1 ? 0 : null;
|
if (location.scale !== "california") return null;
|
||||||
if (location.scale === "bay-area") return direction === -1 ? 1 : null;
|
return direction === 1 ? 0 : 1;
|
||||||
if (location.scale === "california") return direction === 1 ? 0 : 1;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,18 +159,14 @@ export function transitionJourney(state: JourneyState, event: JourneyEvent): Jou
|
|||||||
return reject(state, "invalid-event");
|
return reject(state, "invalid-event");
|
||||||
}
|
}
|
||||||
if (state.location.scale === "office") return reject(state, "leave-office-first");
|
if (state.location.scale === "office") return reject(state, "leave-office-first");
|
||||||
if (state.location.scale !== "california") {
|
|
||||||
return reject(state, state.location.scale === event.city ? "already-there" : "wrong-scale");
|
|
||||||
}
|
|
||||||
if (state.vehicle) return reject(state, "exit-vehicle-first");
|
if (state.vehicle) return reject(state, "exit-vehicle-first");
|
||||||
return accept({ ...state, location: { scale: event.city } });
|
return reject(state, "already-there");
|
||||||
}
|
}
|
||||||
|
|
||||||
case "return-to-california": {
|
case "return-to-california": {
|
||||||
if (state.location.scale === "office") return reject(state, "leave-office-first");
|
if (state.location.scale === "office") return reject(state, "leave-office-first");
|
||||||
if (state.location.scale === "california") return reject(state, "already-there");
|
|
||||||
if (state.vehicle) return reject(state, "exit-vehicle-first");
|
if (state.vehicle) return reject(state, "exit-vehicle-first");
|
||||||
return accept({ ...state, location: { scale: "california" } });
|
return reject(state, "already-there");
|
||||||
}
|
}
|
||||||
|
|
||||||
case "select-route": {
|
case "select-route": {
|
||||||
@@ -194,7 +201,7 @@ export function transitionJourney(state: JourneyState, event: JourneyEvent): Jou
|
|||||||
const progress = event.endpoint === "san-francisco" ? 1 : 0;
|
const progress = event.endpoint === "san-francisco" ? 1 : 0;
|
||||||
return accept({
|
return accept({
|
||||||
...state,
|
...state,
|
||||||
location: { scale: ENDPOINT_CITY[event.endpoint] },
|
location: { scale: "california" },
|
||||||
route: { ...state.route, progress },
|
route: { ...state.route, progress },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -217,24 +224,20 @@ export function transitionJourney(state: JourneyState, event: JourneyEvent): Jou
|
|||||||
|
|
||||||
case "enter-office": {
|
case "enter-office": {
|
||||||
if (!isOfficeId(event.officeId)) return reject(state, "invalid-event");
|
if (!isOfficeId(event.officeId)) return reject(state, "invalid-event");
|
||||||
if (state.location.scale !== "bay-area" && state.location.scale !== "socal") {
|
if (state.location.scale !== "california") return reject(state, "wrong-scale");
|
||||||
return reject(state, "wrong-scale");
|
|
||||||
}
|
|
||||||
if (state.vehicle) return reject(state, "exit-vehicle-first");
|
if (state.vehicle) return reject(state, "exit-vehicle-first");
|
||||||
if (OFFICES[event.officeId] !== state.location.scale) return reject(state, "wrong-city");
|
|
||||||
return accept({
|
return accept({
|
||||||
...state,
|
...state,
|
||||||
location: {
|
location: {
|
||||||
scale: "office",
|
scale: "office",
|
||||||
officeId: event.officeId,
|
officeId: event.officeId,
|
||||||
priorCity: state.location.scale,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
case "leave-office": {
|
case "leave-office": {
|
||||||
if (state.location.scale !== "office") return reject(state, "already-outside");
|
if (state.location.scale !== "office") return reject(state, "already-outside");
|
||||||
return accept({ ...state, location: { scale: state.location.priorCity } });
|
return accept({ ...state, location: { scale: "california" } });
|
||||||
}
|
}
|
||||||
|
|
||||||
case "sign-in-actor-swap": {
|
case "sign-in-actor-swap": {
|
||||||
@@ -272,11 +275,18 @@ export function decodeJourneySnapshot(payload: string | unknown): JourneySnapsho
|
|||||||
if (!isRecord(parsed) || parsed.version !== 1) {
|
if (!isRecord(parsed) || parsed.version !== 1) {
|
||||||
return { ok: false, error: "journey: unsupported snapshot version" };
|
return { ok: false, error: "journey: unsupported snapshot version" };
|
||||||
}
|
}
|
||||||
if (!isJourneyState(parsed.state)) {
|
if (!isRecord(parsed.state)) {
|
||||||
|
return { ok: false, error: "journey: invalid snapshot state" };
|
||||||
|
}
|
||||||
|
const migrated = {
|
||||||
|
...parsed.state,
|
||||||
|
location: coerceLocation(parsed.state.location),
|
||||||
|
};
|
||||||
|
if (!isJourneyState(migrated)) {
|
||||||
return { ok: false, error: "journey: invalid snapshot state" };
|
return { ok: false, error: "journey: invalid snapshot state" };
|
||||||
}
|
}
|
||||||
// JSON round-tripping is intentional: it strips aliases across a reconnect boundary.
|
// JSON round-tripping is intentional: it strips aliases across a reconnect boundary.
|
||||||
const state = JSON.parse(JSON.stringify(parsed.state)) as JourneyState;
|
const state = JSON.parse(JSON.stringify(migrated)) as JourneyState;
|
||||||
const snapshot: JourneySnapshotV1 = { version: 1, state };
|
const snapshot: JourneySnapshotV1 = { version: 1, state };
|
||||||
return { ok: true, snapshot, state };
|
return { ok: true, snapshot, state };
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-6
@@ -1,6 +1,14 @@
|
|||||||
/** Serializable contracts for moving one actor between Tera's world scales. */
|
/**
|
||||||
|
* Serializable contracts for moving one actor through Tera.
|
||||||
|
*
|
||||||
|
* Outdoors is one California. Bay Area and Southern California are places on
|
||||||
|
* that board — `?city=sf` is a capture renderer, not a player-facing scale.
|
||||||
|
* The other scale is an office: you are inside a building, and leaving it
|
||||||
|
* returns you to California, not to a metro board the product no longer is.
|
||||||
|
*/
|
||||||
|
|
||||||
export type JourneyScale = "california" | "bay-area" | "socal" | "office";
|
export type JourneyScale = "california" | "office";
|
||||||
|
/** Which metro an office or a route end sits in. Geography, not a journey scale. */
|
||||||
export type JourneyCity = "bay-area" | "socal";
|
export type JourneyCity = "bay-area" | "socal";
|
||||||
export type JourneyMode = "observe" | "play";
|
export type JourneyMode = "observe" | "play";
|
||||||
export type JourneyActorKind = "humanoid" | "dog" | "crow";
|
export type JourneyActorKind = "humanoid" | "dog" | "crow";
|
||||||
@@ -23,13 +31,9 @@ export interface JourneyActor {
|
|||||||
|
|
||||||
export type JourneyLocation =
|
export type JourneyLocation =
|
||||||
| { scale: "california" }
|
| { scale: "california" }
|
||||||
| { scale: "bay-area" }
|
|
||||||
| { scale: "socal" }
|
|
||||||
| {
|
| {
|
||||||
scale: "office";
|
scale: "office";
|
||||||
officeId: JourneyOfficeId;
|
officeId: JourneyOfficeId;
|
||||||
/** The detailed board to restore when the actor leaves through the door. */
|
|
||||||
priorCity: JourneyCity;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface JourneyRouteProgress {
|
export interface JourneyRouteProgress {
|
||||||
@@ -56,6 +60,11 @@ export type JourneyOfficeId = "lumbridge-hq" | "frontier-valley" | "mateo-court"
|
|||||||
|
|
||||||
export type JourneyEvent =
|
export type JourneyEvent =
|
||||||
| { type: "set-mode"; mode: JourneyMode }
|
| { type: "set-mode"; mode: JourneyMode }
|
||||||
|
/**
|
||||||
|
* Leftover of the three-board product. Outdoors is already California, so
|
||||||
|
* both events reject with `already-there` (or `leave-office-first`). The
|
||||||
|
* renderer still honours `?city=` via `switchCity`; that is not a journey.
|
||||||
|
*/
|
||||||
| { type: "navigate-to-city"; city: JourneyCity }
|
| { type: "navigate-to-city"; city: JourneyCity }
|
||||||
| { type: "return-to-california" }
|
| { type: "return-to-california" }
|
||||||
| { type: "select-route"; routeId: JourneyRouteId; direction: 1 | -1 }
|
| { type: "select-route"; routeId: JourneyRouteId; direction: 1 | -1 }
|
||||||
|
|||||||
+13
-10
@@ -435,9 +435,13 @@ function dispatchJourney(event: JourneyEvent): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Leftover of the three-board product. The journey cannot leave California
|
||||||
|
* except through a door; this still exists because `switchCity` for `?city=sf`
|
||||||
|
* used to need it, and the events are what old sessions dispatch.
|
||||||
|
*/
|
||||||
function journeyToCity(city: JourneyCity): void {
|
function journeyToCity(city: JourneyCity): void {
|
||||||
if (journey.location.scale === "office") dispatchJourney({ type: "leave-office" });
|
if (journey.location.scale === "office") dispatchJourney({ type: "leave-office" });
|
||||||
if (journey.location.scale !== "california") dispatchJourney({ type: "return-to-california" });
|
|
||||||
dispatchJourney({ type: "navigate-to-city", city });
|
dispatchJourney({ type: "navigate-to-city", city });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3414,7 +3418,10 @@ function syncJourneyVehicle(now: number): void {
|
|||||||
const endpoint = journey.route.direction === 1 ? "san-francisco" : "los-angeles";
|
const endpoint = journey.route.direction === 1 ? "san-francisco" : "los-angeles";
|
||||||
dispatchJourney({ type: "reach-route-endpoint", endpoint });
|
dispatchJourney({ type: "reach-route-endpoint", endpoint });
|
||||||
dispatchJourney({ type: "exit-vehicle" });
|
dispatchJourney({ type: "exit-vehicle" });
|
||||||
switchCity(endpoint === "san-francisco" ? "sf" : "socal");
|
officeId = endpoint === "san-francisco" ? "lumbridge-hq" : "mateo-court";
|
||||||
|
requestControlMode("overview");
|
||||||
|
city.flyTo(endpoint === "san-francisco" ? "san-francisco" : "los-angeles");
|
||||||
|
renderChrome();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (now - journeySyncedAt < 500) return;
|
if (now - journeySyncedAt < 500) return;
|
||||||
@@ -3571,8 +3578,6 @@ async function enterOffice() {
|
|||||||
};
|
};
|
||||||
attachCurrentWebcamFace();
|
attachCurrentWebcamFace();
|
||||||
moveRealtimePresence();
|
moveRealtimePresence();
|
||||||
const desiredCity: JourneyCity = officeId === "mateo-court" ? "socal" : "bay-area";
|
|
||||||
journeyToCity(desiredCity);
|
|
||||||
dispatchJourney({ type: "enter-office", officeId: officeId as "lumbridge-hq" | "frontier-valley" | "mateo-court" });
|
dispatchJourney({ type: "enter-office", officeId: officeId as "lumbridge-hq" | "frontier-valley" | "mateo-court" });
|
||||||
showPlan();
|
showPlan();
|
||||||
showDetail(null);
|
showDetail(null);
|
||||||
@@ -5522,12 +5527,10 @@ function activePlace(): LadderRung | null {
|
|||||||
/**
|
/**
|
||||||
* Go to a rung, on this board or another one.
|
* Go to a rung, on this board or another one.
|
||||||
*
|
*
|
||||||
* The cross-board case is the whole reason the ladder exists, and it has to
|
* The cross-board case is the whole reason the ladder exists, and it is a
|
||||||
* dispatch the journey event: `journey/state.ts` models a board change as an
|
* renderer change (`?city=` / `?one=0`), not a journey scale. Outdoors is
|
||||||
* explicit event and gates `enter-office` on `OFFICES[officeId] === location.scale`.
|
* California either way; `switchCity` still updates `wantedCity` so capture
|
||||||
* A board that changed without one leaves the door into Mateo Court rejecting
|
* deep links keep aiming at the board they named.
|
||||||
* with `wrong-city` while the Southland is visibly on screen, and nothing throws.
|
|
||||||
* `switchCity` already dispatches; this routes through it rather than around it.
|
|
||||||
*/
|
*/
|
||||||
function goToPlace(rung: LadderRung): void {
|
function goToPlace(rung: LadderRung): void {
|
||||||
if (rung.board !== cityId) {
|
if (rung.board !== cityId) {
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ describe("city and office actor acceptance handoff", () => {
|
|||||||
profile: {},
|
profile: {},
|
||||||
};
|
};
|
||||||
let journey = createJourney({ actor: journeyActor });
|
let journey = createJourney({ actor: journeyActor });
|
||||||
journey = journeyReducer(journey, { type: "navigate-to-city", city: "bay-area" });
|
|
||||||
|
|
||||||
assert.equal(actorKindForPresence(false, "outdoors"), "crow");
|
assert.equal(actorKindForPresence(false, "outdoors"), "crow");
|
||||||
const cityActor = createSceneActor({
|
const cityActor = createSceneActor({
|
||||||
@@ -108,7 +107,7 @@ describe("city and office actor acceptance handoff", () => {
|
|||||||
assert.ok(walker.state().position.x < 5, "the same controller cannot cross the solid wall");
|
assert.ok(walker.state().position.x < 5, "the same controller cannot cross the solid wall");
|
||||||
|
|
||||||
journey = journeyReducer(journey, { type: "leave-office" });
|
journey = journeyReducer(journey, { type: "leave-office" });
|
||||||
assert.equal(journey.location.scale, "bay-area");
|
assert.equal(journey.location.scale, "california");
|
||||||
assert.deepEqual(journey.actor, journeyActor);
|
assert.deepEqual(journey.actor, journeyActor);
|
||||||
assert.deepEqual(cityActor.state(), parkedCityState, "the outdoor actor stays parked during the office visit");
|
assert.deepEqual(cityActor.state(), parkedCityState, "the outdoor actor stays parked during the office visit");
|
||||||
assert.deepEqual(cityActor.state().identity, identity);
|
assert.deepEqual(cityActor.state().identity, identity);
|
||||||
|
|||||||
+57
-35
@@ -32,7 +32,7 @@ const KARTI: JourneyActor = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function playFromLosAngeles(): JourneyState {
|
function playFromLosAngeles(): JourneyState {
|
||||||
let state = createJourney({ actor: CROW, location: { scale: "socal" }, mode: "play" });
|
let state = createJourney({ actor: CROW, location: { scale: "california" }, mode: "play" });
|
||||||
state = journeyReducer(state, {
|
state = journeyReducer(state, {
|
||||||
type: "select-route",
|
type: "select-route",
|
||||||
routeId: "la-sf-i-5",
|
routeId: "la-sf-i-5",
|
||||||
@@ -43,24 +43,23 @@ function playFromLosAngeles(): JourneyState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("journey state machine", () => {
|
describe("journey state machine", () => {
|
||||||
it("navigates explicitly between California and either detailed city board", () => {
|
it("treats metro boards as geography, not as a journey scale", () => {
|
||||||
const california = createJourney({ actor: CROW, mode: "play" });
|
const california = createJourney({ actor: CROW, mode: "play" });
|
||||||
const actor = california.actor;
|
assert.deepEqual(
|
||||||
const bayArea = journeyReducer(california, {
|
transitionJourney(california, { type: "navigate-to-city", city: "bay-area" }),
|
||||||
type: "navigate-to-city",
|
{ accepted: false, state: california, reason: "already-there" },
|
||||||
city: "bay-area",
|
);
|
||||||
});
|
assert.deepEqual(
|
||||||
assert.deepEqual(bayArea.location, { scale: "bay-area" });
|
transitionJourney(california, { type: "return-to-california" }),
|
||||||
assert.equal(bayArea.actor, actor);
|
{ accepted: false, state: california, reason: "already-there" },
|
||||||
|
);
|
||||||
const returned = journeyReducer(bayArea, { type: "return-to-california" });
|
assert.deepEqual(california.location, { scale: "california" });
|
||||||
assert.deepEqual(returned.location, { scale: "california" });
|
assert.equal(endpointCity("los-angeles"), "socal");
|
||||||
assert.equal(returned.actor, actor);
|
assert.equal(endpointCity("san-francisco"), "bay-area");
|
||||||
const southern = journeyReducer(returned, { type: "navigate-to-city", city: "socal" });
|
assert.equal(officeCity("mateo-court"), "socal");
|
||||||
assert.deepEqual(southern.location, { scale: "socal" });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requires leaving an office or vehicle before board navigation", () => {
|
it("requires leaving an office or vehicle before the leftover board events", () => {
|
||||||
let driving = playFromLosAngeles();
|
let driving = playFromLosAngeles();
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
transitionJourney(driving, { type: "navigate-to-city", city: "bay-area" }),
|
transitionJourney(driving, { type: "navigate-to-city", city: "bay-area" }),
|
||||||
@@ -68,8 +67,7 @@ describe("journey state machine", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
driving = journeyReducer(driving, { type: "exit-vehicle" });
|
driving = journeyReducer(driving, { type: "exit-vehicle" });
|
||||||
const bayArea = journeyReducer(driving, { type: "navigate-to-city", city: "bay-area" });
|
let office = journeyReducer(driving, {
|
||||||
let office = journeyReducer(bayArea, {
|
|
||||||
type: "enter-office",
|
type: "enter-office",
|
||||||
officeId: "lumbridge-hq",
|
officeId: "lumbridge-hq",
|
||||||
});
|
});
|
||||||
@@ -78,20 +76,17 @@ describe("journey state machine", () => {
|
|||||||
{ accepted: false, state: office, reason: "leave-office-first" },
|
{ accepted: false, state: office, reason: "leave-office-first" },
|
||||||
);
|
);
|
||||||
office = journeyReducer(office, { type: "leave-office" });
|
office = journeyReducer(office, { type: "leave-office" });
|
||||||
assert.equal(journeyReducer(office, { type: "return-to-california" }).location.scale, "california");
|
assert.deepEqual(office.location, { scale: "california" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("maps route endpoints honestly onto the detailed city boards", () => {
|
it("arrives at a route end still on California", () => {
|
||||||
assert.equal(endpointCity("los-angeles"), "socal");
|
|
||||||
assert.equal(endpointCity("san-francisco"), "bay-area");
|
|
||||||
|
|
||||||
let northbound = playFromLosAngeles();
|
let northbound = playFromLosAngeles();
|
||||||
northbound = journeyReducer(northbound, { type: "update-route-progress", progress: 0.74 });
|
northbound = journeyReducer(northbound, { type: "update-route-progress", progress: 0.74 });
|
||||||
northbound = journeyReducer(northbound, {
|
northbound = journeyReducer(northbound, {
|
||||||
type: "reach-route-endpoint",
|
type: "reach-route-endpoint",
|
||||||
endpoint: "san-francisco",
|
endpoint: "san-francisco",
|
||||||
});
|
});
|
||||||
assert.equal(northbound.location.scale, "bay-area");
|
assert.equal(northbound.location.scale, "california");
|
||||||
assert.equal(northbound.route?.progress, 1);
|
assert.equal(northbound.route?.progress, 1);
|
||||||
assert.equal(northbound.vehicle?.vehicleId, "model-x-hero");
|
assert.equal(northbound.vehicle?.vehicleId, "model-x-hero");
|
||||||
|
|
||||||
@@ -106,24 +101,22 @@ describe("journey state machine", () => {
|
|||||||
type: "reach-route-endpoint",
|
type: "reach-route-endpoint",
|
||||||
endpoint: "los-angeles",
|
endpoint: "los-angeles",
|
||||||
});
|
});
|
||||||
assert.equal(southbound.location.scale, "socal");
|
assert.equal(southbound.location.scale, "california");
|
||||||
assert.equal(southbound.route?.progress, 0);
|
assert.equal(southbound.route?.progress, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("preserves identity and prior city across office doors", () => {
|
it("opens an office from California and leaves onto the same world", () => {
|
||||||
let state = createJourney({ actor: KARTI, location: { scale: "bay-area" }, mode: "play" });
|
let state = createJourney({ actor: KARTI, location: { scale: "california" }, mode: "play" });
|
||||||
const actor = state.actor;
|
const actor = state.actor;
|
||||||
state = journeyReducer(state, { type: "enter-office", officeId: "frontier-valley" });
|
state = journeyReducer(state, { type: "enter-office", officeId: "frontier-valley" });
|
||||||
assert.deepEqual(state.location, {
|
assert.deepEqual(state.location, {
|
||||||
scale: "office",
|
scale: "office",
|
||||||
officeId: "frontier-valley",
|
officeId: "frontier-valley",
|
||||||
priorCity: "bay-area",
|
|
||||||
});
|
});
|
||||||
assert.equal(state.actor, actor);
|
assert.equal(state.actor, actor);
|
||||||
state = journeyReducer(state, { type: "leave-office" });
|
state = journeyReducer(state, { type: "leave-office" });
|
||||||
assert.deepEqual(state.location, { scale: "bay-area" });
|
assert.deepEqual(state.location, { scale: "california" });
|
||||||
assert.equal(state.actor, actor);
|
assert.equal(state.actor, actor);
|
||||||
assert.equal(officeCity("mateo-court"), "socal");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("swaps a signed-in actor without disturbing their journey", () => {
|
it("swaps a signed-in actor without disturbing their journey", () => {
|
||||||
@@ -140,7 +133,6 @@ describe("journey state machine", () => {
|
|||||||
const state = createJourney({ actor: CROW });
|
const state = createJourney({ actor: CROW });
|
||||||
const badEvents: JourneyEvent[] = [
|
const badEvents: JourneyEvent[] = [
|
||||||
{ type: "enter-vehicle", vehicleId: "model-x-hero" },
|
{ type: "enter-vehicle", vehicleId: "model-x-hero" },
|
||||||
{ type: "enter-office", officeId: "lumbridge-hq" },
|
|
||||||
{ type: "leave-office" },
|
{ type: "leave-office" },
|
||||||
{ type: "update-route-progress", progress: 0.5 },
|
{ type: "update-route-progress", progress: 0.5 },
|
||||||
{ type: "reach-route-endpoint", endpoint: "san-francisco" },
|
{ type: "reach-route-endpoint", endpoint: "san-francisco" },
|
||||||
@@ -164,7 +156,7 @@ describe("journey state machine", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("requires play mode, a route, and exiting the car before an office", () => {
|
it("requires play mode, a route, and exiting the car before an office", () => {
|
||||||
const observer = createJourney({ actor: CROW, location: { scale: "socal" } });
|
const observer = createJourney({ actor: CROW, location: { scale: "california" } });
|
||||||
assert.equal(
|
assert.equal(
|
||||||
transitionJourney(observer, { type: "enter-vehicle", vehicleId: "x" }).accepted,
|
transitionJourney(observer, { type: "enter-vehicle", vehicleId: "x" }).accepted,
|
||||||
false,
|
false,
|
||||||
@@ -208,8 +200,7 @@ describe("journey state machine", () => {
|
|||||||
...createJourney({ actor: CROW }),
|
...createJourney({ actor: CROW }),
|
||||||
location: {
|
location: {
|
||||||
scale: "office",
|
scale: "office",
|
||||||
officeId: "mateo-court",
|
officeId: "not-an-office",
|
||||||
priorCity: "bay-area",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).ok,
|
}).ok,
|
||||||
@@ -217,8 +208,39 @@ describe("journey state machine", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reads a three-board session as California", () => {
|
||||||
|
const outdoor = decodeJourneySnapshot({
|
||||||
|
version: 1,
|
||||||
|
state: {
|
||||||
|
...createJourney({ actor: CROW, mode: "play" }),
|
||||||
|
location: { scale: "bay-area" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(outdoor.ok, true);
|
||||||
|
if (outdoor.ok) assert.deepEqual(outdoor.state.location, { scale: "california" });
|
||||||
|
|
||||||
|
const inside = decodeJourneySnapshot({
|
||||||
|
version: 1,
|
||||||
|
state: {
|
||||||
|
...createJourney({ actor: KARTI, mode: "play" }),
|
||||||
|
location: {
|
||||||
|
scale: "office",
|
||||||
|
officeId: "lumbridge-hq",
|
||||||
|
priorCity: "bay-area",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.equal(inside.ok, true);
|
||||||
|
if (inside.ok) {
|
||||||
|
assert.deepEqual(inside.state.location, {
|
||||||
|
scale: "office",
|
||||||
|
officeId: "lumbridge-hq",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("is deterministic for the same initial state and event log", () => {
|
it("is deterministic for the same initial state and event log", () => {
|
||||||
const initial = createJourney({ actor: CROW, location: { scale: "socal" }, mode: "play" });
|
const initial = createJourney({ actor: CROW, location: { scale: "california" }, mode: "play" });
|
||||||
const events: JourneyEvent[] = [
|
const events: JourneyEvent[] = [
|
||||||
{ type: "select-route", routeId: "la-sf-us-101", direction: 1 },
|
{ type: "select-route", routeId: "la-sf-us-101", direction: 1 },
|
||||||
{ type: "enter-vehicle", vehicleId: "model-x-hero" },
|
{ type: "enter-vehicle", vehicleId: "model-x-hero" },
|
||||||
|
|||||||
Reference in New Issue
Block a user