fuzzyworld

tower / files / test / motion.test.ts

motion.test.ts

2.5 KB · history · blame

1import { describe, expect, it } from 'vitest'
2import { G0, OMEGA_RAD_S } from '../src/canon/constants.js'
3import {
4 RAINDROP_TERMINAL_M_S, freeFallDriftM, freeFallLeanRad, freeFallPathLeanRad,
5 freeFallTimeS, steadyFallLeanRad,
6} from '../src/canon/motion.js'
7
8const deg = (r: number): number => (r * 180) / Math.PI
9
10/**
11 * These two numbers are the entire visible evidence that Tower spins, so they are
12 * worth pinning: the hull is sealed, there are no stars, and everything that does
13 * not fall is co-rotating and therefore perfectly still.
14 */
15describe('falling in the rotating frame', () => {
16 it('leaves rain very nearly vertical', () => {
17 expect(deg(steadyFallLeanRad(RAINDROP_TERMINAL_M_S))).toBeCloseTo(1.5, 1)
18 })
19
20 it('leans a waterfall enough to read as a compass', () => {
21 // A 1 km drop: eight times the lean of rain, and unmistakable.
22 expect(deg(freeFallLeanRad(1000))).toBeCloseTo(11.3, 1)
23 })
24
25 it('drifts an object dropped from 1 km by over a hundred metres', () => {
26 expect(freeFallDriftM(1000)).toBeCloseTo(133, 0)
27 })
28
29 /**
30 * design.md §1.9.2 tabulates 7.6° for a kilometre and this view reports 11.3°.
31 * Both are correct and they are not the same angle, so pin the relation that
32 * makes that true rather than quietly letting the two documents disagree.
33 */
34 it('separates the lean of the path from the lean of the velocity', () => {
35 expect(deg(freeFallPathLeanRad(1000))).toBeCloseTo(7.6, 1)
36 for (const h of [100, 500, 1000, 2000]) {
37 expect(Math.tan(freeFallLeanRad(h))).toBeCloseTo(1.5 * Math.tan(freeFallPathLeanRad(h)), 9)
38 }
39 })
40
41 it('grows the lean with the drop, which is why the waterfall wins', () => {
42 const heights = [10, 100, 1000, 4000]
43 const leans = heights.map(freeFallLeanRad)
44 for (let i = 1; i < leans.length; i++) expect(leans[i]!).toBeGreaterThan(leans[i - 1]!)
45 // Rain does not: at terminal velocity the angle is fixed whatever the cloud.
46 expect(steadyFallLeanRad(RAINDROP_TERMINAL_M_S))
47 .toBe(steadyFallLeanRad(RAINDROP_TERMINAL_M_S))
48 })
49
50 it('agrees with the closed forms it is derived from', () => {
51 const h = 2500
52 const t = freeFallTimeS(h)
53 expect(t).toBeCloseTo(Math.sqrt((2 * h) / G0), 9)
54 expect(freeFallLeanRad(h)).toBeCloseTo(Math.atan(OMEGA_RAD_S * t), 9)
55 expect(freeFallDriftM(h)).toBeCloseTo((OMEGA_RAD_S * G0 * t ** 3) / 3, 6)
56 })
57
58 it('leans everything the same way — antispinward, always', () => {
59 expect(steadyFallLeanRad(1)).toBeGreaterThan(0)
60 expect(freeFallLeanRad(1)).toBeGreaterThan(0)
61 })
62})