diff --git a/src/ui/visualEffects.test.mjs b/src/ui/visualEffects.test.mjs
index 9e9542c52..142bbbe4f 100644
--- a/src/ui/visualEffects.test.mjs
+++ b/src/ui/visualEffects.test.mjs
@@ -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();
});
diff --git a/src/ui/visualInput.test.mjs b/src/ui/visualInput.test.mjs
index c69c935ba..a87c9c815 100644
--- a/src/ui/visualInput.test.mjs
+++ b/src/ui/visualInput.test.mjs
@@ -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],
),
);
diff --git a/src/ui/visualPresets.js b/src/ui/visualPresets.js
index e3830601a..90d89e2f0 100644
--- a/src/ui/visualPresets.js
+++ b/src/ui/visualPresets.js
@@ -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'),
};
diff --git a/src/ui/visualSettings.js b/src/ui/visualSettings.js
index a6f7b5674..11974307e 100644
--- a/src/ui/visualSettings.js
+++ b/src/ui/visualSettings.js
@@ -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 || {};
diff --git a/src/view/index.js b/src/view/index.js
index fc5385aaa..221c4e0a1 100644
--- a/src/view/index.js
+++ b/src/view/index.js
@@ -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(
diff --git a/src/voice/actionSchemas.js b/src/voice/actionSchemas.js
index bf7e2f66d..a27907666 100644
--- a/src/voice/actionSchemas.js
+++ b/src/voice/actionSchemas.js
@@ -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'],
diff --git a/src/voice/actionSchemas.test.mjs b/src/voice/actionSchemas.test.mjs
index 97e5004b5..3bf9cfcd9 100644
--- a/src/voice/actionSchemas.test.mjs
+++ b/src/voice/actionSchemas.test.mjs
@@ -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'),
diff --git a/src/voice/gevActions.js b/src/voice/gevActions.js
index 0a43f02f1..0fd17dc77 100644
--- a/src/voice/gevActions.js
+++ b/src/voice/gevActions.js
@@ -66,7 +66,6 @@ const ALLOWED_STYLES = new Set([
'thermal',
'anime',
'noir',
- 'snow',
]);
const PANEL_ALIASES = new Map([
['data', 'data-panel'],
From 2b5770c6a2d1abcc10987046123faf7ea87669dd Mon Sep 17 00:00:00 2001
From: Said Turksever <28983926+asturksever@users.noreply.github.com>
Date: Thu, 29 Oct 2026 11:21:37 +0000
Subject: [PATCH 2/4] Add the changelog entry for the Snow removal
---
changelog.d/1468-remove-snow-preset.md | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 changelog.d/1468-remove-snow-preset.md
diff --git a/changelog.d/1468-remove-snow-preset.md b/changelog.d/1468-remove-snow-preset.md
new file mode 100644
index 000000000..2bd1cb161
--- /dev/null
+++ b/changelog.d/1468-remove-snow-preset.md
@@ -0,0 +1,4 @@
+- Remove the Snow visual preset. Visual Presets, keyboard shortcuts, the
+ Cockpit vision carousel and the visual-style voice action now offer Normal,
+ CRT, NVG, FLIR, Anime and Noir; share links and scene shots that name Snow
+ open on Normal (asturksever, #1468).
From 3b6111be0672d86647e35674fded12211d3fdc87 Mon Sep 17 00:00:00 2001
From: Said Turksever <28983926+asturksever@users.noreply.github.com>
Date: Thu, 29 Oct 2026 11:34:19 +0000
Subject: [PATCH 3/4] Open views naming the retired Snow style on Normal
createView threw on style 'snow', so an embed host or a
show_in_gods_eye_view call holding a view from before Snow was retired
was refused outright, camera and layers included. viewFromParams read
style=snow as the recipient's own style, while the app opened the same
link on Normal.
Both now read a retired style as Normal; truly unknown names still
throw. The retired list moves to a browser-free module shared by the
view API, the share codec and visual settings.
---
docs/CURRENT-STATE.md | 4 +++-
docs/TOOLS.md | 3 ++-
scripts/package-boundaries.json | 18 ++++++++++++------
src/app/embed.test.mjs | 14 ++++++++++++++
src/share/viewCodec.js | 2 +-
src/ui/visualPresets.js | 6 ------
src/ui/visualSettings.js | 3 ++-
src/view/index.js | 10 +++++++---
src/view/index.test.mjs | 8 ++++++++
src/visualStylePolicy.js | 7 +++++++
10 files changed, 56 insertions(+), 19 deletions(-)
create mode 100644 src/visualStylePolicy.js
diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md
index 690024d28..4f3acfc1b 100644
--- a/docs/CURRENT-STATE.md
+++ b/docs/CURRENT-STATE.md
@@ -1393,7 +1393,9 @@ 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 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.
+long or compact form, opens on Normal and drops the Snow shader parameters. A
+view passed to embed mode or `show_in_gods_eye_view`, and a saved scene shot,
+that names Snow also open on Normal.
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.
diff --git a/docs/TOOLS.md b/docs/TOOLS.md
index 45ee62546..80fd80b13 100644
--- a/docs/TOOLS.md
+++ b/docs/TOOLS.md
@@ -92,7 +92,8 @@ pitch), data layers, visual style, map imagery, and an aircraft, military
aircraft or satellite to follow. `createView` builds and bounds one,
`viewToParams` and `viewFromParams` write and read it in the share-link
format the app restores, and `viewUrl` gives the address that opens it. The
-style names are the ones share links use. Ships cannot be followed from a
+style names are the ones share links use; a retired style (`snow`) is read
+as `normal`. Ships cannot be followed from a
link yet. An aircraft's `follow` may ask for its cockpit view
(`cockpit: true`); links cannot carry that and open the app following it.
diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json
index 4df7229b4..9b46899d7 100644
--- a/scripts/package-boundaries.json
+++ b/scripts/package-boundaries.json
@@ -813,7 +813,8 @@
"src/ui/layerNames.js",
"src/ui/statusText.js",
"src/ui/statusBadge.js",
- "src/scopeMaskPolicy.js"
+ "src/scopeMaskPolicy.js",
+ "src/visualStylePolicy.js"
],
"external": ["cesium"]
},
@@ -2627,7 +2628,8 @@
"src/voice/geminiCaptureWorklet.js",
"src/ui/statusBadge.js",
"src/sceneCommandRelease.js",
- "src/scopeMaskPolicy.js"
+ "src/scopeMaskPolicy.js",
+ "src/visualStylePolicy.js"
],
"external": [
"hls.js",
@@ -3364,7 +3366,8 @@
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/scopeMaskPolicy.js",
- "src/ui/displayControls.js"
+ "src/ui/displayControls.js",
+ "src/visualStylePolicy.js"
],
"external": ["cesium"]
},
@@ -3941,7 +3944,8 @@
"src/data/idSwitches.js",
"src/data/detectionPolicy.js",
"src/scopeMaskPolicy.js",
- "src/i18n/marker.js"
+ "src/i18n/marker.js",
+ "src/visualStylePolicy.js"
],
"external": []
},
@@ -4045,7 +4049,8 @@
"src/tools/views.js",
"src/tools/globePanel.js",
"src/tools/queries/panelRequest.js",
- "src/geoDistance.js"
+ "src/geoDistance.js",
+ "src/visualStylePolicy.js"
],
"external": ["satellite.js"]
},
@@ -4146,7 +4151,8 @@
"src/view/index.js",
"src/data/idSwitches.js",
"src/data/layerState.js",
- "src/data/layerStateTokenReservations.json"
+ "src/data/layerStateTokenReservations.json",
+ "src/visualStylePolicy.js"
],
"external": []
},
diff --git a/src/app/embed.test.mjs b/src/app/embed.test.mjs
index 54799567e..5fa949342 100644
--- a/src/app/embed.test.mjs
+++ b/src/app/embed.test.mjs
@@ -146,6 +146,12 @@ test('an embedded page takes views only from its parent and answers it', async (
id: 3,
view: { camera: { lat: 1, lon: 2 }, layers: ['flights'] },
});
+ // A view kept from before Snow was retired still applies, on Normal.
+ send(parent, {
+ type: 'gev:view',
+ id: 4,
+ view: { camera: { lat: 1, lon: 2 }, style: 'snow' },
+ });
await new Promise((resolve) => setTimeout(resolve, 10));
assert.deepEqual(posted[1], {
type: 'gev:view-applied',
@@ -161,6 +167,13 @@ test('an embedded page takes views only from its parent and answers it', async (
name === 'set_layer_visibility' && args.layerId === 'flights',
),
);
+ assert.equal(posted[3].id, 4);
+ assert.equal(posted[3].ok, true);
+ assert.ok(
+ calls.some(
+ ([name, args]) => name === 'set_visual_style' && args.style === 'normal',
+ ),
+ );
assert.equal(posted.filter((message) => message.id === 1).length, 0);
// Ready names no data and goes to any parent; answers go only to the
// origin that asked.
@@ -168,6 +181,7 @@ test('an embedded page takes views only from its parent and answers it', async (
'*',
'https://host.example',
'https://host.example',
+ 'https://host.example',
]);
remove();
});
diff --git a/src/share/viewCodec.js b/src/share/viewCodec.js
index bb5f6d440..b72f79d34 100644
--- a/src/share/viewCodec.js
+++ b/src/share/viewCodec.js
@@ -31,9 +31,9 @@ import {
} from '../data/detectionPolicy.js';
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
import { VIEW_STYLES } from '../view/index.js';
+import { RETIRED_STYLES } from '../visualStylePolicy.js';
import {
GLOBAL_POST_DEFAULTS,
- RETIRED_STYLES,
STYLE_PRESET_DEFAULTS,
STYLES,
} from '../ui/visualPresets.js';
diff --git a/src/ui/visualPresets.js b/src/ui/visualPresets.js
index 90d89e2f0..ca251e48c 100644
--- a/src/ui/visualPresets.js
+++ b/src/ui/visualPresets.js
@@ -17,12 +17,6 @@ export const STYLES = {
noir: noirShader,
};
-/**
- * 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%.
*
diff --git a/src/ui/visualSettings.js b/src/ui/visualSettings.js
index 11974307e..7c5a881eb 100644
--- a/src/ui/visualSettings.js
+++ b/src/ui/visualSettings.js
@@ -1,6 +1,7 @@
import { clampScopeFeatherPct } from '../scopeMaskPolicy.js';
import { syncScopeControls } from './displayControls.js';
-import { RETIRED_STYLES, STYLE_STATUS_LABELS } from './visualPresets.js';
+import { STYLE_STATUS_LABELS } from './visualPresets.js';
+import { RETIRED_STYLES } from '../visualStylePolicy.js';
import { syncSegmentedControl } from './segmentedControl.js';
import { t } from '../i18n/index.js';
import { UiLifetime } from './uiLifetime.js';
diff --git a/src/view/index.js b/src/view/index.js
index 221c4e0a1..022914277 100644
--- a/src/view/index.js
+++ b/src/view/index.js
@@ -11,6 +11,7 @@ import {
decodeLayerStateParams,
encodeLayerStateParams,
} from '../data/layerState.js';
+import { RETIRED_STYLES } from '../visualStylePolicy.js';
/** Visual styles by internal name, with the name share links use. */
export const STYLE_URL_NAMES = Object.freeze({
@@ -253,6 +254,7 @@ export function createView({
} = {}) {
if (!Number.isFinite(camera?.lat) || !Number.isFinite(camera?.lon))
throw new TypeError('A view needs a camera lat and lon');
+ if (RETIRED_STYLES.includes(style)) style = 'normal';
if (style !== null && !VIEW_STYLES.includes(style))
throw new TypeError(`Unknown view style: ${style}`);
if (map !== null && !VIEW_MAPS.includes(map))
@@ -369,9 +371,11 @@ export function viewFromParams(params) {
pitch_deg: number('pitch'),
},
layers: state?.enabledLayerIds ?? [],
- style: Object.hasOwn(STYLES_BY_URL_NAME, params.get('style'))
- ? STYLES_BY_URL_NAME[params.get('style')]
- : null,
+ style: RETIRED_STYLES.includes(params.get('style'))
+ ? 'normal'
+ : Object.hasOwn(STYLES_BY_URL_NAME, params.get('style'))
+ ? STYLES_BY_URL_NAME[params.get('style')]
+ : null,
map: VIEW_MAPS.includes(map) ? map : null,
follow,
annotations: annotationsFromParams(params),
diff --git a/src/view/index.test.mjs b/src/view/index.test.mjs
index 34299c498..66630e6a0 100644
--- a/src/view/index.test.mjs
+++ b/src/view/index.test.mjs
@@ -127,6 +127,14 @@ test('annotations travel in the link, keeping only what the app draws', async ()
assert.equal(viewToParams(many).has('an'), false);
});
+test('a view naming the retired Snow style opens on Normal', () => {
+ const camera = { lat: 1, lon: 2 };
+ assert.equal(createView({ camera, style: 'snow' }).style, 'normal');
+ const view = viewFromParams(new URLSearchParams('lat=1&lon=2&style=snow'));
+ assert.equal(view.style, 'normal');
+ assert.equal(viewToParams(view).get('style'), 'normal');
+});
+
test('inherited and unknown style keys do not corrupt a restored view', () => {
for (const style of [
'constructor',
diff --git a/src/visualStylePolicy.js b/src/visualStylePolicy.js
new file mode 100644
index 000000000..1a384a7a8
--- /dev/null
+++ b/src/visualStylePolicy.js
@@ -0,0 +1,7 @@
+/**
+ * Browser-free style vocabulary shared by views, share links and the UI.
+ * Styles the app no longer offers, by the name share links used: a view,
+ * 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']);
From 32125384fe8287865187088d257ff54cbaac82a6 Mon Sep 17 00:00:00 2001
From: Said Turksever <28983926+asturksever@users.noreply.github.com>
Date: Thu, 29 Oct 2026 14:04:11 +0000
Subject: [PATCH 4/4] Drop Snow from the new catalogs and migrate saved Snow
shots
The it, pt and uk catalogs merged from main still carried the two Snow
button strings, which the catalog tests report as stale. They are
pruned with npm run i18n -- --prune.
A Scene Director shot saved with Snow now normalizes to Normal when the
project loads, so the shot list shows NORMAL rather than an untranslated
SNOW for a shot that plays on Normal. The README's style keys read 1-6.
---
README.md | 4 ++--
scripts/package-boundaries.json | 3 ++-
src/director/document.test.mjs | 9 +++++++++
src/i18n/locales/it.json | 2 --
src/i18n/locales/pt.json | 2 --
src/i18n/locales/uk.json | 2 --
src/scenes/project.js | 5 ++++-
7 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index ceb66787a..745bec3d8 100644
--- a/README.md
+++ b/README.md
@@ -294,14 +294,14 @@ Choose a first-run mission, or try these in order. The GIFs show Google Photorea

-6. **Switch the optics.** Tap `1`–`7` — CRT, NVG, FLIR — and the whole live planet re-renders through a different sensor.
+6. **Switch the optics.** Tap `1`–`6` — CRT, NVG, FLIR — and the whole live planet re-renders through a different sensor.

7. **Talk to it** _(needs an OpenAI key)_: _"Take me to LAX and select the nearest airborne aircraft."_
8. **Come home.** Hit **Reset Globe** — or just say _"zoom out to a globe view."_
-**Keyboard:** `1`–`7` visual styles · `H` HUD · `D` detection · `C` cockpit · `` ` `` frame rate · `Esc` out. Trackpad pinch zooms the globe.
+**Keyboard:** `1`–`6` visual styles · `H` HUD · `D` detection · `C` cockpit · `` ` `` frame rate · `Esc` out. Trackpad pinch zooms the globe.
---
diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json
index 8aabedc08..b49872430 100644
--- a/scripts/package-boundaries.json
+++ b/scripts/package-boundaries.json
@@ -964,7 +964,8 @@
"src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
- "src/ui/unitLabels.js"
+ "src/ui/unitLabels.js",
+ "src/visualStylePolicy.js"
],
"external": ["cesium"]
},
diff --git a/src/director/document.test.mjs b/src/director/document.test.mjs
index c0b8b0935..9294abb7b 100644
--- a/src/director/document.test.mjs
+++ b/src/director/document.test.mjs
@@ -135,6 +135,15 @@ test('document byte, nesting, collection, finite-number and string bounds are en
);
});
+test('a shot saved with the retired Snow style migrates to Normal', () => {
+ const project = fixture();
+ project.scenes[0].shots[0].visual = { style: 'snow' };
+ const migrated = normalizeProject(
+ parseSceneDocument(JSON.stringify(project)),
+ );
+ assert.equal(migrated.scenes[0].shots[0].visual.style, 'normal');
+});
+
test('captured scope and extended detection edits survive migration', () => {
const project = fixture();
project.scenes[0].shots[0].visual = {
diff --git a/src/i18n/locales/it.json b/src/i18n/locales/it.json
index 4594275cc..502b50605 100644
--- a/src/i18n/locales/it.json
+++ b/src/i18n/locales/it.json
@@ -1700,7 +1700,6 @@
"Thermal": "Termico",
"Anime": "Anime",
"Noir": "Noir",
- "Snow": "Neve",
"Current cockpit vision style: {style}. Activate for next style.": "Stile di visione attuale del cockpit: {style}. Attiva per lo stile successivo.",
"Current style: {style} — click for next": "Stile attuale: {style}. Clic per il successivo",
"Collapse Contact panel": "Comprimi il pannello Contatto",
@@ -2781,7 +2780,6 @@
"Simulate FLIR-style thermal contrast. Turn up Ironbow for color.": "Simula il contrasto termico in stile FLIR. Alza Ironbow per il colore.",
"Apply bright cel-shaded color and illustrated outlines.": "Applica colori vivaci cel-shaded e contorni illustrati.",
"Apply high-contrast monochrome film-noir grading.": "Applica un color grading noir monocromatico ad alto contrasto.",
- "Add a cold, snowy whiteout treatment to the scene.": "Aggiungi alla scena un effetto freddo di nevicata e whiteout.",
"MAP SOURCE": "FONTE MAPPA",
"Map source": "Fonte mappa",
"Style": "Stile",
diff --git a/src/i18n/locales/pt.json b/src/i18n/locales/pt.json
index 087aeb106..f139e517a 100644
--- a/src/i18n/locales/pt.json
+++ b/src/i18n/locales/pt.json
@@ -1700,7 +1700,6 @@
"Thermal": "Térmico",
"Anime": "Anime",
"Noir": "Noir",
- "Snow": "Neve",
"Current cockpit vision style: {style}. Activate for next style.": "Estilo atual de visão da cabine: {style}. Ative para o próximo estilo.",
"Current style: {style} — click for next": "Estilo atual: {style} — clique para o próximo",
"Collapse Contact panel": "Recolher painel de contato",
@@ -2783,7 +2782,6 @@
"Simulate FLIR-style thermal contrast. Turn up Ironbow for color.": "Simular contraste térmico estilo FLIR. Aumente o Ironbow para ter cor.",
"Apply bright cel-shaded color and illustrated outlines.": "Aplicar cores vivas em cel shading e contornos ilustrados.",
"Apply high-contrast monochrome film-noir grading.": "Aplicar gradação monocromática de filme noir com alto contraste.",
- "Add a cold, snowy whiteout treatment to the scene.": "Adicionar à cena um efeito frio de nevasca branca.",
"MAP SOURCE": "FONTE DO MAPA",
"Map source": "Fonte do mapa",
"Style": "Estilo",
diff --git a/src/i18n/locales/uk.json b/src/i18n/locales/uk.json
index 338a4fcc7..2e6fa95c0 100644
--- a/src/i18n/locales/uk.json
+++ b/src/i18n/locales/uk.json
@@ -1723,7 +1723,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": "Згорнути панель контактів",
@@ -2850,7 +2849,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": "Стиль",
diff --git a/src/scenes/project.js b/src/scenes/project.js
index eb3084962..f2297afaf 100644
--- a/src/scenes/project.js
+++ b/src/scenes/project.js
@@ -7,6 +7,7 @@ import {
BLOOM_SCALE_VERSION,
decodeBloomIntensity,
} from '../bloom.js';
+import { RETIRED_STYLES } from '../visualStylePolicy.js';
/** @constant {number} Current schema version for project migration */
export const PROJECT_VERSION = SCENE_DOCUMENT_VERSION;
@@ -262,7 +263,9 @@ export function normalizeShot(
...(rawShot?.dataPackIds ? { dataPackIds: [...rawShot.dataPackIds] } : {}),
...(rawShot?.move ? { move: deepClone(rawShot.move) } : {}),
visual: {
- style: visual.style || 'normal',
+ style: RETIRED_STYLES.includes(visual.style)
+ ? 'normal'
+ : visual.style || 'normal',
bloom: normalizeBloomState(bloom, {
projectVersion,
fallbackIntensity: 50,