import { test } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { Mesh, MeshBasicMaterial, Raycaster, Vector3 } from 'three'; import { GISAMUN } from '../src/constants/gisamun.ts'; import { isCoastalLand } from '../src/scene/coast.ts'; import { createTerrainGeometry, validateTerrain } from '../src/scene/terrainGeometry.ts'; import { calculateSun } from '../src/astronomy/sunCalculator.ts'; import { directionFromAngles } from '../src/astronomy/direction.ts'; const base = new URL('../public/terrain/gisamun/', import.meta.url); test('Gisamun observer is on land and the eastern view is sea, with both DSM tiles used', () => { const meta=JSON.parse(readFileSync(new URL('metadata.json',base),'utf8')); const coast=JSON.parse(readFileSync(new URL('coast.json',base),'utf8')); const bytes=readFileSync(new URL('heights.f32',base)); const heights=new Float32Array(bytes.buffer.slice(bytes.byteOffset,bytes.byteOffset+bytes.byteLength)); validateTerrain(meta,heights,GISAMUN); assert.ok(isCoastalLand(coast,0,0)); assert.equal(isCoastalLand(coast,200,0),false); assert.equal(meta.sources.filter((s:{file:string})=>s.file.endsWith('.tif')).length,2); assert.ok(heights.every(Number.isFinite)); assert.ok(heights.some(h=>h<0), 'retain below-zero Copernicus samples instead of clamping to OSM land/sea heights'); assert.equal(meta.corrections.osmHeightCorrection,false); }); test('Gisamun terrain clears the October dawn sun that is blocked at Hajodae', () => { const meta=JSON.parse(readFileSync(new URL('metadata.json',base),'utf8')); const bytes=readFileSync(new URL('heights.f32',base)); const heights=new Float32Array(bytes.buffer.slice(bytes.byteOffset,bytes.byteOffset+bytes.byteLength)); const geometry=createTerrainGeometry(meta,heights,true), material=new MeshBasicMaterial(); const mesh=new Mesh(geometry,material); const observer={...GISAMUN,elevation:meta.observerSurfaceElevation,eyeHeight:1.7}; for (const time of ['06:52','06:55','07:00']) { const sun=calculateSun(new Date(`2026-10-29T${time}:00+09:00`),observer); assert.ok(sun.altitude>0); const ray=new Raycaster(new Vector3(0,1.7,0),new Vector3(...directionFromAngles(sun.azimuth,sun.altitude))); assert.equal(ray.intersectObject(mesh).length,0,`${time} should clear the modelled terrain`); } geometry.dispose(); material.dispose(); });