- Music, sound and voice volumes are shared by all games by default; a game that
has never been launched with its own volumes plays with the shared ones
- The sound section of the player settings has a "own volume for this game" switch:
turning it on starts from the current values and then the sliders change only this
game, turning it off removes the override and returns to the shared volumes
- The override belongs to the game (game_id from /api/versions/{id}), so forks and mods
share it; it falls back to the version id when the id is unavailable
- Reading speed and auto delay stay shared
- volume-store.ts keeps the storage logic (damaged data ignored, values clamped,
unavailable storage tolerated) and has its own tests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
59 lines
2.6 KiB
JavaScript
59 lines
2.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { fileURLToPath } from "node:url";
|
|
import { test } from "node:test";
|
|
import { build } from "esbuild";
|
|
|
|
const compiled = await build({ entryPoints: [fileURLToPath(new URL("../src/player/volume-store.ts", import.meta.url))],
|
|
bundle: true, write: false, format: "esm", platform: "node", logLevel: "silent" });
|
|
const { VolumeStore } = await import(`data:text/javascript;base64,${Buffer.from(compiled.outputFiles[0].text).toString("base64")}`);
|
|
|
|
function storage(initial = {}) {
|
|
const data = new Map(Object.entries(initial));
|
|
return {
|
|
data,
|
|
getItem: (k) => data.get(k) ?? null,
|
|
setItem: (k, v) => void data.set(k, v),
|
|
removeItem: (k) => void data.delete(k),
|
|
};
|
|
}
|
|
|
|
test("a game that was never launched has no volumes of its own, so it plays with the shared ones", () => {
|
|
const store = new VolumeStore("pgvn:audio:game:7", storage());
|
|
assert.equal(store.read(), null);
|
|
});
|
|
|
|
test("own volumes are kept per game and do not touch other games", () => {
|
|
const backing = storage();
|
|
new VolumeStore("pgvn:audio:game:7", backing).write({ music: 0.2, sound: 0.3, voice: 1 });
|
|
assert.deepEqual(new VolumeStore("pgvn:audio:game:7", backing).read(), { music: 0.2, sound: 0.3, voice: 1 });
|
|
assert.equal(new VolumeStore("pgvn:audio:game:8", backing).read(), null);
|
|
});
|
|
|
|
test("clearing returns the game to the shared volumes", () => {
|
|
const backing = storage();
|
|
const store = new VolumeStore("k", backing);
|
|
store.write({ music: 0, sound: 0, voice: 0 });
|
|
store.clear();
|
|
assert.equal(store.read(), null);
|
|
assert.equal(backing.data.size, 0);
|
|
});
|
|
|
|
test("damaged or out-of-range data is ignored or limited, and a missing key means shared volumes only", () => {
|
|
assert.equal(new VolumeStore("k", storage({ k: "{not json" })).read(), null);
|
|
assert.equal(new VolumeStore("k", storage({ k: JSON.stringify({ music: "loud", sound: 1, voice: 1 }) })).read(), null);
|
|
assert.deepEqual(new VolumeStore("k", storage({ k: JSON.stringify({ music: 5, sound: -1, voice: 0.5 }) })).read(),
|
|
{ music: 1, sound: 0, voice: 0.5 });
|
|
const none = new VolumeStore(null, storage());
|
|
assert.equal(none.available, false);
|
|
none.write({ music: 1, sound: 1, voice: 1 });
|
|
assert.equal(none.read(), null);
|
|
});
|
|
|
|
test("an unavailable storage does not break playing", () => {
|
|
const broken = { getItem: () => { throw new Error("blocked"); }, setItem: () => { throw new Error("blocked"); },
|
|
removeItem: () => { throw new Error("blocked"); } };
|
|
const store = new VolumeStore("k", broken);
|
|
assert.equal(store.read(), null);
|
|
assert.doesNotThrow(() => { store.write({ music: 1, sound: 1, voice: 1 }); store.clear(); });
|
|
});
|