mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 02:14:01 +00:00
Remove the Snow visual preset
Snow is no longer offered anywhere a style can be chosen: the Visual Presets tray, keyboard shortcut 7, the Cockpit vision carousel, and the set_visual_style voice action. The shader and its labels go with it; weather snowfall and the SNOW weather condition are unaffected. Share links keep working. Snow's enum code (6) stays reserved, so a compact or long-form link that names it opens on Normal and drops the Snow shader parameters without reporting them as unknown. A Scene Director shot saved with Snow plays on Normal too. Snow's frozen share defaults matched Normal's apart from those parameters, so no other setting in an old link changes meaning. The legacy voice-schema pins restore Snow before hashing, so the unchanged pins still prove nothing else moved; the complete Realtime payload digest is re-derived.
This commit is contained in:
1 parent
aaa3ff46cf
commit
39a60b4034
44 files changed
+200
-259
No files matched your search
@@ -61,7 +61,7 @@ Start with the included data sources, then add your own. Each layer is a separat
|
||||
- **🎯 Click-to-track anything:** Camera locks on, draws a fading trail, surfaces full metadata — and a tracked fire or vessel hands you off to the nearest live camera in one click.
|
||||
- **🖊️ Voice whiteboard:** Speak annotations onto the world — real boundary polygons, marks, and routes.
|
||||
- **🛫 3D hangar:** Real per-class aircraft models — 787, ATR-72, Citation, Bell 206, MQ-9 — and a tracked contact swaps from glyph to 3D model as you close in.
|
||||
- **🎨 Reskin reality:** GLSL sensor looks over the normal globe — CRT, NVG, FLIR/thermal, Noir, Snow.
|
||||
- **🎨 Reskin reality:** GLSL sensor looks over the normal globe — CRT, NVG, FLIR/thermal, Noir.
|
||||
- **🟩 Detection overlay:** Screen-space bounding boxes and IDs on everything in view.
|
||||
- **🎖️ Military HUD:** Tactical heads-up display with intelligence-style telemetry.
|
||||
- **🌐 Global Context:** Stage the full situational picture with one switch — and get your exact view back when you leave.
|
||||
|
||||
@@ -1392,8 +1392,8 @@ and sharpen. Display supplies actions and renders settings; effect execution has
|
||||
no DOM dependency. Existing presets and the 500 ms transition remain unchanged.
|
||||
Stopping the controller revokes animation before asynchronous UI teardown; final
|
||||
destruction removes its stages and restores the borrowed bloom configuration.
|
||||
Snow's sideways sway stays within a fixed range at any session age; Wind scales
|
||||
its amplitude.
|
||||
Snow is no longer a visual preset. A share link that names it, in either the
|
||||
long or compact form, opens on Normal and drops the Snow shader parameters.
|
||||
Sharpen runs only when enabled with intensity above 0.001. At slider zero its
|
||||
stage is disabled; raising the slider resumes it when sharpen is enabled.
|
||||
|
||||
@@ -2831,7 +2831,7 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
|
||||
> failed searches leave the current camera owner untouched, and Context Focus
|
||||
> can return to the preserved Contact. Focus also restores the selected
|
||||
> aircraft's canonical follow frame after a manual zoom-away.
|
||||
> Visual presets retain the order Normal, CRT, NVG, FLIR, Anime, Noir, Snow.
|
||||
> Visual presets retain the order Normal, CRT, NVG, FLIR, Anime, Noir.
|
||||
> Configurable preset selection—including same-style reselection—opens the
|
||||
> shared Parameters surface directly below Detection and scrolls it into
|
||||
> view; share-link restoration
|
||||
@@ -2883,7 +2883,7 @@ down | auth-failed` (plus the unchanged `missing-key`/`unsupported`) with
|
||||
> Inside Cockpit, the focused summary card is titled Contact in both visible
|
||||
> copy and its accessible control labels.
|
||||
> The top-center Cockpit vision cycle is the fixed, duplicate-free sequence
|
||||
> Normal, CRT, NVG, FLIR, Anime, Noir, and Snow. Entry selects the map preset
|
||||
> Normal, CRT, NVG, FLIR, Anime, and Noir. Entry selects the map preset
|
||||
> already in use, so a FLIR map opens on the existing FLIR item while
|
||||
> Normal remains available exactly once. NONE is not offered. Cockpit choices
|
||||
> temporarily override the map preset, while Exit Cockpit and Reset both restore
|
||||
@@ -5172,8 +5172,8 @@ and unreachable upstream (502/504) separately from road geometry.
|
||||
first, activates the Contacts sub-view, and returns the settled 250 km window.
|
||||
Its `aircraft` count is the exact civilian-plus-military total when both feeds
|
||||
can answer, or `unknown` when either component is unavailable.
|
||||
- Cockpit's top vision switch cycles all seven map looks: Normal, CRT, NVG,
|
||||
FLIR, Anime, Noir, and Snow. Entry selects the matching map style without adding
|
||||
- Cockpit's top vision switch cycles all six map looks: Normal, CRT, NVG,
|
||||
FLIR, Anime, and Noir. Entry selects the matching map style without adding
|
||||
a duplicate carousel item. There is no empty `NONE` entry.
|
||||
|
||||
### Live AIS Vessels (June 2026)
|
||||
@@ -5659,7 +5659,7 @@ apply to `public/` assets or built-preview serving.
|
||||
- **Cockpit left-panel clearance:** the Cockpit Contact card and peripheral HUD participate in the adaptive left accordion's live obstacle measurements, including live viewport-height changes. Expanding Layers or Scenes keeps the active panel in the available upper-left corridor with internal scrolling; it does not cover the Contact card, lower Cockpit controls, or Cesium credit line. Outside Cockpit the hidden card does not alter the normal corridor.
|
||||
- **Cockpit Context scope:** the 250 km radius applies to the air/sea proximity cohorts. Installation counts come only from the currently loaded viewport and are labeled `CURRENT VIEWPORT ONLY` in the cockpit as well as the normal Context panel; neither surface presents them as a complete 250 km installation survey.
|
||||
- **Cockpit camera anchor:** first-person mode does not write feed-boundary corrections directly into the camera. A cockpit-only inertial anchor advances from the selected aircraft's displayed course and speed, then converges toward the authoritative delayed track with correction capped below forward motion. The displayed kinematics are derived from the same consecutive fix segment as the rendered position, with raw feed speed/course used only as fallback; a transient zero/missing feed speed therefore cannot freeze a visibly moving aircraft after layer enable or a map/cockpit handoff. Rendered altitude continues to come from that interpolated track position. Late ADS-B fixes and short render stalls can remove drift without accelerating or reversing the view. Camera placement runs before scene update/culling at a bounded 20 Hz so a moving cockpit does not force Photoreal 3D Tiles to retraverse on every display frame; textual instruments update at 10 Hz and context/layout work at 4 Hz. Every far Cockpit contact pip shares one stable Cesium texture-atlas entry and skips unused screen-projected course calculations, while only in-range 2D aircraft silhouettes pay the screen-projected rotation cost; ambient glTF collections are hidden/retained rather than synchronously destroyed at cockpit entry, and context rails lay out only on explicit content/state changes and viewport resize. The deliberate 15/30-second layer interpolation delays and per-Cesium-frame position caches remain unchanged.
|
||||
- **Cockpit route, vision, and view controls:** visible on-screen `COCKPIT`, `RESET`, and `EXIT COCKPIT` controls replace reliance on the `C` shortcut. RESET uses the same canonical globe route as the map and voice actions, exits Cockpit, and releases its camera ownership rather than exposing the hidden map-style top action. When the tracked commercial flight has a plausible ADSBDB route, the top of the right briefing rail shows a compact `FROM → TO` airport strip and the visor shows a centered estimated-destination chevron with its relative bearing; its destination label rounds to whole degrees in 000–359, wrapping rounded north to 000° while the chevron continues to use the relative bearing independently of label rounding; absent or implausible route data hides the strip and cue rather than guessing. The cockpit-local vision control is an interactive `PREV / CURRENT / NEXT` carousel over `NORMAL`, `CRT`, `NVG`, `FLIR`, `ANIME`, `NOIR`, and `SNOW`; its previous/next actions wrap, and activating the current value advances to the next style. Cockpit enters on the active map preset, so a Cyber FLIR map opens Cockpit on FLIR while Normal remains a distinct option instead of producing a second FLIR entry. Cockpit choices temporarily activate the existing Cesium post-process stages, while exiting Cockpit through either EXIT or RESET restores the pre-entry map style. The regional-news page uses a free Google News RSS locality query first, with the existing GDELT query retained only as a fail-soft fallback; linked headlines remain reporting, not verified incidents or risk intelligence.
|
||||
- **Cockpit route, vision, and view controls:** visible on-screen `COCKPIT`, `RESET`, and `EXIT COCKPIT` controls replace reliance on the `C` shortcut. RESET uses the same canonical globe route as the map and voice actions, exits Cockpit, and releases its camera ownership rather than exposing the hidden map-style top action. When the tracked commercial flight has a plausible ADSBDB route, the top of the right briefing rail shows a compact `FROM → TO` airport strip and the visor shows a centered estimated-destination chevron with its relative bearing; its destination label rounds to whole degrees in 000–359, wrapping rounded north to 000° while the chevron continues to use the relative bearing independently of label rounding; absent or implausible route data hides the strip and cue rather than guessing. The cockpit-local vision control is an interactive `PREV / CURRENT / NEXT` carousel over `NORMAL`, `CRT`, `NVG`, `FLIR`, `ANIME`, and `NOIR`; its previous/next actions wrap, and activating the current value advances to the next style. Cockpit enters on the active map preset, so a Cyber FLIR map opens Cockpit on FLIR while Normal remains a distinct option instead of producing a second FLIR entry. Cockpit choices temporarily activate the existing Cesium post-process stages, while exiting Cockpit through either EXIT or RESET restores the pre-entry map style. The regional-news page uses a free Google News RSS locality query first, with the existing GDELT query retained only as a fail-soft fallback; linked headlines remain reporting, not verified incidents or risk intelligence.
|
||||
- Cockpit wind-direction labels wrap after rounding to whole degrees, so a direction of 359.5° displays as `N · 0°` rather than `N · 360°`.
|
||||
- **Cockpit missing observations:** missing local weather observations remain unknown rather than being converted to zero or clear skies, in the briefing and in the cloud effects; a missing temperature drops the whole local weather record, including its wind and condition.
|
||||
- **Cockpit weather status:** the earlier multi-canvas atmospheric compositor remains fail-closed and is not attached to the live viewer. Cockpit clouds are a separate transparent WebGL pass with a capped 520×320 framebuffer, 24 ray steps, three FBM octaves, and a 12 FPS ceiling. It defaults off and starts only when local storage explicitly contains the persisted `WX ON` opt-in (`'1'`). When opted in, observations refresh after five minutes or 25 km of aircraft movement, fail transparent when unavailable or clear, and stop on exit or disable. `WX OFF` governs atmospheric rendering only: the briefing still fetches source-backed Natural Earth region, headline, and Open-Meteo local-information data, aborting and replacing any in-flight request when the selected aircraft changes. No weather effect runs in map mode and no synthetic fallback is shown.
|
||||
|
||||
+2
-1
@@ -97,7 +97,8 @@ operational sample contained 3,909 observations and two selected labels, but its
|
||||
live aircraft and traffic rows were empty, so it remains a limited stress case.
|
||||
|
||||
Snow, Noir, dense detection, and text-heavy combined layers are the clearest
|
||||
controlled comparison points for later optimization work.
|
||||
controlled comparison points for later optimization work. Snow has since been
|
||||
retired as a visual preset; its row records the measurement as taken.
|
||||
|
||||
## Keyed live sources
|
||||
|
||||
|
||||
+33
-31
@@ -90,35 +90,35 @@ probability of about 255 in 256 (many of them already fail as base64url).
|
||||
|
||||
## Field registry (top level)
|
||||
|
||||
| # | State key | Type | Default | Encoding |
|
||||
| --- | ---------------------------- | ----- | ----------- | ------------------------------------------------------- |
|
||||
| 1 | `view.kind` | enum | see below | 0 place, 1 thing |
|
||||
| 2 | `lat` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 3 | `lon` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 4 | `alt` | sint | 800 | whole metres |
|
||||
| 5 | `heading` | angle | 0 | whole degrees, see angles |
|
||||
| 6 | `pitch` | sint | −35 | whole degrees |
|
||||
| 7 | `roll` | angle | 0 | whole degrees |
|
||||
| 8 | `sharedAtMs` | epoch | none | zigzag seconds since 2026-01-01T00:00:00Z |
|
||||
| 9 | booleans | flags | 0 | bit set when the value differs from default |
|
||||
| 10 | `layerState.enabledLayerIds` | LEN | none | layer bitset, see layers |
|
||||
| 11 | `layerState.options` | LEN | none | packed option triples, see layers |
|
||||
| 12 | `panelState` | LEN | none | packed panel entries, see panels |
|
||||
| 13 | thing | LEN | none | sub-message, see thing views |
|
||||
| 14 | `style` | enum | `normal` | normal, retro, surveillance, thermal, anime, noir, snow |
|
||||
| 15 | `mapStack` | enum | `photoreal` | photoreal, bing-aerial, bing-labels, esri-imagery, osm |
|
||||
| 16 | `hudVariant` | enum | style | tactical, operator, minimal, cyber |
|
||||
| 17 | `bloomIntensity` | uint | style | |
|
||||
| 18 | `bloomVersion` | uint | 2 | |
|
||||
| 19 | `sharpenIntensity` | uint | style | |
|
||||
| 20 | `detectionMode` | enum | style | OFF, SPARSE, BALANCED, DENSE |
|
||||
| 21 | `detectionDensity` | uint | style | one of 0, 25, 50, 75, 100 |
|
||||
| 22 | `detectionAllocation` | enum | style | ELASTIC, WEIGHTED |
|
||||
| 23 | `detectionFadePct` | uint | style | 0–40 |
|
||||
| 24 | `detectionOutsideOpacityPct` | uint | style | 0–100 |
|
||||
| 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 |
|
||||
| # | State key | Type | Default | Encoding |
|
||||
| --- | ---------------------------- | ----- | ----------- | ----------------------------------------------------------------- |
|
||||
| 1 | `view.kind` | enum | see below | 0 place, 1 thing |
|
||||
| 2 | `lat` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 3 | `lon` | coord | 0 | zigzag of degrees × 10⁴ |
|
||||
| 4 | `alt` | sint | 800 | whole metres |
|
||||
| 5 | `heading` | angle | 0 | whole degrees, see angles |
|
||||
| 6 | `pitch` | sint | −35 | whole degrees |
|
||||
| 7 | `roll` | angle | 0 | whole degrees |
|
||||
| 8 | `sharedAtMs` | epoch | none | zigzag seconds since 2026-01-01T00:00:00Z |
|
||||
| 9 | booleans | flags | 0 | bit set when the value differs from default |
|
||||
| 10 | `layerState.enabledLayerIds` | LEN | none | layer bitset, see layers |
|
||||
| 11 | `layerState.options` | LEN | none | packed option triples, see layers |
|
||||
| 12 | `panelState` | LEN | none | packed panel entries, see panels |
|
||||
| 13 | thing | LEN | none | sub-message, see thing views |
|
||||
| 14 | `style` | enum | `normal` | normal, retro, surveillance, thermal, anime, noir, snow (retired) |
|
||||
| 15 | `mapStack` | enum | `photoreal` | photoreal, bing-aerial, bing-labels, esri-imagery, osm |
|
||||
| 16 | `hudVariant` | enum | style | tactical, operator, minimal, cyber |
|
||||
| 17 | `bloomIntensity` | uint | style | |
|
||||
| 18 | `bloomVersion` | uint | 2 | |
|
||||
| 19 | `sharpenIntensity` | uint | style | |
|
||||
| 20 | `detectionMode` | enum | style | OFF, SPARSE, BALANCED, DENSE |
|
||||
| 21 | `detectionDensity` | uint | style | one of 0, 25, 50, 75, 100 |
|
||||
| 22 | `detectionAllocation` | enum | style | ELASTIC, WEIGHTED |
|
||||
| 23 | `detectionFadePct` | uint | style | 0–40 |
|
||||
| 24 | `detectionOutsideOpacityPct` | uint | style | 0–100 |
|
||||
| 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 |
|
||||
|
||||
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.
|
||||
@@ -163,7 +163,7 @@ for it.) In v1:
|
||||
| thermal | sensitivity 0.85, bloom 0.2, mode 0.33, pixelation 1, palette 0 |
|
||||
| anime | saturation 1, edgeThick 0.5 |
|
||||
| noir | contrastAmt 1.2, grainAmt 0.5, vignetteAmt 0.5 |
|
||||
| snow | density 0.6, wind 0.5 |
|
||||
| snow | retired: decodes as normal; its parameters are dropped |
|
||||
|
||||
These are frozen like every other default: the codec reads them from the
|
||||
app's tables today, and a test pins them, so if a table moves the old values
|
||||
@@ -342,7 +342,9 @@ the app that can open it.
|
||||
values in the codec (see Style defaults). The same applies
|
||||
to `SHARE_VIEW_LAYER_OPTION_ABSENT` and the panel defaults.
|
||||
4. **Codes are append-only.** Enum lists, entity kinds, flag bits, panel
|
||||
numbers and style parameter tokens only grow.
|
||||
numbers and style parameter tokens only grow. A style the app no longer
|
||||
offers keeps its code: `snow` (6) is retired, so a link that names it
|
||||
opens as Normal and the parameters written for it are dropped.
|
||||
5. **Add, don't change.** A new setting takes the next free field number
|
||||
(28 onwards; fields 1–15 have one-byte tags and are all taken). A new
|
||||
boolean takes the next flag bit instead, which costs nothing when it is at
|
||||
|
||||
@@ -212,7 +212,6 @@
|
||||
"src/styles/thermal.js",
|
||||
"src/styles/anime.js",
|
||||
"src/styles/noir.js",
|
||||
"src/styles/snow.js",
|
||||
"scripts/qa-visual-effects.mjs",
|
||||
"scripts/qa-draw-tool.mjs",
|
||||
"scripts/qa-geo-import.mjs",
|
||||
|
||||
@@ -463,7 +463,6 @@
|
||||
"src/styles/thermal.js",
|
||||
"src/styles/anime.js",
|
||||
"src/styles/noir.js",
|
||||
"src/styles/snow.js",
|
||||
"src/i18n/marker.js"
|
||||
],
|
||||
"external": ["cesium"]
|
||||
@@ -697,7 +696,6 @@
|
||||
"src/styles/anime.js",
|
||||
"src/styles/noir.js",
|
||||
"src/styles/retro.js",
|
||||
"src/styles/snow.js",
|
||||
"src/styles/surveillance.js",
|
||||
"src/styles/thermal.js",
|
||||
"src/ui/applicationShell.js",
|
||||
@@ -2240,7 +2238,6 @@
|
||||
"src/styles/anime.js",
|
||||
"src/styles/noir.js",
|
||||
"src/styles/retro.js",
|
||||
"src/styles/snow.js",
|
||||
"src/styles/surveillance.js",
|
||||
"src/styles/thermal.js",
|
||||
"src/ui/applicationShell.js",
|
||||
@@ -3347,7 +3344,6 @@
|
||||
"src/styles/anime.js",
|
||||
"src/styles/noir.js",
|
||||
"src/styles/retro.js",
|
||||
"src/styles/snow.js",
|
||||
"src/styles/surveillance.js",
|
||||
"src/styles/thermal.js",
|
||||
"src/ui/applicationShortcuts.js",
|
||||
@@ -3937,7 +3933,6 @@
|
||||
"src/styles/anime.js",
|
||||
"src/styles/noir.js",
|
||||
"src/styles/retro.js",
|
||||
"src/styles/snow.js",
|
||||
"src/styles/surveillance.js",
|
||||
"src/styles/thermal.js",
|
||||
"src/view/index.js",
|
||||
|
||||
@@ -1603,7 +1603,7 @@ try {
|
||||
cockpit.setVisionMode('optical');
|
||||
current.focus({ preventScroll: true });
|
||||
const states = [read()];
|
||||
for (let index = 0; index < 7; index += 1) {
|
||||
for (let index = 0; index < 6; index += 1) {
|
||||
current.click();
|
||||
states.push(read());
|
||||
}
|
||||
@@ -1637,36 +1637,36 @@ try {
|
||||
reentered,
|
||||
};
|
||||
});
|
||||
const cycleModes = ['optical', 'crt', 'nvg', 'thermal', 'anime', 'noir', 'snow', 'optical'];
|
||||
const cycleLabels = ['NORMAL', 'CRT', 'NVG', 'FLIR', 'ANIME', 'NOIR', 'SNOW', 'NORMAL'];
|
||||
const cycleModes = ['optical', 'crt', 'nvg', 'thermal', 'anime', 'noir', 'optical'];
|
||||
const cycleLabels = ['NORMAL', 'CRT', 'NVG', 'FLIR', 'ANIME', 'NOIR', 'NORMAL'];
|
||||
check(
|
||||
'Cockpit vision control exposes Normal, CRT, NVG, FLIR, Anime, Noir and Snow before wrapping',
|
||||
'Cockpit vision control exposes Normal, CRT, NVG, FLIR, Anime and Noir before wrapping',
|
||||
JSON.stringify(visionCycle.states.map((state) => state.mode)) === JSON.stringify(cycleModes)
|
||||
&& JSON.stringify(visionCycle.states.map((state) => state.label)) === JSON.stringify(cycleLabels)
|
||||
&& visionCycle.states[5]?.aria === 'Current cockpit vision style: Noir. Activate for next style.'
|
||||
&& visionCycle.states[7]?.aria === 'Current cockpit vision style: Normal. Activate for next style.',
|
||||
&& visionCycle.states[6]?.aria === 'Current cockpit vision style: Normal. Activate for next style.',
|
||||
JSON.stringify(visionCycle.states.map(({ mode, label, aria }) => ({ mode, label, aria }))),
|
||||
);
|
||||
check(
|
||||
'user vision changes open Display for every temporary treatment and retain selector focus',
|
||||
visionCycle.states.slice(1, 7).every((state) => state.displayOpen === 'true')
|
||||
&& visionCycle.states.slice(1, 7).every((state) => state.radioOpen === 'false')
|
||||
&& visionCycle.states.slice(1, 7).every((state) => state.parametersActive)
|
||||
&& visionCycle.states.slice(1, 7).every((state) => !state.parametersCollapsed)
|
||||
&& visionCycle.states.slice(1, 7).every((state) => state.signalCollapsed)
|
||||
visionCycle.states.slice(1, 6).every((state) => state.displayOpen === 'true')
|
||||
&& visionCycle.states.slice(1, 6).every((state) => state.radioOpen === 'false')
|
||||
&& visionCycle.states.slice(1, 6).every((state) => state.parametersActive)
|
||||
&& visionCycle.states.slice(1, 6).every((state) => !state.parametersCollapsed)
|
||||
&& visionCycle.states.slice(1, 6).every((state) => state.signalCollapsed)
|
||||
&& visionCycle.states.every((state) => state.focusRetained),
|
||||
JSON.stringify(visionCycle.states),
|
||||
);
|
||||
check(
|
||||
'each Cockpit vision mode drives only its own shader and Normal clears them all',
|
||||
visionCycle.states.slice(1, 7).every((state) => {
|
||||
visionCycle.states.slice(1, 6).every((state) => {
|
||||
const target = {
|
||||
crt: 'retro', nvg: 'surveillance', thermal: 'thermal', anime: 'anime', noir: 'noir', snow: 'snow',
|
||||
crt: 'retro', nvg: 'surveillance', thermal: 'thermal', anime: 'anime', noir: 'noir',
|
||||
}[state.mode];
|
||||
return Object.entries(state.intensities).every(([name, value]) =>
|
||||
name === target ? value > 0 : value === 0);
|
||||
})
|
||||
&& Object.values(visionCycle.states[7].intensities).every((value) => value === 0),
|
||||
&& Object.values(visionCycle.states[6].intensities).every((value) => value === 0),
|
||||
JSON.stringify(visionCycle.states.map(({ mode, intensities }) => ({ mode, intensities }))),
|
||||
);
|
||||
check(
|
||||
@@ -2317,13 +2317,13 @@ try {
|
||||
const styleManager = window.__godsEyeView.styleManager;
|
||||
const styleBefore = styleManager.activeStyle;
|
||||
// A Cockpit-only choice that Reset must undo.
|
||||
styleManager.cockpitView.setVisionMode('snow');
|
||||
styleManager.cockpitView.setVisionMode('anime');
|
||||
window.__qaCockpitReset = {
|
||||
calls: 0,
|
||||
original: manager.resetToGlobeView,
|
||||
subjectId: awareness?.getContextSnapshot?.()?.subject?.id || null,
|
||||
styleBefore,
|
||||
snowDuringCockpit: styleManager.stages?.snow?.uniforms?.intensity,
|
||||
animeDuringCockpit: styleManager.stages?.anime?.uniforms?.intensity,
|
||||
};
|
||||
manager.resetToGlobeView = function qaCountedCockpitReset(...args) {
|
||||
window.__qaCockpitReset.calls += 1;
|
||||
@@ -2360,7 +2360,7 @@ try {
|
||||
height,
|
||||
subjectPreserved: Boolean(qa.subjectId && subjectId === qa.subjectId),
|
||||
styleRestored: gev.styleManager.activeStyle === qa.styleBefore,
|
||||
snowDuringCockpit: qa.snowDuringCockpit,
|
||||
animeDuringCockpit: qa.animeDuringCockpit,
|
||||
// Cockpit-time intensities are Cockpit's own, so check against the map preset.
|
||||
visionRestored: Object.entries(gev.styleManager.stages || {}).every(([name, stage]) =>
|
||||
name === gev.styleManager.activeStyle
|
||||
@@ -2376,7 +2376,7 @@ try {
|
||||
&& resetState.resetHidden
|
||||
&& Math.abs(resetState.height - 18_000_000) < 150_000
|
||||
&& resetState.subjectPreserved
|
||||
&& resetState.snowDuringCockpit > 0
|
||||
&& resetState.animeDuringCockpit > 0
|
||||
&& resetState.styleRestored
|
||||
&& resetState.visionRestored,
|
||||
JSON.stringify(resetState),
|
||||
|
||||
@@ -200,8 +200,8 @@ const checkResponsiveStyleFocus = async (width) => {
|
||||
await page.focus('#control-panel-toggle');
|
||||
await pressTabs(2); // disclosure -> pin -> Normal
|
||||
}
|
||||
for (const [index, style] of ['normal', 'thermal', 'snow'].entries()) {
|
||||
if (index) await pressTabs(3);
|
||||
for (const [tabs, style] of [[0, 'normal'], [3, 'thermal'], [2, 'noir']]) {
|
||||
if (tabs) await pressTabs(tabs);
|
||||
const state = await styleFocusMetrics();
|
||||
check(`${width} px ${style} keyboard ring is visible and unclipped`,
|
||||
hasVisibleStyleFocus(state, style) && state.selectedStyle === 'normal'
|
||||
@@ -567,13 +567,13 @@ try {
|
||||
// Visual Styles precede the five Map Source tiles. Keep the existing opening
|
||||
// handoff to selected OSM, then use the user's real Shift+Tab path into styles.
|
||||
await pressTabs(5, true);
|
||||
const snowFocus = await styleFocusMetrics();
|
||||
check('Shift+Tab reaches Snow with a visible ring while Normal and OSM stay selected',
|
||||
hasVisibleStyleFocus(snowFocus, 'snow') && !snowFocus.selected
|
||||
&& snowFocus.selectedStyle === 'normal' && snowFocus.selectedMap === 'osm',
|
||||
JSON.stringify(snowFocus));
|
||||
const noirFocus = await styleFocusMetrics();
|
||||
check('Shift+Tab reaches Noir with a visible ring while Normal and OSM stay selected',
|
||||
hasVisibleStyleFocus(noirFocus, 'noir') && !noirFocus.selected
|
||||
&& noirFocus.selectedStyle === 'normal' && noirFocus.selectedMap === 'osm',
|
||||
JSON.stringify(noirFocus));
|
||||
await page.screenshot({ path: path.join(shotsDir, 'keyboard-style-focus.png') });
|
||||
await pressTabs(3, true);
|
||||
await pressTabs(2, true);
|
||||
const middleFocus = await styleFocusMetrics();
|
||||
check('Shift+Tab traverses the middle FLIR style without activating it',
|
||||
hasVisibleStyleFocus(middleFocus, 'thermal') && !middleFocus.selected
|
||||
|
||||
@@ -80,7 +80,6 @@ try {
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
'snow',
|
||||
'normal',
|
||||
]) {
|
||||
await page.evaluate(
|
||||
|
||||
@@ -46,7 +46,7 @@ function realtimeInstructions(
|
||||
// string is the whole rollback.
|
||||
'NAMED VIEWS are shorthand for tool calls you already have — there is no "mode" tool for them. Treat ONLY these as the shorthand: "infrastructure mode" / "the infrastructure view" / "show me global infrastructure" means three set_layer_visibility calls (local-datacenters, local-dams, telegeography-submarine-cables) plus zoom_to_globe; "environmental mode" / "earth watch" / "active events", said as the name of a view, means set_layer_visibility for local-firms and earthquakes plus zoom_to_globe. Anything vaguer is NOT this shorthand — an open-ended question about the world or the news is an ordinary question: answer it, or use analyst_query over the layers already on. Never switch a whole view on to answer a question nobody asked to see. When you do run one, make every call before speaking, then give one confirmation naming the resulting state; if the fires layer comes back unavailable because no FIRMS key is configured, say so plainly — the earthquakes still loaded. "Live contacts" and "space missions" are NOT this pattern: they stay set_context_mode{mode:"contacts"} and set_context_mode{mode:"space-missions"}.',
|
||||
'For visual filter requests, call set_visual_style with one of the allowed style IDs.',
|
||||
'SHIPS vs STYLES: "vessels" is easily heard as "visuals". "Show me vessels", "turn on vessels", "the vessels", "visuals" or "vestibules" said WITHOUT a style name means the ships layer: set_layer_visibility{layerId:"ais-live-vessels"}, exactly as "ships" or "boats" does. Call set_visual_style only when the user names a style (retro/CRT, surveillance/night vision, thermal, anime, noir, snow, normal) or says filter, effect or look. Never turn on a different data layer (such as fires) for a ships request.',
|
||||
'SHIPS vs STYLES: "vessels" is easily heard as "visuals". "Show me vessels", "turn on vessels", "the vessels", "visuals" or "vestibules" said WITHOUT a style name means the ships layer: set_layer_visibility{layerId:"ais-live-vessels"}, exactly as "ships" or "boats" does. Call set_visual_style only when the user names a style (retro/CRT, surveillance/night vision, thermal, anime, noir, normal) or says filter, effect or look. Never turn on a different data layer (such as fires) for a ships request.',
|
||||
'Disambiguation table — basemap vs layer vs style: basemap switching requires an explicit stack name — "Bing aerial" means set_map_stack bing-aerial, "aerial with labels" means bing-labels, "OSM"/"road map" means osm, "Esri"/"Esri imagery" means esri-imagery, "Google 3D"/"photorealistic" means photoreal. Any other mention of "satellite" or "satellites" means the satellites DATA LAYER via set_layer_visibility, never a basemap — except "recent/latest satellite imagery" (the recent-imagery layer) and "weather satellite"/clouds (weather-satellite). "surveillance"/"night vision"/"thermal" are visual STYLES via set_visual_style.',
|
||||
'HUD requests ("hud on/off", "switch to operator/minimal/tactical/cyber layout") use set_hud. Detection requests ("detection on", "dense mode", "balanced mode", "sparse mode", "set density to 25", "use weighted allocation") use set_detection. Density snaps to 0/25/50/75/100 and derives Sparse/Balanced/Dense; panoptic is a legacy alias for Dense.',
|
||||
'Cyber sonar requests use set_cyber_sonar: enabled, rings, rangePct, intensityPct (Power), opacityPct, sectorDeg. For settings questions read get_current_view_state.controls.hud.sonar. Sonar is not detection. Do not silently switch themes, reveal the HUD or enable sonar when changing a slider. If the user explicitly requests Cyber plus sonar settings, call set_hud first, wait for success, then set_cyber_sonar. Confirm returned values; mapSweepActive and contactSweepActive distinguish saved settings from active map effects, including in Cockpit or reduced motion. Do not claim visible contacts solely because enabled is true.',
|
||||
|
||||
@@ -87,7 +87,7 @@ test('circular angle distance remains small across the wrap point', () => {
|
||||
|
||||
test('celestial ring is available only in Normal style', () => {
|
||||
assert.equal(isCelestialRingStyleSupported('normal'), true);
|
||||
for (const style of ['retro', 'surveillance', 'thermal', 'anime', 'noir', 'snow']) {
|
||||
for (const style of ['retro', 'surveillance', 'thermal', 'anime', 'noir']) {
|
||||
assert.equal(isCelestialRingStyleSupported(style), false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -151,7 +151,7 @@ test('Cockpit vision cycle exposes every map preset exactly once', () => {
|
||||
);
|
||||
assert.match(
|
||||
setVisionMode.toString(),
|
||||
/const labels = \{\s*optical: t\('NORMAL'\),\s*crt: t\('CRT'\),\s*nvg: t\('NVG'\),\s*thermal: t\('FLIR'\),\s*anime: t\('ANIME'\),\s*noir: t\('NOIR'\),\s*snow: t\('SNOW'\),?\s*\};/,
|
||||
/const labels = \{\s*optical: t\('NORMAL'\),\s*crt: t\('CRT'\),\s*nvg: t\('NVG'\),\s*thermal: t\('FLIR'\),\s*anime: t\('ANIME'\),\s*noir: t\('NOIR'\),?\s*\};/,
|
||||
);
|
||||
assert.doesNotMatch(setVisionMode.toString(), /none: 'NONE'/);
|
||||
assert.doesNotMatch(ui, /getInheritedVisionLabel/);
|
||||
|
||||
@@ -5,7 +5,6 @@ export const COCKPIT_VISION_MODES = Object.freeze([
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
'snow',
|
||||
]);
|
||||
|
||||
const TARGET_STYLE_BY_MODE = Object.freeze({
|
||||
@@ -14,7 +13,6 @@ const TARGET_STYLE_BY_MODE = Object.freeze({
|
||||
thermal: 'thermal',
|
||||
anime: 'anime',
|
||||
noir: 'noir',
|
||||
snow: 'snow',
|
||||
});
|
||||
|
||||
const COCKPIT_MODE_BY_STYLE = Object.freeze({
|
||||
@@ -24,7 +22,6 @@ const COCKPIT_MODE_BY_STYLE = Object.freeze({
|
||||
thermal: 'thermal',
|
||||
anime: 'anime',
|
||||
noir: 'noir',
|
||||
snow: 'snow',
|
||||
});
|
||||
|
||||
/** Normalize a requested Cockpit vision mode to the unfiltered Normal entry. */
|
||||
|
||||
@@ -23,7 +23,6 @@ test('Cockpit vision order mirrors the unique map preset order', () => {
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
'snow',
|
||||
]);
|
||||
assert.equal(normalizeCockpitVisionMode('none'), 'optical');
|
||||
assert.equal(normalizeCockpitVisionMode('unknown'), 'optical');
|
||||
@@ -36,7 +35,7 @@ test('Cockpit entry selects the vision mode equivalent to the active map style',
|
||||
assert.equal(cockpitVisionModeForStyle('thermal'), 'thermal');
|
||||
assert.equal(cockpitVisionModeForStyle('anime'), 'anime');
|
||||
assert.equal(cockpitVisionModeForStyle('noir'), 'noir');
|
||||
assert.equal(cockpitVisionModeForStyle('snow'), 'snow');
|
||||
assert.equal(cockpitVisionModeForStyle('snow'), 'optical');
|
||||
assert.equal(cockpitVisionModeForStyle('unsupported'), 'optical');
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
* jam keeps a size bump so queues read as fat beads.
|
||||
* - `crt` (retro): saturated primaries that survive 10-level posterization
|
||||
* plus an all-bucket size boost to out-shout the pixel grid.
|
||||
* - `normal` (normal/anime/snow/unknown): every lookup returns null/0 —
|
||||
* - `normal` (normal/anime/unknown): every lookup returns null/0 —
|
||||
* the shipped palette and sizing apply untouched.
|
||||
*
|
||||
* Sim/uncovered dots (bucket null/'sim') are NEVER restyled under any
|
||||
|
||||
@@ -13,7 +13,7 @@ const luma = ([r, g, b]) => (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
||||
|
||||
const BUCKETS = ['free', 'slow', 'jam'];
|
||||
const MONO_STYLES = ['surveillance', 'thermal', 'noir'];
|
||||
const NORMAL_STYLES = ['normal', 'anime', 'snow', 'does-not-exist', '', undefined, null];
|
||||
const NORMAL_STYLES = ['normal', 'anime', 'does-not-exist', '', undefined, null];
|
||||
|
||||
test('profile mapping: NVG/FLIR/noir are mono, retro is crt, everything else normal', () => {
|
||||
for (const s of MONO_STYLES) assert.equal(trafficStyleProfile(s), 'mono', s);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* black sprite with a light ring is what a hot object looks like there.
|
||||
* - `crt` (retro): saturated per-mode colours that survive posterisation,
|
||||
* a size boost to out-shout the pixel grid, and a thinner halo.
|
||||
* - `normal` (normal/anime/snow/unknown): every lookup returns the shipped
|
||||
* - `normal` (normal/anime/unknown): every lookup returns the shipped
|
||||
* value — mode colour, shipped size, hairline edge only.
|
||||
*
|
||||
* Brackets are a different matter. The detection canvas composites ABOVE the
|
||||
|
||||
@@ -17,7 +17,6 @@ test('profile mapping: NVG/FLIR/noir and cockpit nvg are mono, retro is crt, eve
|
||||
for (const plain of [
|
||||
'normal',
|
||||
'anime',
|
||||
'snow',
|
||||
'minecraft',
|
||||
'',
|
||||
null,
|
||||
|
||||
@@ -1040,6 +1040,19 @@ test('the voice TOOL SCHEMA matches the pinned contract — the mission mapping
|
||||
'rotate_globe',
|
||||
'stop',
|
||||
]);
|
||||
// Snow is retired from set_visual_style; restore it to keep the legacy pin.
|
||||
const visualStyle = legacyTools.find(
|
||||
(tool) => tool.name === 'set_visual_style',
|
||||
).parameters.properties.style;
|
||||
assert.deepEqual(visualStyle.enum, [
|
||||
'normal',
|
||||
'retro',
|
||||
'surveillance',
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
]);
|
||||
visualStyle.enum = [...visualStyle.enum, 'snow'];
|
||||
const block = JSON.stringify(legacyTools);
|
||||
// Re-derived for the voice layer manifest (generated layer enums, alias and
|
||||
// field hints), point-and-ask's pointer/referent arguments and the prompt
|
||||
|
||||
@@ -76,7 +76,6 @@ const MODE_LABELS = Object.freeze({
|
||||
thermal: msg('FLIR'),
|
||||
anime: msg('ANIME'),
|
||||
noir: msg('NOIR'),
|
||||
snow: msg('SNOW'),
|
||||
});
|
||||
|
||||
/** Region ids from _regionLabel() and the name shown for each. */
|
||||
|
||||
@@ -66,8 +66,6 @@
|
||||
"Anime": "Anime",
|
||||
"Apply high-contrast monochrome film-noir grading.": "Aplica una gradación monocroma de alto contraste estilo film noir.",
|
||||
"Noir": "Noir",
|
||||
"Add a cold, snowy whiteout treatment to the scene.": "Añade a la escena un tratamiento frío de ventisca blanca.",
|
||||
"Snow": "Nieve",
|
||||
"MAP SOURCE": "FUENTE DEL MAPA",
|
||||
"Map source": "Fuente del mapa",
|
||||
"Style": "Estilo",
|
||||
|
||||
@@ -416,7 +416,6 @@
|
||||
"Thermal": "Тепловизор",
|
||||
"Anime": "Аниме",
|
||||
"Noir": "Нуар",
|
||||
"Snow": "Снег",
|
||||
"Current cockpit vision style: {style}. Activate for next style.": "Текущий стиль обзора кабины: {style}. Активируйте, чтобы переключить на следующий.",
|
||||
"Current style: {style} — click for next": "Текущий стиль: {style} — нажмите для следующего",
|
||||
"AIRCRAFT": "САМОЛЁТ",
|
||||
@@ -690,7 +689,6 @@
|
||||
"Simulate FLIR-style thermal contrast. Turn up Ironbow for color.": "Имитирует тепловой контраст в стиле FLIR. Для цвета увеличьте Ironbow.",
|
||||
"Apply bright cel-shaded color and illustrated outlines.": "Применяет яркие cel-shading-цвета и рисованные контуры.",
|
||||
"Apply high-contrast monochrome film-noir grading.": "Применяет высококонтрастную монохромную цветокоррекцию в стиле нуар.",
|
||||
"Add a cold, snowy whiteout treatment to the scene.": "Добавляет в сцену холодный снежный эффект «белой мглы».",
|
||||
"Expand LOCATION": "Развернуть панель «Место»",
|
||||
"Collapse panel": "Свернуть панель",
|
||||
"Pin location tray": "Закрепить панель места",
|
||||
|
||||
@@ -1624,7 +1624,6 @@
|
||||
"Thermal": "热成像",
|
||||
"Anime": "动漫",
|
||||
"Noir": "黑白",
|
||||
"Snow": "雪景",
|
||||
"Current cockpit vision style: {style}. Activate for next style.": "当前座舱视觉样式:{style}。点击可切换到下一个样式。",
|
||||
"Current style: {style} — click for next": "当前样式:{style}——点击切换到下一个",
|
||||
"Collapse Contact panel": "收起目标面板",
|
||||
@@ -2535,7 +2534,6 @@
|
||||
"Simulate FLIR-style thermal contrast. Turn up Ironbow for color.": "模拟 FLIR 风格的热成像对比。调高 Ironbow 可显示色彩。",
|
||||
"Apply bright cel-shaded color and illustrated outlines.": "应用明亮的赛璐珞色彩和插画轮廓线。",
|
||||
"Apply high-contrast monochrome film-noir grading.": "应用高对比度的黑白电影风格调色。",
|
||||
"Add a cold, snowy whiteout treatment to the scene.": "为场景添加寒冷的暴雪白化效果。",
|
||||
"MAP SOURCE": "地图源",
|
||||
"Map source": "地图源",
|
||||
"Style": "样式",
|
||||
|
||||
@@ -1614,7 +1614,6 @@
|
||||
"Thermal": "熱成像",
|
||||
"Anime": "動漫",
|
||||
"Noir": "黑白",
|
||||
"Snow": "雪景",
|
||||
"Current cockpit vision style: {style}. Activate for next style.": "當前座艙視覺樣式:{style}。點擊可切換到下一個樣式。",
|
||||
"Current style: {style} — click for next": "當前樣式:{style}——點擊切換到下一個",
|
||||
"Collapse Contact panel": "收起目標面板",
|
||||
@@ -2523,7 +2522,6 @@
|
||||
"Simulate FLIR-style thermal contrast. Turn up Ironbow for color.": "模擬 FLIR 風格的熱成像對比。調高 Ironbow 可顯示色彩。",
|
||||
"Apply bright cel-shaded color and illustrated outlines.": "應用明亮的賽璐珞色彩和插畫輪廓線。",
|
||||
"Apply high-contrast monochrome film-noir grading.": "應用高對比度的黑白電影風格調色。",
|
||||
"Add a cold, snowy whiteout treatment to the scene.": "為場景添加寒冷的暴雪白化效果。",
|
||||
"MAP SOURCE": "地圖源",
|
||||
"Map source": "地圖源",
|
||||
"Style": "樣式",
|
||||
|
||||
@@ -239,6 +239,19 @@ test('no unchanged Realtime tool definition drifts silently', () => {
|
||||
'Direct the camera like a drone operator: orbit the current view target, pan, tilt, or rotate — one bounded nudge (mode=once) or continuous motion until stopped (mode=continuous). Continuous motion also stops on any manual camera input or when a navigation tool runs. Say the RESULTING state when confirming ("Orbiting slowly").';
|
||||
camera.parameters.properties.direction.description =
|
||||
'Required except for orbit (defaults right/clockwise) and stop.';
|
||||
// Snow is retired from set_visual_style; restore it to keep the legacy pin.
|
||||
const visualStyle = unchanged.find(
|
||||
(tool) => tool.name === 'set_visual_style',
|
||||
).parameters.properties.style;
|
||||
assert.deepEqual(visualStyle.enum, [
|
||||
'normal',
|
||||
'retro',
|
||||
'surveillance',
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
]);
|
||||
visualStyle.enum = [...visualStyle.enum, 'snow'];
|
||||
assert.equal(unchanged.length, 14);
|
||||
const digest = createHash('sha256')
|
||||
.update(JSON.stringify(unchanged))
|
||||
|
||||
@@ -107,6 +107,13 @@ test('the HAL example survives the round trip and shrinks to 39 characters', ()
|
||||
);
|
||||
});
|
||||
|
||||
test('a long-form link to the retired Snow style opens as Normal', () => {
|
||||
const snow = parseLegacy('v=2&lat=1&lon=2&style=snow&sp=d.90_w.20');
|
||||
const normal = parseLegacy('v=2&lat=1&lon=2');
|
||||
assert.equal(snow.style, 'normal');
|
||||
assert.deepEqual(snow.styleParams, normal.styleParams);
|
||||
});
|
||||
|
||||
test('links from earlier long-form versions keep their historical meaning', () => {
|
||||
for (const hash of [
|
||||
'lat=10&lon=20',
|
||||
|
||||
@@ -33,6 +33,7 @@ import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
|
||||
import { VIEW_STYLES } from '../view/index.js';
|
||||
import {
|
||||
GLOBAL_POST_DEFAULTS,
|
||||
RETIRED_STYLES,
|
||||
STYLE_PRESET_DEFAULTS,
|
||||
STYLES,
|
||||
} from '../ui/visualPresets.js';
|
||||
@@ -101,7 +102,7 @@ export const SHARE_VIEW_ENUMS = Object.freeze({
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
'snow',
|
||||
'snow', // retired (RETIRED_STYLES): decodes as Normal
|
||||
]),
|
||||
mapStack: Object.freeze([
|
||||
'photoreal',
|
||||
@@ -221,10 +222,6 @@ export const SHARE_VIEW_STYLE_PARAMS = Object.freeze({
|
||||
{ key: 'grainAmt', token: 'g', min: 0, max: 1 },
|
||||
{ key: 'vignetteAmt', token: 'v', min: 0, max: 1 },
|
||||
]),
|
||||
snow: freezeAll([
|
||||
{ key: 'density', token: 'd', min: 0, max: 1 },
|
||||
{ key: 'wind', token: 'w', min: 0, max: 1 },
|
||||
]),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -1321,8 +1318,12 @@ export function decodeShareView(code, { nowMs = Date.now() } = {}) {
|
||||
return { ok: false, reason: 'out-of-range' };
|
||||
}
|
||||
|
||||
// A retired style's code stays reserved: the view opens on Normal, and
|
||||
// parameters written for it are dropped rather than reported as unknown.
|
||||
const retiredStyle = RETIRED_STYLES.includes(raw.style);
|
||||
if (raw.style !== undefined && !VIEW_STYLES.includes(raw.style)) {
|
||||
if (get('style').len) unknown.code('enum:style', raw.style);
|
||||
if (get('style').len && !retiredStyle)
|
||||
unknown.code('enum:style', raw.style);
|
||||
raw.style = undefined;
|
||||
}
|
||||
for (const key of ['hudVariant', 'mapStack']) {
|
||||
@@ -1357,7 +1358,7 @@ export function decodeShareView(code, { nowMs = Date.now() } = {}) {
|
||||
unknown.code('layer-option', 'without-layers');
|
||||
if (get('panelState'))
|
||||
raw.panelState = decodePanels(get('panelState').len, unknown);
|
||||
if (get('styleParams'))
|
||||
if (get('styleParams') && !retiredStyle)
|
||||
raw.styleParams = decodeStyleParams(
|
||||
get('styleParams').len,
|
||||
raw.style ?? 'normal',
|
||||
|
||||
@@ -2,6 +2,7 @@ import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
LAYER_STATE_REGISTRY,
|
||||
LAYER_STATE_TOKEN_ALPHABET,
|
||||
LAYER_STATE_TOKEN_RESERVATIONS,
|
||||
REGISTERED_LAYER_IDS,
|
||||
createDefaultLayerState,
|
||||
@@ -440,7 +441,8 @@ test('defaults travel as frozen meanings that match today’s app defaults', ()
|
||||
assert.deepEqual(SHARE_VIEW_ENUMS.hudVariant.slice(0, HUD_LAYOUTS.length), [
|
||||
...HUD_LAYOUTS,
|
||||
]);
|
||||
assert.deepEqual(SHARE_VIEW_ENUMS.style, [...VIEW_STYLES]);
|
||||
// Snow is retired; its code stays reserved (see the retired-style test).
|
||||
assert.deepEqual(SHARE_VIEW_ENUMS.style, [...VIEW_STYLES, 'snow']);
|
||||
assert.deepEqual(SHARE_VIEW_ENUMS.mapStack, [...VIEW_MAPS]);
|
||||
assert.deepEqual(SHARE_VIEW_ENUMS.detectionAllocation, [
|
||||
...ALLOCATION_STRATEGIES,
|
||||
@@ -549,7 +551,6 @@ test('the field registry is well formed and permanent', () => {
|
||||
...common,
|
||||
styleParams: { contrastAmt: 1.2, grainAmt: 0.5, vignetteAmt: 0.5 },
|
||||
},
|
||||
snow: { ...common, styleParams: { density: 0.6, wind: 0.5 } },
|
||||
});
|
||||
assert.deepEqual(
|
||||
SHARE_VIEW_ENTITY_KINDS.map((e) => [e.code, e.kind]),
|
||||
@@ -966,6 +967,29 @@ test('enum names outside the code tables travel as text', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test('links to the retired Snow style open as Normal', () => {
|
||||
const snowCode = SHARE_VIEW_ENUMS.style.indexOf('snow');
|
||||
assert.equal(snowCode, 6, 'a retired code is never reused');
|
||||
const params = new WireWriter()
|
||||
.varint(LAYER_STATE_TOKEN_ALPHABET.indexOf('d'))
|
||||
.varint(90)
|
||||
.varint(LAYER_STATE_TOKEN_ALPHABET.indexOf('w'))
|
||||
.varint(20)
|
||||
.finish();
|
||||
for (const writeStyle of [
|
||||
(writer) => writer.uintField(14, snowCode),
|
||||
(writer) => writer.textField(14, 'snow'),
|
||||
]) {
|
||||
const writer = new WireWriter();
|
||||
writer.bytes.push(1);
|
||||
writeStyle(writer);
|
||||
writer.bytesField(27, params);
|
||||
const { state, unknownFields } = decodeOk(hashOf(writer.finish()));
|
||||
assert.deepEqual(state, normalizeShareViewState({}));
|
||||
assert.equal(unknownFields, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
/* ---------------- integrity ---------------- */
|
||||
|
||||
test('malformed input is rejected with a reason, never thrown', () => {
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import { msg } from '../i18n/marker.js';
|
||||
|
||||
/**
|
||||
* Snow Style — White overlay + Desaturation + Falling particles
|
||||
* Transforms the world into a winter wonderland
|
||||
*/
|
||||
export const snowShader = {
|
||||
name: 'snow',
|
||||
uniforms: {
|
||||
density: { default: 0.6, min: 0, max: 1, label: msg('Density') },
|
||||
wind: { default: 0.5, min: 0, max: 1, label: msg('Wind') },
|
||||
},
|
||||
fragmentShader: /* glsl */ `
|
||||
uniform sampler2D colorTexture;
|
||||
uniform vec2 colorTextureDimensions;
|
||||
uniform float intensity;
|
||||
uniform float time;
|
||||
uniform float density;
|
||||
uniform float wind;
|
||||
in vec2 v_textureCoordinates;
|
||||
|
||||
// Hash function for pseudo-random snow
|
||||
float hash(vec2 p) {
|
||||
p = fract(p * vec2(123.34, 456.21));
|
||||
p += dot(p, p + 45.32);
|
||||
return fract(p.x * p.y);
|
||||
}
|
||||
|
||||
// Layered snow particles
|
||||
float snowLayer(vec2 uv, float layer) {
|
||||
float depth = 0.5 + layer * 0.5;
|
||||
float speed = 0.4 + layer * 0.3;
|
||||
float size = mix(0.01, 0.025, layer);
|
||||
float windForce = 0.05 + wind * 0.2;
|
||||
|
||||
vec2 snowUV = uv * vec2(1.0, 0.5) * (4.0 + layer * 4.0);
|
||||
snowUV.y += time * speed;
|
||||
// Offset is the integral of a swaying velocity sin(time * 0.5 + phase) * windForce,
|
||||
// so the sway stays bounded however long the app has been running.
|
||||
snowUV.x -= 2.0 * cos(time * 0.5 + layer * 3.14) * windForce;
|
||||
|
||||
vec2 cell = floor(snowUV);
|
||||
vec2 f = fract(snowUV);
|
||||
|
||||
float snow = 0.0;
|
||||
// Check neighboring cells for smooth particles
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
vec2 neighbor = vec2(float(x), float(y));
|
||||
vec2 point = vec2(hash(cell + neighbor), hash(cell + neighbor + 100.0));
|
||||
// Animate particle position slightly
|
||||
point = 0.5 + 0.4 * sin(time * 0.3 + 6.2831 * point);
|
||||
float d = length(f - neighbor - point);
|
||||
snow += smoothstep(size, 0.0, d) * depth;
|
||||
}
|
||||
}
|
||||
return snow;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = v_textureCoordinates;
|
||||
vec4 color = texture(colorTexture, uv);
|
||||
|
||||
// Cool/blue color shift
|
||||
vec3 cool = color.rgb * vec3(0.85, 0.9, 1.1);
|
||||
|
||||
// Desaturate partially
|
||||
float luma = dot(cool, vec3(0.299, 0.587, 0.114));
|
||||
vec3 desaturated = mix(cool, vec3(luma), 0.4 * intensity);
|
||||
|
||||
// Brighten (snow coverage lightens everything)
|
||||
vec3 brightened = mix(desaturated, vec3(1.0), 0.15 * intensity);
|
||||
|
||||
// Add white frost overlay on brighter areas
|
||||
float frostMask = smoothstep(0.3, 0.8, luma);
|
||||
vec3 frosted = mix(brightened, vec3(0.95, 0.97, 1.0), frostMask * 0.25 * intensity);
|
||||
|
||||
// Accumulate snow particle layers (density controls amount)
|
||||
float snow = 0.0;
|
||||
snow += snowLayer(uv, 0.0) * 0.5;
|
||||
snow += snowLayer(uv, 0.25) * 0.6;
|
||||
snow += snowLayer(uv, 0.5) * 0.7;
|
||||
snow += snowLayer(uv, 0.75) * 0.8;
|
||||
snow += snowLayer(uv, 1.0) * 1.0;
|
||||
snow = clamp(snow * (0.4 + density * 1.2), 0.0, 1.0);
|
||||
|
||||
// Composite snow particles
|
||||
vec3 result = frosted + snow * 0.8 * intensity;
|
||||
|
||||
// Subtle fog in lower areas
|
||||
float fog = smoothstep(0.0, 0.4, 1.0 - uv.y) * 0.1 * intensity;
|
||||
result = mix(result, vec3(0.85, 0.88, 0.95), fog);
|
||||
|
||||
out_FragColor = vec4(mix(color.rgb, result, intensity), color.a);
|
||||
}
|
||||
`,
|
||||
};
|
||||
@@ -1598,7 +1598,7 @@ export class StyleManager extends ShellFacade {
|
||||
* 2. Crossfades the new shader stage intensity to 1.
|
||||
* 3. Applies style preset defaults (bloom/sharpen/HUD) if applyPreset is true.
|
||||
* 4. Updates button highlights, style indicator, slider panel, HUD, and detection overlay.
|
||||
* @param {string} styleName - Target style ('normal'|'retro'|'surveillance'|'thermal'|'anime'|'noir'|'snow').
|
||||
* @param {string} styleName - Target style ('normal'|'retro'|'surveillance'|'thermal'|'anime'|'noir').
|
||||
* @param {object} [options]
|
||||
* @param {boolean} [options.applyPreset=true] - Whether to apply STYLE_PRESET_DEFAULTS for the new style.
|
||||
* @param {boolean} [options.userInitiated=false] - Whether a direct user preset choice owns the Cyber exit style.
|
||||
|
||||
@@ -5,7 +5,6 @@ const STYLE_KEYS = Object.freeze({
|
||||
4: 'thermal',
|
||||
5: 'anime',
|
||||
6: 'noir',
|
||||
7: 'snow',
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -268,7 +268,6 @@ export function setVisionMode(mode, { revealParameters = false } = {}) {
|
||||
thermal: t('FLIR'),
|
||||
anime: t('ANIME'),
|
||||
noir: t('NOIR'),
|
||||
snow: t('SNOW'),
|
||||
};
|
||||
const names = {
|
||||
optical: t('Normal'),
|
||||
@@ -277,7 +276,6 @@ export function setVisionMode(mode, { revealParameters = false } = {}) {
|
||||
thermal: t('Thermal'),
|
||||
anime: t('Anime'),
|
||||
noir: t('Noir'),
|
||||
snow: t('Snow'),
|
||||
};
|
||||
if (this.visionCurrent) {
|
||||
this.visionCurrent.dataset.cockpitVision = next;
|
||||
|
||||
+26
-20
@@ -105,7 +105,6 @@ test('visual styles retain Cyber through real preset application without droppin
|
||||
'normal',
|
||||
'noir',
|
||||
'anime',
|
||||
'snow',
|
||||
'thermal',
|
||||
'retro',
|
||||
]) {
|
||||
@@ -162,8 +161,8 @@ test('a preset explicitly selected in Cyber survives exit and resets for re-entr
|
||||
owner.setStyle('noir');
|
||||
|
||||
owner._setHudVariant('cyber', { applyVisualDefaults: true });
|
||||
owner.setStyle('snow', { userInitiated: true });
|
||||
assert.equal(owner.activeStyle, 'snow');
|
||||
owner.setStyle('anime', { userInitiated: true });
|
||||
assert.equal(owner.activeStyle, 'anime');
|
||||
assert.equal(owner._preCyberStyle, 'noir');
|
||||
assert.equal(owner._cyberStyleUserSelected, true);
|
||||
|
||||
@@ -172,16 +171,16 @@ test('a preset explicitly selected in Cyber survives exit and resets for re-entr
|
||||
assert.equal(owner._preCyberStyle, 'noir');
|
||||
owner._setHudVariant('operator', { applyVisualDefaults: true });
|
||||
assert.equal(hud.getVariant(), 'operator');
|
||||
assert.equal(owner.activeStyle, 'snow');
|
||||
assert.equal(owner.activeStyle, 'anime');
|
||||
assert.equal(owner._preCyberStyle, null);
|
||||
assert.equal(owner._cyberStyleUserSelected, false);
|
||||
|
||||
// A later untouched Cyber round trip restores the new pre-Cyber preset.
|
||||
owner._setHudVariant('cyber', { applyVisualDefaults: true });
|
||||
assert.equal(owner.activeStyle, 'thermal');
|
||||
assert.equal(owner._preCyberStyle, 'snow');
|
||||
assert.equal(owner._preCyberStyle, 'anime');
|
||||
owner._setHudVariant('minimal', { applyVisualDefaults: true });
|
||||
assert.equal(owner.activeStyle, 'snow');
|
||||
assert.equal(owner.activeStyle, 'anime');
|
||||
});
|
||||
|
||||
test('an explicit same-value FLIR choice is preserved on Cyber exit', (t) => {
|
||||
@@ -200,15 +199,15 @@ test('an authoritative same-Cyber reset clears local preset ownership', (t) => {
|
||||
const { owner } = styleOwner(t, 'tactical');
|
||||
owner.setStyle('noir');
|
||||
owner._setHudVariant('cyber', { applyVisualDefaults: true });
|
||||
owner.setStyle('snow', { userInitiated: true });
|
||||
owner.setStyle('anime', { userInitiated: true });
|
||||
assert.equal(owner._cyberStyleUserSelected, true);
|
||||
|
||||
owner._setHudVariant('cyber');
|
||||
assert.equal(owner.activeStyle, 'snow');
|
||||
assert.equal(owner.activeStyle, 'anime');
|
||||
assert.equal(owner._preCyberStyle, null);
|
||||
assert.equal(owner._cyberStyleUserSelected, false);
|
||||
owner._setHudVariant('operator', { applyVisualDefaults: true });
|
||||
assert.equal(owner.activeStyle, 'snow');
|
||||
assert.equal(owner.activeStyle, 'anime');
|
||||
});
|
||||
|
||||
test('Display and voice HUD routes preserve explicit Cyber entry and exit authority', async (t) => {
|
||||
@@ -226,10 +225,10 @@ test('Display and voice HUD routes preserve explicit Cyber entry and exit author
|
||||
const display = makeOwner();
|
||||
const hudLayout = new EventTarget();
|
||||
hudLayout.value = 'cyber';
|
||||
const snowButton = new EventTarget();
|
||||
snowButton.dataset = { style: 'snow' };
|
||||
const animeButton = new EventTarget();
|
||||
animeButton.dataset = { style: 'anime' };
|
||||
const controls = bindDisplayControls({
|
||||
elements: { hudLayout, styleButtons: [snowButton] },
|
||||
elements: { hudLayout, styleButtons: [animeButton] },
|
||||
actions: {
|
||||
setHudLayout: (value) => display.owner.setHudLayout(value),
|
||||
setStyle: (value) =>
|
||||
@@ -239,12 +238,12 @@ test('Display and voice HUD routes preserve explicit Cyber entry and exit author
|
||||
hudLayout.dispatchEvent(new Event('change'));
|
||||
assert.equal(display.hud.getVariant(), 'cyber');
|
||||
assert.equal(display.owner.activeStyle, 'thermal');
|
||||
snowButton.dispatchEvent(new Event('click'));
|
||||
assert.equal(display.owner.activeStyle, 'snow');
|
||||
animeButton.dispatchEvent(new Event('click'));
|
||||
assert.equal(display.owner.activeStyle, 'anime');
|
||||
hudLayout.value = 'operator';
|
||||
hudLayout.dispatchEvent(new Event('change'));
|
||||
assert.equal(display.hud.getVariant(), 'operator');
|
||||
assert.equal(display.owner.activeStyle, 'snow');
|
||||
assert.equal(display.owner.activeStyle, 'anime');
|
||||
controls.destroy();
|
||||
|
||||
const voice = makeOwner();
|
||||
@@ -263,13 +262,13 @@ test('Display and voice HUD routes preserve explicit Cyber entry and exit author
|
||||
assert.equal(entered.ok, true);
|
||||
assert.equal(voice.hud.getVariant(), 'cyber');
|
||||
assert.equal(voice.owner.activeStyle, 'thermal');
|
||||
const selected = await runVoiceAction('set_visual_style', { style: 'snow' });
|
||||
const selected = await runVoiceAction('set_visual_style', { style: 'anime' });
|
||||
assert.equal(selected.ok, true);
|
||||
assert.equal(voice.owner.activeStyle, 'snow');
|
||||
assert.equal(voice.owner.activeStyle, 'anime');
|
||||
const exited = await runVoiceAction('set_hud', { layout: 'operator' });
|
||||
assert.equal(exited.ok, true);
|
||||
assert.equal(voice.hud.getVariant(), 'operator');
|
||||
assert.equal(voice.owner.activeStyle, 'snow');
|
||||
assert.equal(voice.owner.activeStyle, 'anime');
|
||||
});
|
||||
|
||||
test('scope choice and expanded feather survive every HUD layout and both restore paths', async (t) => {
|
||||
@@ -301,11 +300,18 @@ test('scope choice and expanded feather survive every HUD layout and both restor
|
||||
assert.equal(owner._scopeFeatherSlider.value, '200');
|
||||
});
|
||||
|
||||
test('a scene shot saved with the retired Snow style plays on Normal', async (t) => {
|
||||
const { owner } = styleOwner(t, 'tactical');
|
||||
owner.setStyle('noir');
|
||||
await owner.applyVisualState({ style: 'snow' });
|
||||
assert.equal(owner.activeStyle, 'normal');
|
||||
});
|
||||
|
||||
test('scene and share HUD transitions do not restore a local pre-Cyber preset', async (t) => {
|
||||
const { owner, hud } = styleOwner(t, 'tactical');
|
||||
owner.setStyle('noir');
|
||||
owner._setHudVariant('cyber', { applyVisualDefaults: true });
|
||||
owner.setStyle('snow', { userInitiated: true });
|
||||
owner.setStyle('anime', { userInitiated: true });
|
||||
assert.equal(owner._cyberStyleUserSelected, true);
|
||||
|
||||
await owner.applyVisualState({ style: 'retro', hud: { variant: 'cyber' } });
|
||||
@@ -319,7 +325,7 @@ test('scene and share HUD transitions do not restore a local pre-Cyber preset',
|
||||
owner.setStyle('surveillance');
|
||||
owner._setHudVariant('cyber', { applyVisualDefaults: true });
|
||||
assert.equal(owner._preCyberStyle, 'surveillance');
|
||||
owner.setStyle('snow', { userInitiated: true });
|
||||
owner.setStyle('anime', { userInitiated: true });
|
||||
await owner.restoreShareState({
|
||||
style: 'thermal',
|
||||
hudVariant: 'cyber',
|
||||
|
||||
@@ -12,7 +12,6 @@ const STYLE_LABELS = Object.freeze({
|
||||
thermal: msg('FLIR'),
|
||||
anime: msg('ANIME'),
|
||||
noir: msg('NOIR'),
|
||||
snow: msg('SNOW'),
|
||||
});
|
||||
|
||||
const DETECTION_MODE_LABELS = Object.freeze({
|
||||
|
||||
@@ -747,12 +747,6 @@
|
||||
.btn-icon::before {
|
||||
content: '◐';
|
||||
}
|
||||
:root[data-ui-theme='cyber']
|
||||
#command-dock
|
||||
.style-btn[data-style='snow']
|
||||
.btn-icon::before {
|
||||
content: '❄︎';
|
||||
}
|
||||
|
||||
:root[data-ui-theme='cyber'] :where(#bloom-toggle, #sharpen-toggle) {
|
||||
font-size: 0;
|
||||
|
||||
@@ -43,11 +43,6 @@
|
||||
<span class="btn-label" data-i18n>Noir</span>
|
||||
<span class="btn-key">6</span>
|
||||
</button>
|
||||
<button class="style-btn" data-style="snow" title="Add a cold, snowy whiteout treatment to the scene." data-i18n-title>
|
||||
<span class="btn-icon">❄</span>
|
||||
<span class="btn-label" data-i18n>Snow</span>
|
||||
<span class="btn-key">7</span>
|
||||
</button>
|
||||
</div>
|
||||
<section class="map-source-section" aria-labelledby="map-source-label">
|
||||
<div class="map-source-heading">
|
||||
|
||||
@@ -77,7 +77,7 @@ test('construction is inert and initialization creates one owned pipeline', () =
|
||||
assert.equal(f.frames.size, 0);
|
||||
f.effects.initStyles();
|
||||
f.effects.initPostProcess();
|
||||
assert.equal(f.stages.size, 7);
|
||||
assert.equal(f.stages.size, 6);
|
||||
assert.ok(
|
||||
Object.values(f.effects.stages).every(
|
||||
(stage) => !stage.enabled && stage.uniforms.intensity === 0,
|
||||
@@ -85,7 +85,7 @@ test('construction is inert and initialization creates one owned pipeline', () =
|
||||
);
|
||||
f.effects.initStyles();
|
||||
f.effects.initPostProcess();
|
||||
assert.equal(f.stages.size, 7);
|
||||
assert.equal(f.stages.size, 6);
|
||||
f.effects.destroy();
|
||||
});
|
||||
|
||||
@@ -252,7 +252,7 @@ test('stop revokes pending work immediately but retains stages until final destr
|
||||
assert.equal(f.frames.size, 0);
|
||||
assert.equal(
|
||||
f.stages.size,
|
||||
7,
|
||||
6,
|
||||
'Context and Cockpit may still be releasing these stages',
|
||||
);
|
||||
const intensity = f.effects.stages.retro.uniforms.intensity;
|
||||
@@ -281,7 +281,7 @@ test('destroying one instance does not remove another pipeline or clock', () =>
|
||||
a.effects.destroy();
|
||||
b.tick(250);
|
||||
assert.equal(a.stages.size, 0);
|
||||
assert.equal(b.stages.size, 6);
|
||||
assert.equal(b.stages.size, 5);
|
||||
assert.equal(b.effects.stages.retro.uniforms.intensity, 0.5);
|
||||
b.effects.destroy();
|
||||
});
|
||||
|
||||
@@ -73,13 +73,13 @@ function shortcuts() {
|
||||
return { documentRef, searchInput, calls, controller, press };
|
||||
}
|
||||
|
||||
test('number keys retain the seven style mappings', () => {
|
||||
test('number keys retain the six style mappings', () => {
|
||||
const f = shortcuts();
|
||||
for (const key of ['1', '2', '3', '4', '5', '6', '7', '8', 'Space'])
|
||||
f.press(key);
|
||||
assert.deepEqual(
|
||||
f.calls,
|
||||
['normal', 'retro', 'surveillance', 'thermal', 'anime', 'noir', 'snow'].map(
|
||||
['normal', 'retro', 'surveillance', 'thermal', 'anime', 'noir'].map(
|
||||
(style) => ['setStyle', style],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { retroShader } from '../styles/retro.js';
|
||||
import { animeShader } from '../styles/anime.js';
|
||||
import { noirShader } from '../styles/noir.js';
|
||||
import { snowShader } from '../styles/snow.js';
|
||||
import { nightVisionShader } from '../styles/surveillance.js';
|
||||
import { thermalShader } from '../styles/thermal.js';
|
||||
import { BLOOM_INTENSITY_DEFAULT } from '../bloom.js';
|
||||
@@ -16,9 +15,14 @@ export const STYLES = {
|
||||
thermal: thermalShader,
|
||||
anime: animeShader,
|
||||
noir: noirShader,
|
||||
snow: snowShader,
|
||||
};
|
||||
|
||||
/**
|
||||
* Styles the app no longer offers. A share link, scene or shot that names one
|
||||
* opens on Normal instead of keeping whatever style was already showing.
|
||||
*/
|
||||
export const RETIRED_STYLES = Object.freeze(['snow']);
|
||||
|
||||
/**
|
||||
* The tactical detection look: Dense at 75%.
|
||||
*
|
||||
@@ -156,5 +160,4 @@ export const STYLE_STATUS_LABELS = {
|
||||
thermal: msg('FLIR'),
|
||||
anime: msg('ANIME'),
|
||||
noir: msg('NOIR'),
|
||||
snow: msg('SNOW'),
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
|
||||
import { syncScopeControls } from './displayControls.js';
|
||||
import { STYLE_STATUS_LABELS } from './visualPresets.js';
|
||||
import { RETIRED_STYLES, STYLE_STATUS_LABELS } from './visualPresets.js';
|
||||
import { syncSegmentedControl } from './segmentedControl.js';
|
||||
import { t } from '../i18n/index.js';
|
||||
import { UiLifetime } from './uiLifetime.js';
|
||||
@@ -1272,8 +1272,9 @@ export class VisualSettings {
|
||||
const superseded = () => typeof isCurrent === 'function' && !isCurrent();
|
||||
if (superseded()) return false;
|
||||
|
||||
if (state.style && state.style !== this.activeStyle) {
|
||||
this.setStyle(state.style, { applyPreset: false });
|
||||
const style = RETIRED_STYLES.includes(state.style) ? 'normal' : state.style;
|
||||
if (style && style !== this.activeStyle) {
|
||||
this.setStyle(style, { applyPreset: false });
|
||||
}
|
||||
|
||||
const bloomState = state.bloom || {};
|
||||
|
||||
@@ -20,7 +20,6 @@ export const STYLE_URL_NAMES = Object.freeze({
|
||||
thermal: 'flir',
|
||||
anime: 'anime',
|
||||
noir: 'noir',
|
||||
snow: 'snow',
|
||||
});
|
||||
|
||||
const STYLES_BY_URL_NAME = Object.freeze(
|
||||
|
||||
@@ -248,15 +248,7 @@ const schemas = [
|
||||
properties: {
|
||||
style: {
|
||||
type: 'string',
|
||||
enum: [
|
||||
'normal',
|
||||
'retro',
|
||||
'surveillance',
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
'snow',
|
||||
],
|
||||
enum: ['normal', 'retro', 'surveillance', 'thermal', 'anime', 'noir'],
|
||||
},
|
||||
},
|
||||
required: ['style'],
|
||||
|
||||
@@ -45,7 +45,8 @@ test('the complete Realtime tool payload pins the manifest-generated layer relea
|
||||
// S2 GhostMaps extends the generated layer enums.
|
||||
// Panel adds named voice targets and timeout; shared Panel/Cockpit guidance
|
||||
// is consolidated without changing either feature's arguments.
|
||||
'9ee82c4214499280c208c93751b19d7b724c81b7727a2d0c6dab0184749fbe15',
|
||||
// Snow leaves the set_visual_style enum.
|
||||
'724a82b538a12a994401fc941e175625b032bfb96109934e091d3ef5b9600909',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -169,6 +170,18 @@ test('all legacy action arguments are byte-identical after removing the delibera
|
||||
// Cyber adds one HUD layout.
|
||||
const hud = property('set_hud').layout;
|
||||
hud.enum = hud.enum.filter((layout) => layout !== 'cyber');
|
||||
// Snow is retired from set_visual_style; restore it to keep the legacy pin.
|
||||
const visualStyle = legacy.find((tool) => tool.name === 'set_visual_style')
|
||||
.parameters.properties.style;
|
||||
assert.deepEqual(visualStyle.enum, [
|
||||
'normal',
|
||||
'retro',
|
||||
'surveillance',
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
]);
|
||||
visualStyle.enum = [...visualStyle.enum, 'snow'];
|
||||
// Derived by applying the same removals to the 4b56d0e9 actionSchemas.
|
||||
assert.equal(
|
||||
createHash('sha256').update(JSON.stringify(legacy)).digest('hex'),
|
||||
|
||||
@@ -66,7 +66,6 @@ const ALLOWED_STYLES = new Set([
|
||||
'thermal',
|
||||
'anime',
|
||||
'noir',
|
||||
'snow',
|
||||
]);
|
||||
const PANEL_ALIASES = new Map([
|
||||
['data', 'data-panel'],
|
||||
|
||||
Reference in new issue
Block a user