| 1 | import { NextDraw } from '../src/nextdraw.js';
|
| 2 | import { EBBSimulator, VirtualClock } from '../src/sim/ebb-sim.js';
|
| 3 |
|
| 4 | /** A connected-ready NextDraw wired to a simulated machine. */
|
| 5 | export function makeMachine(simOptions = {}, configure = null) {
|
| 6 | const clock = new VirtualClock();
|
| 7 | const sim = new EBBSimulator({ clock, ...simOptions });
|
| 8 | const messages = [];
|
| 9 | const nd = new NextDraw({ transport: sim, clock, log: (m) => messages.push(m) });
|
| 10 | nd.interactive();
|
| 11 | configure?.(nd);
|
| 12 | return { nd, sim, clock, messages };
|
| 13 | }
|
| 14 |
|
| 15 | export async function connected(simOptions = {}, configure = null) {
|
| 16 | const m = makeMachine(simOptions, configure);
|
| 17 | const ok = await m.nd.connect();
|
| 18 | if (!ok) throw new Error(`connect failed: ${m.messages.join(' / ')}`);
|
| 19 | return m;
|
| 20 | }
|
| 21 |
|
| 22 | /** Deterministic PRNG (mulberry32) so failures are reproducible. */
|
| 23 | export function rng(seed) {
|
| 24 | let a = seed >>> 0;
|
| 25 | return () => {
|
| 26 | a = (a + 0x6d2b79f5) >>> 0;
|
| 27 | let t = a;
|
| 28 | t = Math.imul(t ^ (t >>> 15), t | 1);
|
| 29 | t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
| 30 | return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
| 31 | };
|
| 32 | }
|
| 33 |
|
| 34 | function pointSegmentDistance(p, a, b) {
|
| 35 | const dx = b[0] - a[0];
|
| 36 | const dy = b[1] - a[1];
|
| 37 | const len2 = dx * dx + dy * dy;
|
| 38 | let t = len2 > 0 ? ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2 : 0;
|
| 39 | t = Math.max(0, Math.min(1, t));
|
| 40 | return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));
|
| 41 | }
|
| 42 |
|
| 43 | export function distanceToPolylines(p, polylines) {
|
| 44 | let best = Infinity;
|
| 45 | for (const line of polylines) {
|
| 46 | if (line.length === 1) best = Math.min(best, Math.hypot(p[0] - line[0][0], p[1] - line[0][1]));
|
| 47 | for (let i = 1; i < line.length; i++) best = Math.min(best, pointSegmentDistance(p, line[i - 1], line[i]));
|
| 48 | }
|
| 49 | return best;
|
| 50 | }
|
| 51 |
|
| 52 | /** Two-sided (Hausdorff-style, vertex-sampled) distance between polyline sets. */
|
| 53 | export function polylineSetDistance(a, b) {
|
| 54 | let worst = 0;
|
| 55 | for (const line of a) for (const p of line) worst = Math.max(worst, distanceToPolylines(p, b));
|
| 56 | for (const line of b) for (const p of line) worst = Math.max(worst, distanceToPolylines(p, a));
|
| 57 | return worst;
|
| 58 | }
|
| 59 |
|
| 60 | /** Commands sent, with status polling (QG) removed. */
|
| 61 | export function commands(sim) {
|
| 62 | return sim.log.map((l) => l.cmd).filter((c) => c !== 'QG');
|
| 63 | }
|