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'],