Files
long-range-scan/src/game/aim.test.ts
T
jcoffey-dev c00772058e Point at it: the remaster plays with the mouse
Click a sector to warp to it, a raider to put a torpedo through it, a
starbase to moor alongside, or a quadrant on the chart to cross to it.
Hovering draws the course and says what it will cost first, which is also how
anybody works out that course 3 is north.

None of it is a new move. A click becomes the same course and warp factor the
prompt takes, at the same one decimal place, and the log writes the questions
and the answers as though they had been typed. The keyboard still works on
both skins.

The conversion is the inverse of a piecewise-linear compass, so it is found by
searching the eighty-one courses a player could actually type rather than by
inverting eight octants of algebra. It is tested by flying it: all 4,032
ordered pairs of sectors in a quadrant, turned into a course and a warp and
flown by the engine's own arithmetic, have to land on the sector that was
clicked. They do.

NAV, TOR and SRS leave the remaster's command row, because each of them is
now a thing on screen to click. What is left becomes an action bar, and the
two that spend energy ask how much as a row of amounts rather than a second
prompt.
2026-09-08 23:53:23 -07:00

86 lines
2.8 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { SECTORS } from './constants'
import { courseTo, landing, navFor, stepsFor } from './aim'
import { heading } from './galaxy'
/**
* Clicking a sector has to arrive at that sector, or the remaster is a
* different game from the one on paper rather than the same one with the
* lamp on. This is the test that says so.
*/
describe('reading a course off a direction', () => {
it('recovers the eight spokes exactly', () => {
const spokes: [number, number, number][] = [
[0, 1, 1], // east, along the row
[-1, 1, 2],
[-1, 0, 3], // north
[-1, -1, 4],
[0, -1, 5],
[1, -1, 6],
[1, 0, 7], // south
[1, 1, 8],
]
for (const [row, col, course] of spokes) {
expect(courseTo({ row, col }), `${row},${col}`).toBe(course)
}
})
it('counts steps along the heading, not as the crow flies', () => {
// Three diagonal steps is three, even though it covers 4.24 sectors.
expect(stepsFor({ row: 3, col: 3 }, 8)).toBe(3)
expect(stepsFor({ row: 0, col: 5 }, 1)).toBe(5)
})
})
describe('clicking a sector', () => {
/**
* The whole grid against itself: 4032 ordered pairs of distinct sectors,
* every one of them turned into a course and a warp and then flown by the
* same arithmetic the engine uses.
*/
it('lands on the sector that was clicked, from anywhere to anywhere', () => {
const misses: string[] = []
for (let fr = 0; fr < SECTORS; fr++) {
for (let fc = 0; fc < SECTORS; fc++) {
for (let tr = 0; tr < SECTORS; tr++) {
for (let tc = 0; tc < SECTORS; tc++) {
if (fr === tr && fc === tc) continue
const from = { row: fr, col: fc }
const d = { row: tr - fr, col: tc - fc }
const { course, warp } = navFor(d)
const at = landing(from, course, warp)
if (at.row !== tr || at.col !== tc) {
misses.push(`${fr},${fc} -> ${tr},${tc} landed ${at.row},${at.col}`)
}
}
}
}
}
expect(misses).toEqual([])
})
it('asks for a course and a warp a player could have typed', () => {
const { course, warp } = navFor({ row: -2, col: 5 })
expect(course).toBeGreaterThanOrEqual(1)
expect(course).toBeLessThan(9)
// One decimal place, which is what both prompts accept.
expect(course * 10).toBeCloseTo(Math.round(course * 10))
expect(warp * 10).toBeCloseTo(Math.round(warp * 10))
})
})
describe('clicking a quadrant', () => {
it('crosses whole quadrants and keeps the sector you were in', () => {
// Two quadrants east is sixteen sectors east, and lands in the same
// sector of the new one.
const d = { row: 0, col: 2 * SECTORS }
const { course, warp } = navFor(d)
expect(course).toBe(1)
expect(warp).toBe(2)
expect(heading(course)).toEqual({ row: 0, col: 1 })
})
})