Split The code is public; the app answers only to the crew.

A snapshot records the map it was played on: `Saved` gains `map`, validated like every other field #163

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/cs:scout/160-a-snapshot-records-the-map-it-was-played → main 1 commit fork

  • Merged. scout/160-a-snapshot-records-the-map-it-was-played was merged into main as 013620e.

Review

Sign in to review this change.
2 files changed +20 −1
src/client/saved.test.ts +9
@@ -47,6 +47,7 @@ const validSaved: Saved = {
4747 losses: { t: 1, ct: 0 },
4848 playerTeam: "ct",
4949 seed: 12345,
50 map: "de_cinder",
5051 name: "scout",
5152 players: Array.from({ length: 10 }, (_, i) => ({
5253 money: 4000 + i,
@@ -76,6 +77,7 @@ test("snapshot reads only what world.ts already exports, and parses back", () =>
7677 expect(saved.players).toHaveLength(10);
7778 expect(saved.matchId).toBe(matchId);
7879 expect(saved.seed).toBe(world.seed);
80 expect(saved.map).toBe("de_forge");
7981 expect(saved.name).toBe("scout");
8082 expect(parseSaved(JSON.stringify(saved))).toEqual(saved);
8183 });
@@ -141,6 +143,13 @@ test("the wrong number of players is not a match", () => {
141143 expect(parseSaved(JSON.stringify(long))).toBeNull();
142144 });
143145
146 test("an unknown, wrongly-typed or missing map is not a match", () => {
147 expect(parseSaved(JSON.stringify({ ...validSaved, map: "de_nonsense" }))).toBeNull();
148 expect(parseSaved(JSON.stringify({ ...validSaved, map: 7 }))).toBeNull();
149 const { map: _map, ...withoutMap } = validSaved;
150 expect(parseSaved(JSON.stringify(withoutMap))).toBeNull();
151 });
152
144153 test("a matchId that is not the shape the server issues is not a match", () => {
145154 for (const matchId of ["", "nope", "../../etc", "1", "a".repeat(65)]) {
146155 expect(parseSaved(JSON.stringify({ ...validSaved, matchId }))).toBeNull();
src/client/saved.ts +11 −1
@@ -14,6 +14,7 @@
1414 * match — wiring this into `main.ts` and `world.ts` — is the next issue.
1515 */
1616
17 import { MAP_NAMES, type MapName } from "./map.ts";
1718 import type { Rules, Team } from "./match.ts";
1819 import { LOSS_BONUS } from "./match.ts";
1920 import { WEAPONS, type WeaponId } from "./weapons.ts";
@@ -51,6 +52,7 @@ export type Saved = {
5152 losses: { t: number; ct: number };
5253 playerTeam: Team;
5354 seed: number;
55 map: MapName;
5456 name: string;
5557 players: SavedPlayer[];
5658 };
@@ -95,6 +97,9 @@ export function snapshot(world: World, matchId: string): Saved {
9597 losses: { ...world.match.losses },
9698 playerTeam: world.match.playerTeam,
9799 seed: world.seed,
100 // world.map.name is a plain string in MapDef, but every MapDef comes from
101 // MAPS, so it is always one of MAP_NAMES.
102 map: world.map.name as MapName,
98103 name: world.me.name,
99104 players: world.players.map(savePlayer),
100105 };
@@ -124,6 +129,8 @@ const inRange = (n: number, lo: number, hi: number): boolean => n >= lo && n <=
124129
125130 const isWeaponId = (v: unknown): v is WeaponId => typeof v === "string" && Object.hasOwn(WEAPONS, v);
126131
132 const isMapName = (v: unknown): v is MapName => typeof v === "string" && (MAP_NAMES as readonly string[]).includes(v);
133
127134 const encoder = new TextEncoder();
128135 const byteLength = (s: string): number => encoder.encode(s).length;
129136
@@ -195,6 +202,9 @@ export function parseSaved(raw: string | null): Saved | null {
195202 const seed = parsed.seed;
196203 if (!wholeNumber(seed) || !inRange(seed, 0, 0xffffffff)) return null;
197204
205 const map = parsed.map;
206 if (!isMapName(map)) return null;
207
198208 const name = parsed.name;
199209 if (typeof name !== "string" || name.length < 1 || name.length > MAX_NAME_LENGTH) return null;
200210
@@ -207,7 +217,7 @@ export function parseSaved(raw: string | null): Saved | null {
207217 players.push(player);
208218 }
209219
210 return { rules, matchId, round, score, losses, playerTeam, seed, name, players };
220 return { rules, matchId, round, score, losses, playerTeam, seed, map, name, players };
211221 }
212222
213223 const KEY = "cs.saved";

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs