testsAndMisc/reverse_survivors/src/core/vec-rng.test.ts
Krzysztof Rudnicki a47191673a feat(reverse_survivors): migrate Reverse Survivors game into the monorepo
Moves the standalone React 19 + Vite 8 + TypeScript 6 game from
~/Downloads/reverse/reverse-survivors into reverse_survivors/, so it is
version-controlled, indexed, and covered by CI instead of sitting untracked
outside any repository.

- Relocates the project's nested .github/workflows/ci.yml to the repository
  root as .github/workflows/reverse-survivors-ci.yml. GitHub only reads
  workflows from the root, so the nested copy would have been inert — same
  treatment billsplit received. The workflow is path-scoped to
  reverse_survivors/** and runs npm ci / check / build on Node 22.
- Sets cache-dependency-path on actions/setup-node, since
  defaults.run.working-directory applies only to run: steps and not to
  actions; without it the npm cache looks for a lockfile at the root.
- Keeps npm and the existing package-lock.json. TypeScript stays pinned at
  6.0.3 deliberately: typescript-eslint 8.65 caps its peer range at <6.1.0
  and TS 7 drops the JS compiler API that typed linting needs.
- Adds a reverse_survivors/ row to the Repository Layout table in README.md
  and CLAUDE.md.

Verified in place: npm ci, npm run check (95 tests, 100% statements /
branches / functions / lines), npm run build, and a dev-server smoke check
serving /src/main.tsx.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 10:45:12 +02:00

74 lines
2.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { createRng, nextFloat, nextInt, pick } from './rng'
import { clamp, dist, toward } from './vec'
describe('vec', () => {
it('measures distance', () => {
expect(dist({ x: 0, y: 0 }, { x: 3, y: 4 })).toBe(5)
})
it('builds a velocity toward a target with the requested speed', () => {
const v = toward({ x: 0, y: 0 }, { x: 10, y: 0 }, 50)
expect(v).toEqual({ x: 50, y: 0 })
})
it('returns zero velocity for coincident points', () => {
expect(toward({ x: 5, y: 5 }, { x: 5, y: 5 }, 99)).toEqual({ x: 0, y: 0 })
})
it('clamps below, inside, and above the range', () => {
expect(clamp(-1, 0, 10)).toBe(0)
expect(clamp(4, 0, 10)).toBe(4)
expect(clamp(11, 0, 10)).toBe(10)
})
})
describe('rng', () => {
it('is deterministic for a given seed', () => {
const a = createRng(123)
const b = createRng(123)
expect([nextFloat(a), nextFloat(a), nextFloat(a)])
.toEqual([nextFloat(b), nextFloat(b), nextFloat(b)])
})
it('produces different sequences for different seeds', () => {
expect(nextFloat(createRng(1))).not.toBe(nextFloat(createRng(2)))
})
it('stays within [0, 1)', () => {
const rng = createRng(77)
for (let i = 0; i < 200; i += 1) {
const v = nextFloat(rng)
expect(v).toBeGreaterThanOrEqual(0)
expect(v).toBeLessThan(1)
}
})
it('wraps negative seeds into unsigned space', () => {
expect(createRng(-1).s).toBe(0xffffffff)
})
it('nextInt stays within [0, max)', () => {
const rng = createRng(9)
for (let i = 0; i < 100; i += 1) {
const v = nextInt(rng, 4)
expect(v).toBeGreaterThanOrEqual(0)
expect(v).toBeLessThan(4)
}
})
it('pick eventually returns every element of a tuple', () => {
const rng = createRng(5)
const seen = new Set<string>()
for (let i = 0; i < 64; i += 1) {
seen.add(pick(rng, ['a', 'b', 'c'] as const))
}
expect(seen).toEqual(new Set(['a', 'b', 'c']))
})
it('pick is deterministic', () => {
expect(pick(createRng(42), [10, 20, 30] as const))
.toBe(pick(createRng(42), [10, 20, 30] as const))
})
})