{"version":3,"sources":["ship.mjs"],"sourcesContent":["// SHIPS — vessels phase 3 (the owner's picks, confirmed Oct 12 23:53 UTC, and the phase-3 brief of the same day).\n//\n// Byte-identical in public/ and functions/lib/ (generate-version-hash.js holds them equal). The server decides every trip\n// with it (functions/ships.js: what a trip costs, what pays, who rides and who stays in orbit); the page\n// quotes the same numbers on the Travel here button and the pilot's panel before the press. Pure, so tests/ships.test.mjs\n// pins every rule without a server or a renderer.\n//\n// ─── THE OWNER'S PICKS, AND HOW EACH IS READ HERE ─────────────────────────────────────────────────────────────────────\n//   SHIP      A vessel with at least one thruster aboard is a SHIP; without, a STATION (vessels.mjs kind). Both drives count.\n//   DRIVES    Two thruster parts, \"both with varying power — but both can be used in either scenario\": either one makes any\n//             trip, in a system or between systems and galaxies. They differ in THRUST and in what they burn:\n//               EM Drive            weak (EM_THRUST each) and cheap: it runs on the vessel's power pool (vessel-life.mjs),\n//                                   EM_POWER_PER_FARE unit-minutes of stored charge per unit of fare.\n//               Methalox Thruster   strong (MX_THRUST each): it burns Liquid Methane and Liquid Oxygen, MX_CH4_PER_FARE and\n//                                   MX_LOX_PER_FARE kubes per unit of fare (one to two, the mix's proportion by kube).\n//   FARE      The trip's fare is the one the server charges a player for the same move (travel.mjs fareFor: a hop between\n//             stars by parsecs, a crossing between galaxies by lattice cells), times a MULTIPLIER from the ship's mass over\n//             its thrust, clamped 1×–4×. A move a player makes for nothing — between two bodies of one system — is a\n//             SHIP_LOCAL_FARE hop for a ship (it is a flight, not a landing), and Move, to another spot round the same body,\n//             SHIP_MOVE_FARE.\n//   MASS      The vessel's kubes weighted by their density (kubes.json / products.json `density`): a whole kube its\n//             density, a slab half of it, a bit an eighth (a bit is a half-kube cube).\n//   WHO PAYS  The SHIP ONLY: its power pool for the EM drives' share, its fuel for the Methalox share, split by thrust.\n//             When it cannot cover a trip with every drive firing, it tries the drives it can feed (planTrip: an EM drive\n//             alone on a solar ship, a Methalox thruster alone when the batteries are low); when nothing can, the trip\n//             cannot start and the button says what is missing (\"Needs 12 more power\" / \"Needs 2 Liquid Oxygen\").\n//   FUEL      What a Methalox thruster burns is carried aboard in CARGO CRATES (no new tank part): Liquid Methane and\n//             Liquid Oxygen as kubes (the Chemical Synthesizer condenses Methane and Oxygen into them, as it does Argon),\n//             a Thermos holding either, and a Gas Canister holding Methane or Oxygen, which counts kube for kube (the\n//             canister is the pressure tank). A kube is opened as a whole: what a trip leaves of it stays in the lines\n//             (`fuelOpen`) and is burnt first next time.\n//   PILOT     Docked at a Suit Dock on the ship's outside (users/{uid}.docked: the player held at the dock, their\n//             whereabouts the ship), the vessel's OWNER or a BUILDER pilots (canPilot); a public dock lets anyone dock, not\n//             fly. The cards' Travel here then moves the ship and its crew, and Move parks it at another free spot round\n//             the same body for a small charge.\n//   CREW      The owner, Oct 13 (\"eliminate the concept of insides\"): a vessel has no inside to be in, so the CREW is the\n//             players DOCKED at its Suit Docks, and only they travel with it (crewOutcome). Everyone else near the vessel\n//             is in orbit in its system and stays there. (Phase 3's jolt for a player floating free inside, and its\n//             \"left in orbit\" for one outside the hull, went with the insides.) A record that has a player aboard but not\n//             docked (from before the change) is left in orbit at the old spot, which is where they are anyway.\n//   ARRIVE    \"Pick on arrival, default to nearest\": the ship parks at the free spot nearest where the card pointed\n//             (arrivalSpot) and for ARRIVAL_PICK_MS its pilot may tap the band for another free spot at no charge.\n//   SCRAP     \"Send into interstellar space\": the owner deletes an empty vessel (a ship or a station); nothing returned.\n//   BERTH     Never: vessels do not dock together.\n\nimport { VESSEL_REACH, parkRanges, nearestFree, orbitAt, canBuildVessel } from './vessels.mjs';\nimport { inGrid, keyCell, cellKeyOf } from './vessel-life.mjs';\n\n// ---- THE PARTS ---------------------------------------------------------------------------------------------------------\nexport const EM_DRIVE = 'EM Drive';\nexport const METHALOX = 'Methalox Thruster';\nexport const isDriveName = (n) => n === EM_DRIVE || n === METHALOX;\nexport const LIQUID_METHANE = 'Liquid Methane';\nexport const LIQUID_OXYGEN = 'Liquid Oxygen';\n\n// ---- THE NUMBERS (tuned so the sample ship reads the owner's sentence: EM cheap but weak, Methalox strong but fuelled) ---\n// A small ship is a few hundred kubes of Steel (density 4.75), 1,000 to 2,000 mass. Two EM drives (400 thrust) fly it at\n// about 3×: an in-system hop is 1 × 3 × 10 = 30 unit-minutes, half of one Battery, which four Solar Panels in good light\n// put back in under ten minutes. Crossing to a star 9 pc away (the median hop, fare 22) on EM alone is 660 unit-minutes:\n// a bank of eleven Batteries, so EM CAN cross systems, slowly and only with storage. One Methalox thruster (1,500 thrust)\n// flies the same ship at 1× and that crossing burns 1.1 Liquid Methane and 2.2 Liquid Oxygen. The longest trips (the fare\n// cap, 100) burn 5 and 10 kubes at 1×.\nexport const EM_THRUST = 200;\nexport const MX_THRUST = 1500;\nexport const MULT_MIN = 1;\nexport const MULT_MAX = 4;\nexport const SHIP_LOCAL_FARE = 1;          // a hop between two bodies of the ship's own system\nexport const SHIP_MOVE_FARE = 0.25;        // Move: another free spot round the same body\nexport const EM_POWER_PER_FARE = 10;       // unit-minutes of the pool per unit of fare (a Battery holds 60)\nexport const MX_CH4_PER_FARE = 0.05;       // kubes of Liquid Methane per unit of fare\nexport const MX_LOX_PER_FARE = 0.1;        // …and of Liquid Oxygen\nexport const ARRIVAL_PICK_MS = 30000;      // after arriving, the pilot may pick another spot round the body for nothing\nexport const TRAVEL_ANIM_MS = 6000;        // how long everyone sees a ship fly from its old spot to its new one, plume lit\nconst EPS = 1e-6;\n\n// ---- MASS AND THRUST ---------------------------------------------------------------------------------------------------\n// `blocks`: the vessel's chunk as names (local key → name; a bit's key has a fourth part, its seat). opts.densityOf(name)\n// (default 1), opts.isSlab(name). The core (vessels.mjs: the Docking Port at 0,0,0) is there whatever the edits say.\nexport function shipFacts(blocks, opts = {}) {\n  const dens = opts.densityOf || (() => 1), isSlab = opts.isSlab || (() => false);\n  let em = 0, mx = 0, mass = 0, kubes = 0, core = false;\n  const drives = [];\n  for (const lk in (blocks || {})) {\n    const n = blocks[lk];\n    if (typeof n !== 'string') continue;\n    const c = keyCell(lk); if (!c) continue;\n    const d = Math.max(0, Number(dens(n)) || 0);\n    if (c.slot != null) { mass += d / 8; continue; }             // a bit: half a kube on each side\n    if (c.x === 0 && c.y === 0 && c.z === 0) core = true;\n    kubes++;\n    mass += isSlab(n) ? d / 2 : d;\n    if (n === EM_DRIVE) { em++; drives.push({ x: c.x, y: c.y, z: c.z, name: n }); }\n    else if (n === METHALOX) { mx++; drives.push({ x: c.x, y: c.y, z: c.z, name: n }); }\n  }\n  if (!core) { mass += Math.max(0, Number(dens('Docking Port')) || 0); kubes++; }\n  return { em, mx, mass: Math.round(mass * 100) / 100, kubes, drives, ship: em + mx > 0 };\n}\nexport const thrustOf = (em, mx) => Math.max(0, em | 0) * EM_THRUST + Math.max(0, mx | 0) * MX_THRUST;\n// THE MULTIPLIER: mass over thrust, never under 1× (a light ship pays the fare) nor over 4× (a heavy one still goes).\nexport function multiplierOf(mass, thrust) {\n  if (!(thrust > 0)) return null;\n  return Math.round(Math.max(MULT_MIN, Math.min(MULT_MAX, Math.max(0, Number(mass) || 0) / thrust)) * 100) / 100;\n}\nexport const kindOfFacts = (f) => (f && f.em + f.mx > 0 ? 'ship' : 'station');\n\n// ---- THE FUEL ABOARD ----------------------------------------------------------------------------------------------------\n// What each stack holds of the two, in kubes: a kube of either liquid is itself; a container (a stack with a fill `q` and a\n// blend `mix`: a Thermos, a Gas Canister) holds q × each fraction, Methane and Oxygen gas counting as their liquids.\nconst FUEL_OF = Object.freeze({ [LIQUID_METHANE]: 'ch4', [LIQUID_OXYGEN]: 'lox', Methane: 'ch4', Oxygen: 'lox' });\nexport function stackFuel(s) {\n  const out = { ch4: 0, lox: 0 };\n  if (!s || typeof s.n !== 'string' || !(s.c > 0)) return out;\n  if (s.q > 0 && s.mix && typeof s.mix === 'object') {\n    for (const k in s.mix) { const f = FUEL_OF[k]; if (f) out[f] += s.c * s.q * (Number(s.mix[k]) || 0); }\n    return out;\n  }\n  if (s.n === LIQUID_METHANE) out.ch4 += s.c;\n  else if (s.n === LIQUID_OXYGEN) out.lox += s.c;\n  return out;\n}\nconst r4 = (v) => Math.round(v * 10000) / 10000;\nexport function crateFuel(slots) {\n  const t = { ch4: 0, lox: 0 };\n  for (const s of slots || []) { const f = stackFuel(s); t.ch4 += f.ch4; t.lox += f.lox; }\n  return { ch4: r4(t.ch4), lox: r4(t.lox) };\n}\n// The vessel's whole stock from its record's summaries: every crate's (fuelBy) and what an opened kube left (fuelOpen).\nexport function fuelTotal(v) {\n  const t = { ch4: 0, lox: 0 };\n  for (const k in (v && v.fuelBy) || {}) { const f = v.fuelBy[k] || {}; t.ch4 += Number(f.ch4) || 0; t.lox += Number(f.lox) || 0; }\n  const o = (v && v.fuelOpen) || {};\n  t.ch4 += Number(o.ch4) || 0; t.lox += Number(o.lox) || 0;\n  return { ch4: r4(t.ch4), lox: r4(t.lox) };\n}\n// BURN `need` ({ ch4, lox } kubes) from the crates (`crates`: [{ key, slots }], each slots array mutated in place) and the\n// open remainder: the remainder first, then containers (exactly, their fill falling), then whole kubes, the last of which is\n// opened and its rest kept open. Deterministic: crates by key, slots in order. Returns { open, short } (short > 0: not\n// enough, and nothing should be written).\nexport function burnFuel(crates, open0, need) {\n  const open = { ch4: Math.max(0, Number(open0 && open0.ch4) || 0), lox: Math.max(0, Number(open0 && open0.lox) || 0) };\n  const short = { ch4: 0, lox: 0 };\n  const list = (crates || []).slice().sort((a, b) => String(a.key).localeCompare(String(b.key)));\n  for (const f of ['ch4', 'lox']) {\n    let left = Math.max(0, Number(need && need[f]) || 0);\n    const take = Math.min(open[f], left); open[f] -= take; left -= take;\n    // containers\n    for (const C of list) for (let i = 0; i < C.slots.length && left > EPS; i++) {\n      const s = C.slots[i]; if (!s || !(s.q > 0) || !s.mix) continue;\n      const names = Object.keys(s.mix).filter((k) => FUEL_OF[k] === f);\n      if (!names.length) continue;\n      const amounts = {}; for (const k in s.mix) amounts[k] = s.q * (Number(s.mix[k]) || 0);\n      // one container at a time: a stack of several takes from each item alike, so a whole stack is drawn in step\n      const per = names.reduce((a, k) => a + amounts[k], 0);\n      if (!(per > 0)) continue;\n      const use = Math.min(per, left / s.c);\n      for (const k of names) amounts[k] -= use * (amounts[k] / per);\n      left -= use * s.c;\n      const q = Object.values(amounts).reduce((a, x) => a + Math.max(0, x), 0);\n      if (q <= 1e-4) { C.slots[i] = { n: s.n, c: s.c }; continue; }        // an emptied container is an empty one\n      const mix = {}; for (const k in amounts) if (amounts[k] > 1e-6) mix[k] = amounts[k] / q;\n      C.slots[i] = { ...s, q: r4(q), mix };\n    }\n    // whole kubes\n    const liquid = f === 'ch4' ? LIQUID_METHANE : LIQUID_OXYGEN;\n    for (const C of list) for (let i = 0; i < C.slots.length && left > EPS; i++) {\n      const s = C.slots[i]; if (!s || s.n !== liquid || s.q > 0) continue;\n      const n = Math.min(s.c, Math.ceil(left - EPS));\n      left -= n;\n      C.slots[i] = s.c - n > 0 ? { ...s, c: s.c - n } : null;\n    }\n    if (left > EPS) short[f] = r4(left);\n    else open[f] += -Math.min(0, left);                                  // what the last opened kube left\n    open[f] = r4(Math.max(0, open[f]));\n  }\n  return { open, short: short.ch4 > 0 || short.lox > 0 ? short : null };\n}\n\n// ---- THE PLAN: WHAT A TRIP COSTS AND WHAT PAYS ----------------------------------------------------------------------------\n// in: { base: the fare before the multiplier, mass, em, mx: drive counts, power: unit-minutes stored, fuel: { ch4, lox },\n//       move?: true for Move (EM first) }\n// out: { ok, fare, mult, fire: 'all' | 'em' | 'mx', power, ch4, lox, missing: null | { power, ch4, lox }, text }\n//   ok     the drives in `fire` can pay it; `power`, `ch4`, `lox` are what it costs\n//   !ok    nothing can: the plan shown is the one with every drive firing, and `missing` what it lacks\nconst FIRE_ORDER = Object.freeze({ trip: ['all', 'em', 'mx'], move: ['em', 'all', 'mx'] });\nfunction costFor(base, mass, em, mx, fire) {\n  const tE = fire !== 'mx' ? thrustOf(em, 0) : 0, tM = fire !== 'em' ? thrustOf(0, mx) : 0, t = tE + tM;\n  if (!(t > 0)) return null;\n  const mult = multiplierOf(mass, t), fare = Math.round(base * mult * 10000) / 10000;\n  return { fire, mult, fare, power: Math.round(fare * (tE / t) * EM_POWER_PER_FARE * 100) / 100,\n           ch4: r4(fare * (tM / t) * MX_CH4_PER_FARE), lox: r4(fare * (tM / t) * MX_LOX_PER_FARE) };\n}\nexport function planTrip(p) {\n  const base = Math.max(0, Number(p && p.base) || 0), mass = Math.max(0, Number(p && p.mass) || 0);\n  const em = Math.max(0, (p && p.em) | 0), mx = Math.max(0, (p && p.mx) | 0);\n  const power = Math.max(0, Number(p && p.power) || 0);\n  const fuel = { ch4: Math.max(0, Number(p && p.fuel && p.fuel.ch4) || 0), lox: Math.max(0, Number(p && p.fuel && p.fuel.lox) || 0) };\n  if (!(em + mx > 0)) return { ok: false, noDrives: true, text: 'Not a ship: it has no thrusters', missing: null, fare: 0, mult: null };\n  const tried = [];\n  for (const fire of FIRE_ORDER[p && p.move ? 'move' : 'trip']) {\n    if ((fire === 'em' && !em) || (fire === 'mx' && !mx) || (fire === 'all' && !(em && mx))) continue;\n    const c = costFor(base, mass, em, mx, fire); if (!c) continue;\n    tried.push(c);\n    if (c.power <= power + EPS && c.ch4 <= fuel.ch4 + EPS && c.lox <= fuel.lox + EPS) return { ok: true, ...c, missing: null, text: costText(c) };\n  }\n  const show = tried.find((c) => c.fire === 'all') || tried[0];\n  const missing = { power: Math.max(0, show.power - power), ch4: Math.max(0, show.ch4 - fuel.ch4), lox: Math.max(0, show.lox - fuel.lox) };\n  return { ok: false, ...show, missing, have: { power, ...fuel }, text: missingText(missing, { power, ...fuel }) };\n}\n// WORDS. What a trip costs: power to the nearest whole unit, fuel to a tenth rounded up. What is MISSING: power rounded up,\n// fuel to a tenth rounded up, so the button never asks for less than is short.\nconst up1 = (v) => Math.ceil(v * 10 - 1e-6) / 10;\nconst fmt1 = (v) => { const x = up1(v); return Number.isInteger(x) ? String(x) : x.toFixed(1); };\nexport function costText(c) {\n  if (!c) return '';\n  const parts = [];\n  if (c.power > 0) parts.push(`${Math.max(1, Math.round(c.power))} power`);\n  if (c.ch4 > 0) parts.push(`${fmt1(c.ch4)} ${LIQUID_METHANE}`);\n  if (c.lox > 0) parts.push(`${fmt1(c.lox)} ${LIQUID_OXYGEN}`);\n  return parts.join(' + ') || 'nothing';\n}\nexport function missingText(m, have) {\n  const parts = [];\n  if (m.power > EPS) parts.push(`${Math.ceil(m.power - 1e-6)} more power`);\n  if (m.ch4 > EPS) parts.push(`${fmt1(m.ch4)}${have && have.ch4 > EPS ? ' more' : ''} ${LIQUID_METHANE}`);\n  if (m.lox > EPS) parts.push(`${fmt1(m.lox)}${have && have.lox > EPS ? ' more' : ''} ${LIQUID_OXYGEN}`);\n  return parts.length ? 'Needs ' + parts.join(' and ') : '';\n}\n// …AND SHORT, for a card's button: \"28 power\", \"0.9 CH₄ + 1.8 LOX\", \"Needs 12 power\", \"Needs 2 LOX\" (the title and the toast\n// carry the long words).\nexport function costShort(c) {\n  if (!c) return '';\n  const parts = [];\n  if (c.power > 0) parts.push(`${Math.max(1, Math.round(c.power))} power`);\n  if (c.ch4 > 0) parts.push(`${fmt1(c.ch4)} CH₄`);\n  if (c.lox > 0) parts.push(`${fmt1(c.lox)} LOX`);\n  return parts.join(' + ') || 'nothing';\n}\nexport function missingShort(m) {\n  const parts = [];\n  if (m.power > EPS) parts.push(`${Math.ceil(m.power - 1e-6)} power`);\n  if (m.ch4 > EPS) parts.push(`${fmt1(m.ch4)} CH₄`);\n  if (m.lox > EPS) parts.push(`${fmt1(m.lox)} LOX`);\n  return parts.length ? 'Needs ' + parts.join(' + ') : '';\n}\n// THE BUTTON'S TAIL: \" · 2.8× · 28 power\", what the card's Travel here shows beside its label.\nexport function tripTail(plan) {\n  if (!plan || plan.noDrives) return '';\n  return ` · ${plan.mult}× · ${plan.ok ? costShort(plan) : missingShort(plan.missing)}`;\n}\nexport const FIRE_WORDS = Object.freeze({ all: 'every drive', em: 'the EM drives', mx: 'the Methalox thrusters' });\n\n// ---- THE BASE FARE OF A SHIP'S MOVE --------------------------------------------------------------------------------------\n// `fare` is the trip's route fare (routes.mjs: shipHopFare inside one system, the transfer between where the two bodies\n// stand; shipJumpFare between stars, the crossing and both climbs); `sameHost` a Move round the same body. Until the Travel\n// Routes picks (Oct 28) every hop inside one system was a flat SHIP_LOCAL_FARE.\nexport function baseFare(fare, sameSystem, sameHost) {\n  if (sameHost) return SHIP_MOVE_FARE;\n  if (fare == null) return null;\n  return Math.max(sameSystem ? SHIP_MOVE_FARE : SHIP_LOCAL_FARE, Number(fare) || 0);\n}\n\n// ---- WHO MAY PILOT, AND WHO MAY SEND IT AWAY --------------------------------------------------------------------------------\n// The owner and the builders (claims-style, as building): a ship goes where its builders take it. A public dock lets anyone\n// dock and refill, never fly.\nexport const canPilot = (v, uid) => canBuildVessel(v, uid);\nexport const PILOT_DENIED = 'Only its owner and builders can fly this ship.';\nexport const NOT_DOCKED = 'Dock at one of its Suit Docks to fly the ship.';\n// Send into interstellar space: the owner, with nobody docked at it. Nothing comes back.\nexport function scrapProblem(v, uid, aboard) {\n  if (!v) return 'That vessel isn’t there any more.';\n  if (!uid || v.owner !== uid) return 'Only its owner can send it into interstellar space.';\n  if (aboard > 0) return `Everyone has to undock first: ${aboard} ${aboard === 1 ? 'player is' : 'players are'} docked at it.`;\n  return null;\n}\n\n// ---- THE CREW -----------------------------------------------------------------------------------------------------------\n// What a move does to a player the server has aboard (whereabouts = the vessel): DOCKED rides with it ('docked'); anyone\n// else is 'left' in orbit at the old spot (there is no inside to ride in).\nexport const crewOutcome = (docked) => (docked ? 'docked' : 'left');\n// Is this player docked at this vessel? The server's record (users/{uid}.docked, written by suitDock) and the dock still\n// there.\nexport function dockedAt(user, addr, blocks) {\n  const d = user && user.docked;\n  if (!d || d.addr !== addr || !Array.isArray(d.cell) || d.cell.length !== 3) return false;\n  return !blocks || blocks[cellKeyOf(d.cell[0], d.cell[1], d.cell[2])] === 'Suit Dock';\n}\n\n// ---- ARRIVING ------------------------------------------------------------------------------------------------------------\n// The default spot round the destination: near the body but clear of its ceiling (as a launch's), at the angle the card\n// pointed from (the camera's bearing from the body, in its plane), the nearest free one to that. null: no room.\nexport function arrivalSpot(band, others, angle, nowMs) {\n  const spans = parkRanges(band);\n  if (!spans.length) return null;\n  const r0 = Math.min(spans[0][1], spans[0][0] + 2 * VESSEL_REACH);\n  return nearestFree(band, orbitAt(r0, Number.isFinite(angle) ? angle : 0, nowMs), others);\n}\nexport const canPickNow = (v, uid, nowMs) => !!(v && v.travel && v.travel.pilot === uid && Number(v.travel.pickUntil) > nowMs);\n\n// ---- THE END OF A PASS -------------------------------------------------------------------------------------------------------\n// The worlds of a system nearest a point (x, z in the star's plane, system units) at simulation time T, nearest first: where\n// a vessel round an asteroid or a comet goes when its pass ends. Planets of the star `tag` names (a far binary's 'b' is the\n// companion's), placed as the renderer places them (system-view.js updatePlanetSystem: startAngle − T·ω round their lane).\nexport function worldsNearest(sys, tag, x, z, T) {\n  const host = tag === 'b' && sys && sys.companion ? sys.companion : sys;\n  const out = [];\n  for (const p of (host && host.children) || []) {\n    if (!p || !p._addr) continue;\n    const d = (host.laneDistances && host.laneDistances[p.lane]) || p._orbitDist || 0, a = (p.startAngle || 0) - T * (p._orbitOmega || 0);\n    out.push({ addr: p._addr, dist: Math.hypot(Math.cos(a) * d - x, Math.sin(a) * d - z), angle: Math.atan2(z - Math.sin(a) * d, x - Math.cos(a) * d) });\n  }\n  return out.sort((a, b) => a.dist - b.dist);\n}\n\n// ---- THE PLUME ---------------------------------------------------------------------------------------------------------------\n// Which way a drive's exhaust leaves it: out of its first face onto open space, looking along the vessel's −Z, then +Z, −X, +X,\n// −Y, +Y (a thruster at the stern fires aft). `free(x, y, z)`: the cell is open. null when it is walled in on every side.\nexport function nozzleOf(x, y, z, free) {\n  for (const d of [[0, 0, -1], [0, 0, 1], [-1, 0, 0], [1, 0, 0], [0, -1, 0], [0, 1, 0]]) {\n    const nx = x + d[0], ny = y + d[1], nz = z + d[2];\n    if (!inGrid(nx, ny, nz) || free(nx, ny, nz)) return d;\n  }\n  return null;\n}\n// A ship's flight as everyone draws it: 0..1 of the way from its old spot to its new one, from its record's `travel`, null\n// when it is not flying now.\nexport function flightPhase(v, nowMs) {\n  const t = v && v.travel;\n  if (!t || !Number.isFinite(t.at)) return null;\n  const u = (nowMs - t.at) / TRAVEL_ANIM_MS;\n  return u >= 0 && u < 1 ? u : null;\n}\n"],"mappings":"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","names":[],"file":"ship.mjs"}