tower / interior-flight / files / test / instruments.test.ts
Branch interior-flight · default branch · all branches
instruments.test.ts
e5555695 2026-09-07 Pavel Safronov | 1 | /** |
| 2 | * The sightline probe reproduces the table in design.md §1.8 — the two-horizons | |
| 3 | * result, which is canon and came out of this calculation in the first place. | |
| 4 | */ | |
| 5 | import { describe, expect, it } from 'vitest' | |
| 6 | import { LENGTH_M, RADIUS_M } from '../src/canon/constants.js' | |
| 7 | import { sightline, travel } from '../src/world/instruments.js' | |
| 8 | import { landStripCentre } from '../src/canon/geometry.js' | |
| 9 | import { setSlatCount } from '../src/canon/slats.js' | |
| 10 | import { SLAT_COUNT_DEFAULT } from '../src/canon/constants.js' | |
| 11 | ||
| 12 | const along = (m: number) => sightline({ theta: 0, z: 0 }, { theta: 0, z: m }) | |
| 13 | /** A point `deg` around the cylinder at the same z — a sightline across the interior. */ | |
| 14 | const across = (deg: number) => | |
| 15 | sightline({ theta: 0, z: 0 }, { theta: (deg * Math.PI) / 180, z: 0 }) | |
| 16 | ||
| 17 | describe('§1.8 looking along the axis', () => { | |
| 18 | it('is 31% clear at 100 km', () => expect(along(100_000).transmission).toBeCloseTo(0.31, 2)) | |
| 19 | it('leaves the far cap a 4% ghost from the midpoint', () => { | |
| 20 | const s = along(LENGTH_M / 2) | |
| 21 | expect(s.transmission).toBeCloseTo(0.044, 3) | |
| 22 | expect(s.clarity).toBe('ghost') | |
| 23 | }) | |
| 24 | it('closes the horizon between 100 and 150 km', () => { | |
| 25 | expect(along(100_000).clarity).toBe('hazy') | |
| 26 | expect(along(150_000).clarity).toBe('ghost') | |
| 27 | }) | |
| 28 | it('never rises off the floor — the axial floor is flat', () => | |
| 29 | expect(along(100_000).apexHeightM).toBeCloseTo(0, 6)) | |
| 30 | }) | |
| 31 | ||
| 32 | describe('§1.8 looking across the interior', () => { | |
| 33 | it('is 78% clear to the point directly overhead, 100 km away', () => { | |
| 34 | const s = across(180) | |
| 35 | expect(s.directDistanceM).toBeCloseTo(2 * RADIUS_M, 6) | |
| 36 | expect(s.transmission).toBeCloseTo(0.78, 2) | |
| 37 | expect(s.clarity).toBe('crisp') | |
| 38 | }) | |
| 39 | it('stays crisp all the way round', () => { | |
| 40 | for (const d of [30, 60, 90, 135, 180]) expect(across(d).clarity).toBe('crisp') | |
| 41 | }) | |
| 42 | it('rises to 14.6 km at 90° — why looking up beats looking along', () => | |
| 43 | expect(across(90).apexHeightM / 1000).toBeCloseTo(14.6, 1)) | |
| 44 | }) | |
| 45 | ||
| 46 | describe('§1.8 the headline: overhead beats ground at the same distance', () => { | |
| 47 | it('is more than twice as clear across the interior at 100 km', () => { | |
| 48 | const axial = along(100_000) | |
| 49 | const overhead = across(180) | |
| 50 | expect(overhead.directDistanceM).toBeCloseTo(axial.directDistanceM, 0) | |
| 51 | expect(overhead.transmission).toBeGreaterThan(axial.transmission * 2) | |
| 52 | }) | |
| 53 | it('gets clearer with distance across, the opposite of along', () => { | |
| 54 | expect(across(180).transmission).toBeGreaterThan(across(60).transmission) | |
| 55 | expect(along(150_000).transmission).toBeLessThan(along(50_000).transmission) | |
| 56 | }) | |
| 57 | }) | |
| 58 | ||
| 59 | describe('travel', () => { | |
| 60 | it('reports no slat crossing with the hull sealed (D-017)', () => { | |
| 61 | const t = travel({ theta: 0, z: 0 }, { theta: Math.PI, z: 0 }, 1.4) | |
| 62 | expect(t.crossesSlat).toBe(false) | |
| 63 | }) | |
| 64 | ||
| 65 | it('times a walk and notices slat crossings when slats exist', () => { | |
| 66 | setSlatCount(8) | |
| 67 | const t = travel( | |
| 68 | { theta: landStripCentre(0), z: 0 }, | |
| 69 | { theta: landStripCentre(1), z: 0 }, | |
| 70 | 1.4, | |
| 71 | ) | |
| 72 | expect(t.crossesSlat).toBe(true) | |
| 73 | expect(t.durationS).toBeCloseTo(t.distanceM / 1.4, 6) | |
| 74 | setSlatCount(SLAT_COUNT_DEFAULT) | |
| 75 | }) | |
| 76 | }) |