mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-14 23:32:20 +00:00
fix(cockpit): entry to the built-in views is as before: aircraft, satellite and vessel keep their fixed order whenever each was selected, and an application's subject takes entry only when it was selected after every built-in one present, so without providers nothing changes
This commit is contained in:
1 parent
6fbc98952f
commit
a4f321a9ef
6 files changed
+119
-40
No files matched your search
@@ -6,6 +6,7 @@
|
|||||||
under reduced motion) and a bottom progress line. The Rail and Transit
|
under reduced motion) and a bottom progress line. The Rail and Transit
|
||||||
layers expose read and hold APIs so a selected train or vehicle can be
|
layers expose read and hold APIs so a selected train or vehicle can be
|
||||||
ridden, and `./layers/rail/paths` is exported; `docs/APPLICATION.md` shows
|
ridden, and `./layers/rail/paths` is exported; `docs/APPLICATION.md` shows
|
||||||
a minimal provider. GEV itself registers none. Cockpit entry now goes to
|
a minimal provider. GEV itself registers none, and entry to the built-in
|
||||||
the most recently selected subject, so a vessel selected after an aircraft
|
aircraft, satellite and vessel views is unchanged; a provider's subject
|
||||||
is tracked enters BRIDGE (Denis Groshev, @ZloyS, #1686).
|
takes entry when it was selected after them (Denis Groshev, @ZloyS,
|
||||||
|
#1686).
|
||||||
+14
-13
@@ -1696,9 +1696,7 @@ and portal cleanup follows that restoration.
|
|||||||
|
|
||||||
### Ship bridge view
|
### Ship bridge view
|
||||||
|
|
||||||
Cockpit can also ride the selected AIS vessel; when other subjects are
|
With no aircraft tracked, Cockpit can ride the selected AIS vessel instead.
|
||||||
selected too, entry takes the most recently selected one (see Application
|
|
||||||
subjects below).
|
|
||||||
Entry needs only the layer of the vessel it rides, Live AIS Vessels, and a
|
Entry needs only the layer of the vessel it rides, Live AIS Vessels, and a
|
||||||
selected vessel; Contacts and every other layer may be off, before or during
|
selected vessel; Contacts and every other layer may be off, before or during
|
||||||
the bridge (`bridgeEntryAllowed` in `src/contextModePolicy.js`). A Context mode
|
the bridge (`bridgeEntryAllowed` in `src/contextModePolicy.js`). A Context mode
|
||||||
@@ -1784,9 +1782,9 @@ satellite instead. Entry needs only the Satellites layer
|
|||||||
layer may be off. A Context mode change in flight blocks entry until it
|
layer may be off. A Context mode change in flight blocks entry until it
|
||||||
settles. Debris (rocket bodies, fragments and dead satellites; see the
|
settles. Debris (rocket bodies, fragments and dead satellites; see the
|
||||||
satellite class toggles) can be tracked but never entered: no ORBIT entry is
|
satellite class toggles) can be tracked but never entered: no ORBIT entry is
|
||||||
offered and double-click refuses it. When an aircraft or a vessel is also
|
offered and double-click refuses it. A tracked aircraft still takes
|
||||||
tracked or selected, entry takes the most recently selected subject (see
|
entry first, and a tracked satellite
|
||||||
Application subjects below). Double-clicking the tracked satellite
|
takes it before a selected vessel. Double-clicking the tracked satellite
|
||||||
enters it, and the entry button reads
|
enters it, and the entry button reads
|
||||||
ORBIT with a satellite icon in Contacts (`#cockpit-entry`) and under the Intel
|
ORBIT with a satellite icon in Contacts (`#cockpit-entry`) and under the Intel
|
||||||
HUD readout (`#cockpit-entry-readout`). Voice (`control_cockpit`) stays
|
HUD readout (`#cockpit-entry-readout`). Voice (`control_cockpit`) stays
|
||||||
@@ -2001,13 +1999,16 @@ layer on, no Context change in flight and the provider's own
|
|||||||
`isEntryAllowed(candidate)`; voice stays aircraft-only and is refused, with
|
`isEntryAllowed(candidate)`; voice stays aircraft-only and is refused, with
|
||||||
the reason, while a provider's view is in use.
|
the reason, while a provider's view is in use.
|
||||||
|
|
||||||
Entry rides the most recently selected subject of all kinds: a tracked
|
The built-in kinds keep their fixed entry order (a tracked aircraft, then the
|
||||||
aircraft, the tracked satellite, the selected vessel or a provider's
|
tracked satellite, then the selected vessel), so without providers entry is
|
||||||
candidate, by when the Cockpit first saw it, or saw it selected again (a
|
unchanged. A provider's candidate takes entry only when it was selected after
|
||||||
provider candidate's `selectedAt` changing); subjects first seen together keep
|
every built-in subject present, by when the Cockpit first saw it or saw it
|
||||||
the fixed order (aircraft, satellite, vessel, then providers by priority). The
|
selected again (its `selectedAt` changing): a train picked after an aircraft
|
||||||
newest decides alone: when its gate is shut there is no entry rather than an
|
was tracked takes entry, and an aircraft tracked after it takes it back.
|
||||||
older subject's.
|
Providers' candidates are ordered most recently selected first, then by
|
||||||
|
priority; one first seen together with a built-in subject comes after it. The
|
||||||
|
first in that order decides alone: when its gate is shut there is no entry
|
||||||
|
rather than the next subject's.
|
||||||
|
|
||||||
A frame places the eye `eye.upM` above `heightM` (the shared floor where the
|
A frame places the eye `eye.upM` above `heightM` (the shared floor where the
|
||||||
frame gives none, or where it last stood while that floor is cold; with
|
frame gives none, or where it last stood while that floor is cold; with
|
||||||
|
|||||||
@@ -273,8 +273,8 @@ export function cockpitSubjectProviders(
|
|||||||
* A provider's candidate: `{id, name, face, selectedAt}`, or null without a
|
* A provider's candidate: `{id, name, face, selectedAt}`, or null without a
|
||||||
* usable id. `name` falls back to the id; `face` is null unless well formed;
|
* usable id. `name` falls back to the id; `face` is null unless well formed;
|
||||||
* `selectedAt` (a number or a short string, else null) is a token the
|
* `selectedAt` (a number or a short string, else null) is a token the
|
||||||
* provider changes each time its subject is selected, even again: entry
|
* provider changes each time its subject is selected, even again: a
|
||||||
* takes the most recently selected subject (entryCandidates).
|
* subject selected after the built-in ones takes entry (entryCandidates).
|
||||||
* @param {unknown} raw What `candidate()` returned.
|
* @param {unknown} raw What `candidate()` returned.
|
||||||
*/
|
*/
|
||||||
export function cockpitSubjectCandidate(raw) {
|
export function cockpitSubjectCandidate(raw) {
|
||||||
|
|||||||
@@ -603,7 +603,7 @@ test('the readout entry reads BRIDGE for a selected vessel', async (t) => {
|
|||||||
assert.match(readoutEntry.textContent, /BRIDGE/);
|
assert.match(readoutEntry.textContent, /BRIDGE/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('without providers too, the later selection takes entry: a vessel after an aircraft reads BRIDGE', async (t) => {
|
test('without providers the built-in order decides: a vessel selected after an aircraft still reads COCKPIT', async (t) => {
|
||||||
const { $, cockpit, store, selectVessel } = fixture(t, {
|
const { $, cockpit, store, selectVessel } = fixture(t, {
|
||||||
enabled: ['flights', 'ais-live-vessels'],
|
enabled: ['flights', 'ais-live-vessels'],
|
||||||
tracked: { layerId: 'flights', info: CIVIL },
|
tracked: { layerId: 'flights', info: CIVIL },
|
||||||
@@ -614,8 +614,8 @@ test('without providers too, the later selection takes entry: a vessel after an
|
|||||||
selectVessel(VESSEL);
|
selectVessel(VESSEL);
|
||||||
cockpit.syncEntry();
|
cockpit.syncEntry();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
assert.equal(store.presence.entryKind, 'vessel');
|
assert.equal(store.presence.entryKind, 'aircraft');
|
||||||
assert.match($('#cockpit-entry-readout').textContent, /BRIDGE/);
|
assert.match($('#cockpit-entry-readout').textContent, /COCKPIT/);
|
||||||
// And the other way round: an aircraft tracked after the vessel.
|
// And the other way round: an aircraft tracked after the vessel.
|
||||||
const later = fixture(t, {
|
const later = fixture(t, {
|
||||||
enabled: ['flights', 'ais-live-vessels'],
|
enabled: ['flights', 'ais-live-vessels'],
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ function fixture(t, { providers = [], vessel = null, layerOn = true } = {}) {
|
|||||||
trackedEntityChanged: listeners(),
|
trackedEntityChanged: listeners(),
|
||||||
};
|
};
|
||||||
let vesselSelected = vessel;
|
let vesselSelected = vessel;
|
||||||
|
let trackedAircraftInfo = null;
|
||||||
let floorM = FLOOR_M;
|
let floorM = FLOOR_M;
|
||||||
const warned = [];
|
const warned = [];
|
||||||
t.mock.method(console, 'warn', (message) => warned.push(String(message)));
|
t.mock.method(console, 'warn', (message) => warned.push(String(message)));
|
||||||
@@ -168,7 +169,7 @@ function fixture(t, { providers = [], vessel = null, layerOn = true } = {}) {
|
|||||||
const dropped = [];
|
const dropped = [];
|
||||||
const cockpit = new CockpitViewController(viewer, {
|
const cockpit = new CockpitViewController(viewer, {
|
||||||
services: {
|
services: {
|
||||||
flightsLayer: { getTrackedInfo: () => null },
|
flightsLayer: { getTrackedInfo: () => trackedAircraftInfo },
|
||||||
militaryFlightsLayer: { getTrackedInfo: () => null },
|
militaryFlightsLayer: { getTrackedInfo: () => null },
|
||||||
aisLiveVesselsLayer: {
|
aisLiveVesselsLayer: {
|
||||||
getCockpitSubject: () =>
|
getCockpitSubject: () =>
|
||||||
@@ -235,6 +236,11 @@ function fixture(t, { providers = [], vessel = null, layerOn = true } = {}) {
|
|||||||
selectVessel(record) {
|
selectVessel(record) {
|
||||||
vesselSelected = record;
|
vesselSelected = record;
|
||||||
},
|
},
|
||||||
|
/** Track the fixture's aircraft as Live Flights does, or untrack it. */
|
||||||
|
trackAircraft(info) {
|
||||||
|
trackedAircraftInfo = info;
|
||||||
|
viewer.trackedEntity = info ? aircraft : undefined;
|
||||||
|
},
|
||||||
setFloor(value) {
|
setFloor(value) {
|
||||||
floorM = value;
|
floorM = value;
|
||||||
},
|
},
|
||||||
@@ -283,6 +289,15 @@ const VESSEL = Object.freeze({
|
|||||||
missedRefreshes: 0,
|
missedRefreshes: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** The fixture's aircraft as Live Flights reports it once tracked. */
|
||||||
|
const TRACKED_AIRCRAFT = Object.freeze({
|
||||||
|
icao24: 'abc123',
|
||||||
|
callsign: 'UAL123',
|
||||||
|
track: 92,
|
||||||
|
latitude: LAT,
|
||||||
|
longitude: LON,
|
||||||
|
});
|
||||||
|
|
||||||
test('a provider candidate offers its own entry face; a shut gate hides it', async (t) => {
|
test('a provider candidate offers its own entry face; a shut gate hides it', async (t) => {
|
||||||
const ride = subjectProvider();
|
const ride = subjectProvider();
|
||||||
const { $, cockpit, store } = fixture(t, { providers: [ride] });
|
const { $, cockpit, store } = fixture(t, { providers: [ride] });
|
||||||
@@ -869,6 +884,51 @@ test('the most recently selected subject wins entry', async (t) => {
|
|||||||
assert.equal(cockpit.enter(), false);
|
assert.equal(cockpit.enter(), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the built-in kinds keep their order; a subject takes entry only when selected after all of them', async (t) => {
|
||||||
|
const clock = manualClock(t, 10_000);
|
||||||
|
const ride = subjectProvider();
|
||||||
|
ride.subject.candidate = null;
|
||||||
|
const { cockpit, selectVessel, trackAircraft } = fixture(t, {
|
||||||
|
providers: [ride],
|
||||||
|
});
|
||||||
|
trackAircraft(TRACKED_AIRCRAFT);
|
||||||
|
cockpit.syncEntry();
|
||||||
|
assert.equal(cockpit.resolveEntryKind(), 'aircraft');
|
||||||
|
// A subject selected after the aircraft was tracked takes entry.
|
||||||
|
clock.advance(500);
|
||||||
|
ride.subject.candidate = { id: 'a1', name: 'Example 12' };
|
||||||
|
cockpit.syncEntry();
|
||||||
|
assert.equal(cockpit.resolveEntryKind(), 'example-kind');
|
||||||
|
// A vessel selected after it: a built-in kind is newer, so the built-in
|
||||||
|
// order decides, as without providers, and the aircraft comes first.
|
||||||
|
clock.advance(500);
|
||||||
|
selectVessel(VESSEL);
|
||||||
|
cockpit.syncEntry();
|
||||||
|
assert.equal(cockpit.resolveEntryKind(), 'aircraft');
|
||||||
|
// The subject selected again is newer than every built-in one.
|
||||||
|
clock.advance(500);
|
||||||
|
ride.subject.candidate = { id: 'a1', name: 'Example 12', selectedAt: 2 };
|
||||||
|
cockpit.syncEntry();
|
||||||
|
assert.equal(cockpit.resolveEntryKind(), 'example-kind');
|
||||||
|
// An aircraft tracked after it takes entry back.
|
||||||
|
selectVessel(null);
|
||||||
|
trackAircraft(null);
|
||||||
|
cockpit.syncEntry();
|
||||||
|
clock.advance(500);
|
||||||
|
trackAircraft(TRACKED_AIRCRAFT);
|
||||||
|
cockpit.syncEntry();
|
||||||
|
assert.equal(cockpit.resolveEntryKind(), 'aircraft');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a subject first seen together with a built-in one comes after it', async (t) => {
|
||||||
|
manualClock(t, 10_000);
|
||||||
|
const ride = subjectProvider();
|
||||||
|
const { cockpit, store } = fixture(t, { providers: [ride], vessel: VESSEL });
|
||||||
|
cockpit.syncEntry();
|
||||||
|
assert.equal(cockpit.resolveEntryKind(), 'vessel');
|
||||||
|
assert.equal(store.presence.entryKind, 'vessel');
|
||||||
|
});
|
||||||
|
|
||||||
test('a provider view with look turns with the arrow keys and words its readout', async (t) => {
|
test('a provider view with look turns with the arrow keys and words its readout', async (t) => {
|
||||||
const clock = manualClock(t, 10_000);
|
const clock = manualClock(t, 10_000);
|
||||||
const ride = subjectProvider();
|
const ride = subjectProvider();
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ export function subjectInfo(frame) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Entry's fixed order, for candidates first seen at the same moment. */
|
/** The built-in kinds' fixed entry order. */
|
||||||
const BUILT_IN_ENTRY_ORDER = Object.freeze({
|
const BUILT_IN_ENTRY_ORDER = Object.freeze({
|
||||||
aircraft: 0,
|
aircraft: 0,
|
||||||
satellite: 1,
|
satellite: 1,
|
||||||
@@ -101,14 +101,19 @@ const BUILT_IN_ENTRY_ORDER = Object.freeze({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every subject entry could ride now, the most recently selected first: a
|
* Every subject entry could ride now, in entry order: a tracked aircraft,
|
||||||
* tracked aircraft, the tracked satellite, the selected vessel and each
|
* the tracked satellite, the selected vessel and each provider's candidate.
|
||||||
* provider's candidate. A candidate's time is when this controller first saw
|
* The built-in kinds keep their fixed order (aircraft, satellite, vessel),
|
||||||
* it (it looks on its entry cadence and on tracking changes), or saw it
|
* whenever each was selected. A provider's candidate goes ahead of them only
|
||||||
* selected again: a provider candidate whose `selectedAt` changes, the same
|
* when it was selected after every built-in one present: a train picked
|
||||||
* subject clicked again, moves to the front. Candidates first seen together
|
* after an aircraft was tracked takes entry, an aircraft tracked after it
|
||||||
* keep the fixed order (aircraft, satellite, vessel, then providers by
|
* takes it back. Providers' candidates are ordered most recently selected
|
||||||
* priority). Each carries `allowed()`, its kind's entry gate.
|
* first, then by priority. A candidate's time is when this controller first
|
||||||
|
* saw it (it looks on its entry cadence and on tracking changes), or saw it
|
||||||
|
* selected again (a provider candidate whose `selectedAt` changes). One
|
||||||
|
* first seen together with a built-in candidate comes after it. With no
|
||||||
|
* provider, entry is the built-in order alone. Each carries `allowed()`, its
|
||||||
|
* kind's entry gate.
|
||||||
* @param {number} [nowMs]
|
* @param {number} [nowMs]
|
||||||
* @returns {Array<{kind: string, id: string, seenAtMs: number, order: number,
|
* @returns {Array<{kind: string, id: string, seenAtMs: number, order: number,
|
||||||
* allowed: () => boolean, provider?: object, candidate?: object}>}
|
* allowed: () => boolean, provider?: object, candidate?: object}>}
|
||||||
@@ -167,13 +172,25 @@ export function entryCandidates(nowMs = performance.now()) {
|
|||||||
// A subject gone and back counts as selected again.
|
// A subject gone and back counts as selected again.
|
||||||
for (const kind of [...seen.keys()])
|
for (const kind of [...seen.keys()])
|
||||||
if (!list.some((entry) => entry.kind === kind)) seen.delete(kind);
|
if (!list.some((entry) => entry.kind === kind)) seen.delete(kind);
|
||||||
return list.sort((a, b) => b.seenAtMs - a.seenAtMs || a.order - b.order);
|
const builtIns = list
|
||||||
|
.filter((entry) => !entry.provider)
|
||||||
|
.sort((a, b) => a.order - b.order);
|
||||||
|
const provided = list
|
||||||
|
.filter((entry) => entry.provider)
|
||||||
|
.sort((a, b) => b.seenAtMs - a.seenAtMs || a.order - b.order);
|
||||||
|
const builtInSeenAtMs = Math.max(
|
||||||
|
-Infinity,
|
||||||
|
...builtIns.map((entry) => entry.seenAtMs),
|
||||||
|
);
|
||||||
|
const later = provided.filter((entry) => entry.seenAtMs > builtInSeenAtMs);
|
||||||
|
const earlier = provided.filter((entry) => entry.seenAtMs <= builtInSeenAtMs);
|
||||||
|
return [...later, ...builtIns, ...earlier];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The subject entry would ride: the most recently selected one. It decides
|
* The subject entry would ride: the first in entry order (entryCandidates).
|
||||||
* alone, so when its own gate is shut entry is unavailable rather than taken
|
* It decides alone, so when its own gate is shut entry is unavailable rather
|
||||||
* by an older selection.
|
* than taken by the next one.
|
||||||
* @returns {object|null} An entry of entryCandidates, or null.
|
* @returns {object|null} An entry of entryCandidates, or null.
|
||||||
*/
|
*/
|
||||||
export function resolveEntry(nowMs = performance.now()) {
|
export function resolveEntry(nowMs = performance.now()) {
|
||||||
@@ -199,9 +216,9 @@ export function readSubjectLayerId() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The kind entry would take: the most recently selected subject's
|
* The kind entry would take, the first in entry order's (resolveEntry): a
|
||||||
* (resolveEntry): a Cesium-tracked aircraft, the tracked satellite, the
|
* Cesium-tracked aircraft, the tracked satellite, the selected vessel or a
|
||||||
* selected vessel or a provider's candidate.
|
* provider's candidate.
|
||||||
* @returns {string|null}
|
* @returns {string|null}
|
||||||
*/
|
*/
|
||||||
export function resolveEntryKind() {
|
export function resolveEntryKind() {
|
||||||
@@ -296,7 +313,7 @@ export function syncEntry() {
|
|||||||
const candidates = this.entryCandidates(performance.now());
|
const candidates = this.entryCandidates(performance.now());
|
||||||
const tracked = candidates.some(({ kind }) => kind === 'aircraft');
|
const tracked = candidates.some(({ kind }) => kind === 'aircraft');
|
||||||
this.syncTr3bToggle(tracked ? this.readAircraftInfo() : null);
|
this.syncTr3bToggle(tracked ? this.readAircraftInfo() : null);
|
||||||
// The most recently selected subject decides (resolveEntry).
|
// The first in entry order decides alone (resolveEntry).
|
||||||
const chosen = candidates[0] ?? null;
|
const chosen = candidates[0] ?? null;
|
||||||
const entryKind = chosen?.allowed() ? chosen.kind : null;
|
const entryKind = chosen?.allowed() ? chosen.kind : null;
|
||||||
const face =
|
const face =
|
||||||
@@ -518,7 +535,7 @@ function enterSubject(provider) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enter first person on the most recently selected subject (resolveEntry): a
|
* Enter first person on the first subject in entry order (resolveEntry): a
|
||||||
* tracked aircraft, the tracked satellite's orbital view, the selected
|
* tracked aircraft, the tracked satellite's orbital view, the selected
|
||||||
* vessel's bridge or an application provider's subject.
|
* vessel's bridge or an application provider's subject.
|
||||||
* @param {{kind?: string|null}} [options] Restrict entry to one subject kind;
|
* @param {{kind?: string|null}} [options] Restrict entry to one subject kind;
|
||||||
|
|||||||
Reference in new issue
Block a user