const test = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const ts = require('typescript'); const exported = {}; const source = fs.readFileSync(path.join(__dirname, '../assets/Script/LaserRules.ts'), 'utf8'); vm.runInNewContext(ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2017, module: ts.ModuleKind.CommonJS }, }).outputText, { exports: exported }); const { nearestLaserHit } = exported; const point = (x, y) => ({ x, y }); const rect = (minX, minY, maxX, maxY) => ({ type: 'rect', minX, minY, maxX, maxY }); const polygon = coordinates => ({ type: 'polygon', points: coordinates.map(([x, y]) => point(x, y)) }); const close = (actual, expected) => assert.ok(Math.abs(actual - expected) < 1e-6, `${actual} != ${expected}`); for (const [direction, start, end, expected] of [ ['right', point(0, 5), point(10, 5), point(4, 5)], ['left', point(10, 5), point(0, 5), point(6, 5)], ['up', point(5, 0), point(5, 10), point(5, 4)], ['down', point(5, 10), point(5, 0), point(5, 6)], ]) { test(`${direction} beam ends at the nearest rectangle surface`, () => { const hit = nearestLaserHit(start, end, [rect(4, 4, 6, 6)]); close(hit.ratio, 0.4); close(hit.distance, 4); close(hit.point.x, expected.x); close(hit.point.y, expected.y); }); } test('nearest blocker wins regardless of collider ordering', () => { const hit = nearestLaserHit(point(0, 0), point(10, 0), [ rect(8, -1, 9, 1), { type: 'circle', center: point(5, 0), radius: 1 }, polygon([[2, -1], [3, -1], [3, 1], [2, 1]]), ]); assert.equal(hit.colliderIndex, 2); close(hit.distance, 2); }); test('fractional dragging positions unblock immediately, without grid snapping', () => { const start = point(0, 0), end = point(10, 0); const moving = rect(4.125, -0.5, 5.125, 0.5); close(nearestLaserHit(start, end, [moving]).distance, 4.125); moving.minY += 0.501; moving.maxY += 0.501; assert.equal(nearestLaserHit(start, end, [moving]), null); moving.minY -= 0.002; moving.maxY -= 0.002; close(nearestLaserHit(start, end, [moving]).distance, 4.125); }); test('a beam in the empty part of a concave shape is not blocked by its bounds', () => { const lShape = polygon([[2, 0], [6, 0], [6, 1], [3, 1], [3, 4], [2, 4]]); assert.equal(nearestLaserHit(point(4, 3), point(7, 3), [lShape]), null); const hit = nearestLaserHit(point(7, 3), point(0, 3), [lShape]); close(hit.point.x, 3); close(hit.distance, 4); lShape.points.reverse(); close(nearestLaserHit(point(7, 3), point(0, 3), [lShape]).distance, 4); }); test('circle surface, tangent, and a clear line are distinguished', () => { const circle = { type: 'circle', center: point(5, 0), radius: 2 }; close(nearestLaserHit(point(0, 0), point(10, 0), [circle]).distance, 3); close(nearestLaserHit(point(0, 2), point(10, 2), [circle]).distance, 5); assert.equal(nearestLaserHit(point(0, 2.001), point(10, 2.001), [circle]), null); }); test('an emitter inside any solid has a zero-distance hit', () => { const solids = [ rect(-1, -1, 1, 1), { type: 'circle', center: point(0, 0), radius: 1 }, polygon([[-1, -1], [1, -1], [1, 1], [-1, 1]]), ]; for (const solid of solids) { const hit = nearestLaserHit(point(0, 0), point(10, 0), [solid]); assert.equal(hit.ratio, 0); assert.equal(hit.distance, 0); } }); test('touching a solid boundary blocks, including a collinear polygon edge', () => { for (const solid of [rect(3, 0, 5, 2), polygon([[3, 0], [5, 0], [5, 2], [3, 2]])]) { close(nearestLaserHit(point(0, 0), point(10, 0), [solid]).distance, 3); close(nearestLaserHit(point(10, 0), point(0, 0), [solid]).distance, 5); assert.equal(nearestLaserHit(point(3, 1), point(0, 1), [solid]).ratio, 0); } }); test('objects behind the emitter or beyond the receiver do not block', () => { const solids = [ rect(-3, -1, -2, 1), rect(11, -1, 12, 1), { type: 'circle', center: point(13, 0), radius: 2 }, polygon([[11, -1], [12, -1], [12, 1], [11, 1]]), ]; assert.equal(nearestLaserHit(point(0, 0), point(10, 0), solids), null); close(nearestLaserHit(point(0, 0), point(10, 0), [rect(10, -1, 11, 1)]).ratio, 1); }); test('rotated polygon surfaces use their edges, not their axis-aligned bounds', () => { const diamond = polygon([[5, 0], [7, 2], [5, 4], [3, 2]]); close(nearestLaserHit(point(0, 1), point(10, 1), [diamond]).distance, 4); const diagonal = nearestLaserHit(point(0, 0), point(10, 10), [rect(3, 3, 4, 4)]); close(diagonal.ratio, 0.3); close(diagonal.distance, Math.sqrt(18)); }); test('crossing beams are queried independently against the same solids', () => { const blockers = [rect(7, 4, 8, 6)]; close(nearestLaserHit(point(0, 5), point(10, 5), blockers).distance, 7); assert.equal(nearestLaserHit(point(5, 0), point(5, 10), blockers), null); close(nearestLaserHit(point(0, 5), point(10, 5), blockers).distance, 7); }); test('empty colliders and zero-length lines have defined results', () => { assert.equal(nearestLaserHit(point(0, 0), point(10, 0), []), null); assert.equal(nearestLaserHit(point(0, 0), point(0, 0), [rect(1, 1, 2, 2)]), null); assert.equal(nearestLaserHit(point(0, 0), point(0, 0), [rect(-1, -1, 1, 1)]).distance, 0); });