Share links carry the world time when made away from live

Field 28, worldTime, holds the world time of a link made away from live:
zigzag seconds since 2026-01-01, like field 8, but it may lie ahead of the
link's creation (a forecast moment). Opening such a link pauses the world
clock at that time; a link without it leaves the clock alone, so a page
opens live and Back or Forward does not stop a replay. Times no JS Date
can hold are dropped.
This commit is contained in:
Sameh Khamis committed 2026-11-01 21:50:09 -08:00
1 parent 0c4da3cdb2
commit 32ec6ed436
8 files changed
+96 -8

No files matched your search

+5
View File
@@ -119,6 +119,7 @@ probability of about 255 in 256 (many of them already fail as base64url).
| 25 | `scopeFeatherPct` | uint | 100 | 0–200 |
| 26 | `scopeTerminusPct` | uint | none | 94–100; absent is the adaptive terminus |
| 27 | `styleParams` | LEN | style | packed `(token, value × 100)` pairs |
| 28 | `worldTime` | epoch | none (live) | zigzag seconds since 2026-01-01T00:00:00Z |
Field 1 absent means `thing` when field 13 is present and `place` otherwise,
so v1 never writes it; a later view kind writes its code.
@@ -132,6 +133,10 @@ style also extends the pinned style-defaults table.
`hudVariant` code 1 (`operator`) is retired: the app no longer offers that
layout and restores it as `tactical`. The code stays reserved.
Field 28 is the world time the link was made at, written only when it was away
from live. A link without it opens live. Unlike field 8 it may lie ahead of the
link's creation (a forecast moment), and it opens paused at that time.
Flag bits (field 9): 0 `bloom`, 1 `sharpen`, 2 `hudVisible`, 3
`celestialRing` (each defaulting by style), 4 `scopeEnabled` (default on).
+6 -3
View File
@@ -1105,7 +1105,8 @@
"src/ui/kit/UiBadge.vue",
"src/ui/stores/globeActions.js",
"src/ui/stores/recording.js",
"src/ui/i18n.js"
"src/ui/i18n.js",
"src/time/worldClock.js"
],
"external": ["cesium", "vue", "pinia", "vue-i18n"]
},
@@ -3890,7 +3891,8 @@
"src/i18n/locales/ru.json",
"src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json"
"src/i18n/locales/zh-Hant.json",
"src/time/worldClock.js"
],
"external": []
},
@@ -4580,7 +4582,8 @@
"src/data/detectionPolicy.js",
"src/scopeMaskPolicy.js",
"src/i18n/marker.js",
"src/visualStylePolicy.js"
"src/visualStylePolicy.js",
"src/time/worldClock.js"
],
"external": []
},
+2 -1
View File
@@ -52,7 +52,8 @@ const utc = (value) =>
: t('Unavailable');
/** A per-application observation layer, using the existing layer lifecycle and
* row controls. History is transient: shared links always open latest imagery. */
* row controls. Its time is the world clock's, so a link made away from live
* opens its imagery at that time; one made live opens the latest. */
export function createWeatherLayer({
feed,
clock,
+14 -2
View File
@@ -31,6 +31,7 @@ import {
} from '../data/detectionPolicy.js';
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
import { VIEW_STYLES } from '../view/index.js';
import { isWorldTime } from '../time/worldClock.js';
import { RETIRED_STYLES } from '../visualStylePolicy.js';
import {
GLOBAL_POST_DEFAULTS,
@@ -373,6 +374,7 @@ export const SHARE_VIEW_FIELDS = freezeAll([
{ number: 25, key: 'scopeFeatherPct', type: 'uint', default: 100 },
{ number: 26, key: 'scopeTerminusPct', type: 'uint', default: null },
{ number: 27, key: 'styleParams', type: 'bytes', default: BY_STYLE },
{ number: 28, key: 'worldTime', type: 'epoch', default: null },
]);
const FIELD_BY_KEY = new Map(SHARE_VIEW_FIELDS.map((f) => [f.key, f]));
const fieldNumber = (key) => FIELD_BY_KEY.get(key).number;
@@ -732,6 +734,9 @@ export function normalizeShareViewState(input = {}) {
: null;
state.panelState = normalizePanelState(source.panelState);
state.sharedAtMs = epochMsOr(source.sharedAtMs);
state.worldTime = isWorldTime(source.worldTime)
? Math.floor(source.worldTime / 1000) * 1000
: null;
let view = normalizeView(source.view);
if (state.layerState) view = liftTrackedEntity(state.layerState, view);
if (view.kind === 'thing') {
@@ -948,8 +953,9 @@ export function encodeShareView(input) {
writer.sintField(field.number, signedAngle(state[field.key]));
break;
case 'sharedAtMs':
if (state.sharedAtMs !== null)
writer.sintField(field.number, epochSeconds(state.sharedAtMs));
case 'worldTime':
if (state[field.key] !== null)
writer.sintField(field.number, epochSeconds(state[field.key]));
break;
case 'flags': {
let bits = 0;
@@ -1347,6 +1353,12 @@ export function decodeShareView(code, { nowMs = Date.now() } = {}) {
raw.sharedAtMs =
createdAtS >= 1 && Number.isSafeInteger(ms) && ms <= nowMs ? ms : null;
}
// The world time the link was made at, when it was away from live. It
// may lie ahead of the link's creation (a forecast) or of now.
if (get('worldTime')) {
const ms = (SHARE_VIEW_EPOCH_S + unz(get('worldTime').varint)) * 1000;
raw.worldTime = isWorldTime(ms) ? ms : null;
}
if (get('layerState'))
raw.layerState = decodeLayers(
+21
View File
@@ -361,6 +361,7 @@ test('every registered field round-trips at once', () => {
})),
},
sharedAtMs: NOW_MS,
worldTime: NOW_MS - 6 * 3_600_000,
};
// A thing view derives its camera (roll 0), so the camera fields are
// checked on the same state as a place view.
@@ -501,6 +502,7 @@ test('the field registry is well formed and permanent', () => {
[25, 'scopeFeatherPct', 100],
[26, 'scopeTerminusPct', null],
[27, 'styleParams', BY_STYLE],
[28, 'worldTime', null],
],
);
assert.deepEqual(
@@ -1338,3 +1340,22 @@ test('a layer option filed under a mirror layer is ignored', () => {
{ scope: 'layer-option', code: `${military}.${layerTokenNumber('m')}` },
]);
});
test('the world time is written only away from live, and may lie ahead', () => {
const at = (worldTime) =>
decodeShareView(encodeShareView({ sharedAtMs: NOW_MS, worldTime }), {
nowMs: NOW_MS,
}).state.worldTime;
assert.equal(at(null), null);
assert.equal(at(NOW_MS - 3_600_000), NOW_MS - 3_600_000);
// A forecast moment ahead of the link's creation is kept.
assert.equal(at(NOW_MS + 86_400_000), NOW_MS + 86_400_000);
// Before the 2026 epoch too: the field is zigzag.
assert.equal(at(Date.UTC(2024, 0, 1)), Date.UTC(2024, 0, 1));
// Past what a Date can hold: dropped, so the link opens live.
assert.equal(at(8.8e15), null);
assert.ok(
encodeShareView({ sharedAtMs: NOW_MS, worldTime: NOW_MS - 1000 }).length >
encodeShareView({ sharedAtMs: NOW_MS }).length,
);
});
+11 -2
View File
@@ -5,6 +5,7 @@ import {
normalizeAllocationStrategy,
} from './data/detectionPolicy.js';
import { clampScopeTerminusPct } from './scopeMask.js';
import { worldClock as pageWorldClock } from './time/worldClock.js';
import { clampScopeFeatherPct } from './scopeMaskPolicy.js';
import {
decodeLayerStateParams,
@@ -68,9 +69,11 @@ export class ShareLinkManager {
isNavigationCurrent,
cancelOwnedNavigation,
initialShareCode = null,
worldClock = pageWorldClock,
} = {},
) {
this.viewer = viewer;
this._worldClock = worldClock;
// A host may hand over the share code to open instead of the address.
this._initialShareCode =
typeof initialShareCode === 'string' ? initialShareCode : null;
@@ -163,8 +166,9 @@ export class ShareLinkManager {
/**
* The restore state for a share code, in the long form's parsed shape plus
* `shareView` (what the view follows, with its offset and last-known fix),
* or null when the code is refused.
* `shareView` (what the view follows, with its offset and last-known fix)
* and `worldTime` (null when the link was made live), or null when the
* code is refused.
*/
_parseShareCode(code) {
const result = decodeShareView(code);
@@ -174,6 +178,7 @@ export class ShareLinkManager {
}
const state = toLegacyShareState(result.state);
state.shareView = result.state.view;
state.worldTime = result.state.worldTime;
return state;
}
@@ -592,6 +597,10 @@ export class ShareLinkManager {
// that state in the share format.
const state = fromLegacyShareState(this._parseShareHash(params.toString()));
state.sharedAtMs = Math.floor(nowMs / 1000) * 1000;
// Away from live, the link opens at the same world time.
state.worldTime = this._worldClock.isLive()
? null
: Math.floor(this._worldClock.now() / 1000) * 1000;
let subject = null;
try {
subject = this._followSubjectProvider?.({ nowMs }) ?? null;
+25
View File
@@ -423,3 +423,28 @@ test('the layer coordinator replaces in-flight share layers with the navigated s
);
coordinator.destroy();
});
test('a link made away from live carries its world time to the page that opens it', (t) => {
installTab(t, SHARE_A);
const worldTime = Date.UTC(2026, 9, 30, 6, 15, 42);
const sharer = new ShareLinkManager(fakeViewer(), {
worldClock: { isLive: () => false, now: () => worldTime + 400 },
});
const link = sharer.buildLink({ nowMs: Date.UTC(2026, 10, 1) });
sharer.destroy();
const code = new URL(link).pathname.split('/').at(-1);
const opener = new ShareLinkManager(fakeViewer(), { initialShareCode: code });
assert.equal(opener.parseInitialHash().worldTime, worldTime);
opener.destroy();
const live = new ShareLinkManager(fakeViewer(), {
worldClock: { isLive: () => true, now: () => worldTime },
});
const liveCode = new URL(live.buildLink()).pathname.split('/').at(-1);
live.destroy();
const liveOpener = new ShareLinkManager(fakeViewer(), {
initialShareCode: liveCode,
});
assert.equal(liveOpener.parseInitialHash().worldTime, null);
liveOpener.destroy();
});
+12
View File
@@ -4,6 +4,16 @@ import { canPresentDeferredStatusNotice } from '../loadingFeedback.js';
import { t, msg, formatDate } from '../i18n/index.js';
import { SHARE_VIEW_ENTITY_KINDS } from '../share/follow.js';
import { UiLifetime } from './uiLifetime.js';
import { isWorldTime, worldClock } from '../time/worldClock.js';
// A link made away from live opens at its world time, paused. One without a
// world time leaves the clock as it is: a page opens live, and Back or Forward
// through long-form hashes (which never carry one) does not stop a replay.
function applySharedWorldTime(state) {
if (!isWorldTime(state?.worldTime)) return;
worldClock.pause();
worldClock.seek(state.worldTime);
}
/** How long a shared vessel may take to appear in the vessel feed. */
export const SHARED_VESSEL_WAIT_MS = 120_000;
@@ -146,6 +156,7 @@ export class ShareRestoration {
: Promise.resolve({ status: 'not-requested', share: null, layers: [] });
if (savedState) {
this._hasShareState = true;
applySharedWorldTime(savedState);
// Reserve camera authority now; the delayed mesh-friendly flight may
// run only if no newer user, voice, or tracking navigation has won.
this._initialShareNavigationGeneration =
@@ -301,6 +312,7 @@ export class ShareRestoration {
layers: [],
});
this._initialShareState = state;
applySharedWorldTime(state);
this._initialShareSelectionSuperseded = false;
this._hasShareState = true;
this._navigatedShareRestoreActive = true;