{"version":3,"sources":["vessels.mjs"],"sourcesContent":["// VESSELS — player-built stations, and in phase 3 ships (the owner's spec, confirmed Oct 12).\n//\n// Byte-identical in public/ and functions/lib/ (generate-version-hash.js holds them equal). The functions read it to\n// decide where a vessel may be launched, who may build on it and who may see it (functions/vessels.js, submitEdits);\n// the page reads it to draw every vessel at the same place at the same moment and to offer the same choices the server\n// will accept. Pure, so tests/vessels.test.mjs pins every rule without a server or a renderer.\n//\n// THE OWNER'S SPEC, which everything below follows:\n//   ONE CONCEPT. A vessel is a grid of kubes like a small world, but standing FACE UP: an axis-aligned grid with level\n//   decks, where a world stands corner-up. With thrusters it is a SHIP, without a STATION; otherwise they are the same.\n//   Phase 1 builds stations; `kind` is on the record from the start so a ship is the same record with one word changed.\n//   GETTING ONE. A crafted DOCKING PORT, launched from orbit over a body into a free spot of its orbit band, for the\n//   vessel token fee. The port becomes the vessel's first block, its CORE, the anchor of its grid. More than one each,\n//   each at the fee.\n//   SIZE. Up to 32×32×32: one storage chunk (VOX_CHUNK = 32), so a whole vessel is one chunk document.\n//   WHERE THEY PARK. Anywhere in a body's ORBIT BAND (bandOf, below), in the body's equatorial plane, at a chosen radius\n//   and angle, moving with the shared clock like a moon so every viewer sees it in the same place without simulating\n//   anything. No two closer than their reaches plus a margin.\n//   WHO PARKS. Over a claimed world its owner decides (by default the owner, their builders and their friends);\n//   anywhere else, anyone.\n//\n// THE RECORD (vessels/{id}, written by functions/vessels.js alone; the address is `galaxy.system.v<id>`,\n// whereabouts.mjs VESSEL_ADDR_RE):\n//   { v, id, addr, system, host, kind, name, owner, ownerIgn, orbit: { r, angle0, epoch, w }, size, builders,\n//     allowAllFriends, visibility, seers, fee, paid, createdAt }\n//   seers: who may see a vessel that is not public — its owner, its builders and, for 'friends', the owner's friends,\n//   resolved by the server as claims resolve their builders. firestore.rules reads it, because a rule cannot ask who\n//   is whose friend.\n// PHASE 2 added `life` (vessel-life.mjs: the power pool; its rooms and air went with the insides, Oct 13). PHASE 3 (ships, ship.mjs) adds what submitEdits\n// works out from the blocks — `kind` ('ship' once a thruster is aboard), `drives` { em, mx }, `mass` — and what a trip\n// writes: `fuelBy` / `fuelOpen` (the Methalox fuel aboard), `travel` (the last trip: where from, when, which drives, the\n// arrival pick's window) and, round an asteroid or a comet, `hostEnd` (when its pass ends). A trip to another system\n// moves `system`, `host` and `addr` together and the vessel's one chunk with them (functions/ships.js).\nimport { VESSEL_ADDR_RE, isVesselAddr, vesselIdOf, bodyOf, systemOf } from './whereabouts.mjs';\nexport { VESSEL_ADDR_RE, isVesselAddr, vesselIdOf };\n\n// ---- WHAT A VESSEL COSTS AND IS MADE FROM ----------------------------------------------------------------------------\n// 500 tokens, about $5: a vessel is a second home that follows its owner's choice of sky, and the owner priced a world\n// at 100 to 1,600 by its size. A vessel's grid holds 32,768 cells, which is a little more than the build room of the\n// smallest world's top face; between the two ends, at the price of a mid-sized world.\nexport const VESSEL_FEE = 500;\nexport const DOCKING_PORT = 'Docking Port';          // the crafted part a launch consumes; it becomes the core\nexport const VESSEL_MAX_PER_PLAYER = 10;             // more than one each (the owner), but not without end\nexport const VESSEL_KINDS = ['station', 'ship'];     // a ship is a vessel with a thruster aboard (ship.mjs, phase 3)\nexport const VESSEL_VISIBILITY = ['public', 'friends', 'private'];\nexport const VESSEL_VISIBILITY_DEFAULT = 'public';   // \"seen by all\" unless the owner says otherwise\n// RIDES (the owner's Travel Routes picks, Oct 28). A4 \"Ships say where they are going\": a pilot (its owner or a builder) may\n// set `ferry: { to, at }`, \"Taking passengers to …\", shown on the ship and in the rides a player is offered; it clears when\n// the ship arrives in that system. A5 \"The pilot sets a token price\": `dockFee`, tokens a rider pays its owner to dock,\n// free by default and never for its owner, its builders, or a rider docking again at the ship they are on.\nexport const DOCK_FEE_MAX = 500;\nexport function dockFeeFor(v, uid, at) {\n  const fee = v && Number.isInteger(v.dockFee) ? v.dockFee : 0;\n  if (!(fee > 0) || !uid || v.owner === uid || (Array.isArray(v.builders) && v.builders.includes(uid)) || (at && at === v.addr)) return 0;\n  return Math.min(DOCK_FEE_MAX, fee);\n}\nexport const FERRY_LABEL_MAX = 40;\nexport const VESSEL_NAME_MAX = 32;\nexport const VESSEL_SEERS_MAX = 500;                 // the ceiling a friends-shared bookmark group keeps too\n// Who may park round a claimed world, the claim's owner's choice (claims/{addr}.vesselParking). 'friends' is the default\n// the owner named: the owner, their builders and their friends.\nexport const VESSEL_PARKING = ['friends', 'builders', 'owner', 'anyone'];\nexport const VESSEL_PARKING_DEFAULT = 'friends';\n\n// ---- THE GRID -------------------------------------------------------------------------------------------------------\n// 32 cells on each axis, -16..15 as a world's grid runs -HALF..HALF-1, so every cell is in chunk 0_0_0 and the chunk ids\n// and local keys are worked out by the same formula a world's are. The core stands at the cell (0, 0, 0), just off the\n// grid's exact middle, which no axis can have in an even grid.\nexport const VESSEL_EDGE = 32;\nexport const VESSEL_HALF = VESSEL_EDGE / 2;\nexport const VESSEL_CORE = [0, 0, 0];\nexport const isVesselCore = (x, y, z) => x === VESSEL_CORE[0] && y === VESSEL_CORE[1] && z === VESSEL_CORE[2];\nexport const inVesselGrid = (x, y, z) => [x, y, z].every((c) => Number.isInteger(c) && c >= -VESSEL_HALF && c < VESSEL_HALF);\n// THE GENERATOR (the spec: \"a vessel has NO seed terrain\"). Empty space and the core: what submitEdits treats as ground\n// on a vessel, and what the page draws before any edit arrives. There is no regeneration from a claim, no sea, no life.\nexport const vesselSolidAt = (x, y, z) => isVesselCore(x, y, z);\nexport const vesselKubeAt = (x, y, z) => (isVesselCore(x, y, z) ? DOCKING_PORT : null);\n\n// ---- UNITS ----------------------------------------------------------------------------------------------------------\n// Orbits are kept in KUBES, so a record means the same thing whatever the renderer's scale. One kube is BASE_SCALE / 2\n// of the system's units (system-engine.js getRadius: a rocky world of Ø kubes has radius Ø·KW).\nexport const KUBE_UNITS = 0.02;\n// A vessel's REACH: how far its full 32×32 deck plan reaches across the plane it orbits in, 16·√2 kubes, rounded up.\n// Its grid stands level in that plane and never turns in it (it orbits without spinning, so \"down is down\" aboard), and\n// every body it could meet has its centre in the same plane, so nothing of it comes nearer a body or another vessel than\n// its centre less this. Spacing and the band hold a vessel to its reach, not to what has been built yet, because it can\n// grow to the edge of its grid at any moment.\nexport const VESSEL_REACH = Math.ceil(VESSEL_HALF * Math.SQRT2);   // 23 kubes\nexport const VESSEL_MARGIN = 4;                                   // kubes of clear space between two reaches\n\n// ---- THE ORBIT BAND ---------------------------------------------------------------------------------------------------\n// From JUST ABOVE THE BODY'S BUILD CEILING to HALFWAY TO ITS NEAREST NEIGHBOUR'S ORBIT (the owner's words), worked out\n// from the same layout the renderer draws (system-engine.js laneDistances, logicalDist, _hillR):\n//   inner  the body's clearance: system-engine.js's Roche limit, max(2.44·r/2, r·√3 + 5 kubes). The build ceiling is\n//          the cube of half-edge getRadius, and a corner-up cube's corner reaches √3 of that; the Roche ring the\n//          \"View object limits\" overlay draws is this line.\n//   outer  HALFWAY ACROSS THE CLEAR GAP to each neighbour, measured from one body's clearance to the other's rather\n//          than half the distance between centres: two neighbours' bands then meet exactly at the middle of the space\n//          between them and never overlap, and a band is never emptied by a big neighbour's own size. For bodies that\n//          go round the same parent the gap is the difference of their orbits, their closest approach, since they go\n//          round at different rates.\n//            a MOON:   its planet, and its sibling moons.\n//            a PLANET: the planets either side and a pair's companion, and its Hill sphere (16 build limits out, the\n//                      sphere the generator nests its moons in), past which a vessel would really be orbiting the star.\n//   A PLANET'S MOONS GO THROUGH ITS BAND rather than ending it. Halfway to the innermost moon is the planet's inner band,\n//   as the owner put it; but a moon generated close in (most are: the generator packs the first one just past the\n//   planet's Roche limit) left a planet whose inner band no vessel fits in, and measured over the home systems that was\n//   four planets in ten, the owner's home among them. So the band runs on past each moon to the Hill sphere, with a\n//   ring kept clear round each moon's orbit (`clear`): the moon's own band swept round with it, the moon's clearance\n//   at least. A vessel round the planet at a radius outside every ring never comes within the moon's band, whatever the\n//   two rates, because its closest approach is the difference of the radii.\n// Returns { inner, outer, clear: [[lo, hi], …] } in kubes from the body's centre, or null for something that cannot\n// host one (a star, a transient, an address the system does not hold).\nexport function rocheOf(b, eng) {\n  const r = eng.getRadius(b) || 0;\n  return Math.max(2.44 * (r / 2), r * Math.sqrt(3) + 5 * KUBE_UNITS);\n}\nfunction hillOf(b, eng) { return b._hillR || 16 * (eng.getRadius(b) || 0); }\n\n// Where a body sits in its system: its parent, its distance from it, its siblings and its moons, each with a distance.\nfunction contextOf(sys, addr, eng) {\n  const hosts = sys.isRogue ? [] : (sys.binaryType === 'far' ? [sys, sys.companion] : [sys]);\n  const moonsOf = (p) => (p.children || []).map((m) => ({ b: m, d: m.logicalDist || (p.laneDistances && p.laneDistances[m.lane]) || 0 }));\n  // A ROGUE's root is a planet with no star, whose moons are filed in its laneDistances (system-engine.js).\n  if (sys.isRogue) {\n    if (sys._addr === addr) return { b: sys, parent: null, d: 0, siblings: [], moons: moonsOf(sys), companion: [] };\n    const moons = moonsOf(sys);\n    for (const M of moons) if (M.b._addr === addr) return { b: M.b, parent: sys, d: M.d, siblings: moons.filter((o) => o !== M), moons: [], companion: [] };\n    return null;\n  }\n  for (const host of hosts) {\n    const planets = (host.children || []).map((c) => ({ b: c, d: (host.laneDistances && host.laneDistances[c.lane]) || 0 }));\n    for (const P of planets) {\n      const p = P.b;\n      const sibs = planets.filter((o) => o !== P);\n      const sep = (p.layout && p.layout.sep) || 0;\n      if (p._addr === addr) return { b: p, parent: null, d: P.d, siblings: sibs, moons: moonsOf(p), companion: p.companion ? [{ b: p.companion, sep }] : [] };\n      if (p.companion && p.companion._addr === addr) return { b: p.companion, parent: null, d: P.d, siblings: sibs, moons: [], companion: [{ b: p, sep }] };\n      const moons = moonsOf(p);\n      for (const M of moons) if (M.b._addr === addr) return { b: M.b, parent: p, d: M.d, siblings: moons.filter((o) => o !== M), moons: [], companion: [] };\n    }\n  }\n  return null;\n}\n// Halfway across the clear gap between two bodies whose centres are `gap` apart: the room the first body's band gets.\nconst halfGap = (ownClear, gap, otherClear) => (ownClear + (gap - otherClear)) / 2;\n// How far a MOON's band reaches from its centre: halfway to its planet and to each sibling, never inside its clearance.\nfunction moonOuter(m, parent, d, siblings, eng) {\n  const own = rocheOf(m, eng);\n  let outer = halfGap(own, d, rocheOf(parent, eng));\n  for (const s of siblings) outer = Math.min(outer, halfGap(own, Math.abs(d - s.d), rocheOf(s.b, eng)));\n  return Math.max(own, outer);\n}\nconst K = (u) => Math.round(u / KUBE_UNITS * 100) / 100;   // units → kubes, to the hundredth (both sides round alike)\n\nexport function bandOf(sys, addr, eng) {\n  if (!sys || typeof addr !== 'string') return null;\n  const ctx = contextOf(sys, addr, eng);\n  if (!ctx) return null;\n  const b = ctx.b, cat = eng.getCategory(b);\n  if (cat === 'Star' || b.isTransient || cat === 'Asteroid' || cat === 'Comet') return null;   // transients: transientBandOf\n  const own = rocheOf(b, eng);\n  if (ctx.parent) return { inner: K(own), outer: K(moonOuter(b, ctx.parent, ctx.d, ctx.siblings, eng)), clear: [] };\n  let outer = hillOf(b, eng);\n  for (const s of ctx.siblings) outer = Math.min(outer, halfGap(own, Math.abs(ctx.d - s.d), rocheOf(s.b, eng)));\n  for (const c of ctx.companion) if (c.sep > 0) outer = Math.min(outer, halfGap(own, c.sep, rocheOf(c.b, eng)));\n  const clear = [];\n  for (const M of ctx.moons) {\n    if (!(M.d > 0)) continue;\n    const reach = moonOuter(M.b, b, M.d, ctx.moons.filter((o) => o !== M), eng);\n    clear.push([K(M.d - reach), K(M.d + reach)]);\n  }\n  clear.sort((x, y) => x[0] - y[0]);\n  return { inner: K(own), outer: K(Math.max(own, outer)), clear };\n}\n// The radii a vessel's CENTRE may take round a body: its reach kept inside the band's edges and out of every ring kept\n// clear for a moon, as a list of [lo, hi] intervals, nearest first. Empty when there is no room at all.\nexport function parkRanges(band) {\n  if (!band) return [];\n  let spans = [[band.inner + VESSEL_REACH, band.outer - VESSEL_REACH]];\n  for (const [a, z] of band.clear || []) {\n    const cut = [a - VESSEL_REACH, z + VESSEL_REACH], next = [];\n    for (const [lo, hi] of spans) {\n      if (cut[1] <= lo || cut[0] >= hi) { next.push([lo, hi]); continue; }\n      if (cut[0] > lo) next.push([lo, cut[0]]);\n      if (cut[1] < hi) next.push([cut[1], hi]);\n    }\n    spans = next;\n  }\n  return spans.filter(([lo, hi]) => hi >= lo).map(([lo, hi]) => [Math.round(lo * 100) / 100, Math.round(hi * 100) / 100]);\n}\nexport const hasRoom = (band) => parkRanges(band).length > 0;\n\n// ---- ROUND AN ASTEROID OR A COMET (phase 3; the owner's round-1 pick: \"2 slots round a transient\") -------------------------\n// A transient (`galaxy.system.c<K>[ab]`, whereabouts.mjs) is a lump 4 to 16 kubes across on one pass through its system. It\n// has no neighbours a vessel could stray into on the scale of a vessel, so its band is a ring just outside its clearance\n// (rocheOf, as a world's), TRANSIENT_RING kubes wider than two reaches: wide enough for a vessel's centre to take a few radii,\n// and round enough at that radius for TRANSIENT_MAX_VESSELS to sit apart. The vessel goes round the moving rock at the one\n// vessel rate, and when the rock's pass ends it is moved to the nearest world's free spot (functions/ships.js).\nexport const TRANSIENT_ADDR_RE = /^[0-9]+\\.[0-9]+\\.c[0-9]+[ab]?$/;\nexport const isTransientAddr = (a) => typeof a === 'string' && TRANSIENT_ADDR_RE.test(a);\nexport const TRANSIENT_RING = 12;\nexport const TRANSIENT_MAX_VESSELS = 2;\nexport const TRANSIENT_FULL = 'This asteroid has room for two vessels, and both are taken.';\n// `t`: the transient as the engine has it ({ diameter, isComet }, system-engine.js transientsAt), `eng` its getRadius.\nexport function transientBandOf(t, eng) {\n  if (!t || !(t.diameter > 0)) return null;\n  const own = rocheOf({ class: t.isComet ? 'Comet' : 'Asteroid', diameter: t.diameter }, eng);\n  const inner = K(own);\n  return { inner, outer: Math.round((inner + 2 * VESSEL_REACH + TRANSIENT_RING) * 100) / 100, clear: [], transient: true };\n}\n// The nearest radius a vessel's centre may take to `r`, or null when there is none.\nexport function nearestRadius(band, r) {\n  let best = null;\n  for (const [lo, hi] of parkRanges(band)) {\n    const c = Math.max(lo, Math.min(hi, r));\n    if (best == null || Math.abs(c - r) < Math.abs(best - r)) best = c;\n  }\n  return best;\n}\n\n// ---- THE SHARED CLOCK -------------------------------------------------------------------------------------------------\n// The renderer's: simulation time = (ms − SIM_EPOCH) · SIM_RATE (system-view.js; energy.mjs restates both), and a body\n// at angular rate w is at startAngle − simTime·w, so its period at 1× is PERIOD_K / w seconds.\n// EVERY VESSEL ROUND A BODY GOES ROUND AT THE SAME RATE, one turn in VESSEL_PERIOD_S. A moon's rate falls with its\n// distance, and vessels at different radii doing the same would lap each other, so two that were apart at their\n// launches would meet later: spacing could only ever be checked for a moment. At one rate the band turns as one piece,\n// distances between vessels never change, and a free spot found now stays free for ever. 45 minutes, inside the\n// 20-60 minute months the generator gives moons.\nexport const SIM_EPOCH_MS = Date.UTC(2025, 0, 1, 0, 0, 0);          // system-view.js SIM_EPOCH\nexport const PERIOD_K = 1800 * 1500;                                 // system-view.js PLANET_DAY · ROT_VIS.Rocky\nexport const SIM_RATE = (2 * Math.PI) / (1000 * PERIOD_K);           // system-view.js SIM_RATE\nexport const VESSEL_PERIOD_S = 45 * 60;\nexport const VESSEL_OMEGA = PERIOD_K / VESSEL_PERIOD_S;\nexport const simTimeAt = (ms) => (ms - SIM_EPOCH_MS) * SIM_RATE;\nconst TAU = 2 * Math.PI;\nconst wrap = (a) => ((a % TAU) + TAU) % TAU;\n// The orbit a launch at `nowMs` writes for a vessel seen at angle `angle` and radius `r` kubes: angle0 is where it was\n// at the epoch, so any later moment is one subtraction away.\nexport function orbitAt(r, angle, nowMs) {\n  return { r: Math.round(r * 100) / 100, angle0: wrap(angle + simTimeAt(nowMs) * VESSEL_OMEGA), epoch: SIM_EPOCH_MS, w: VESSEL_OMEGA };\n}\n// Where a vessel is round its host at simulation time T (what the renderer's simulationTime holds), as an angle.\nexport function vesselAngleAtSim(orbit, T) {\n  if (!orbit) return 0;\n  const w = Number.isFinite(orbit.w) ? orbit.w : VESSEL_OMEGA;\n  return wrap((orbit.angle0 || 0) - T * w);\n}\nexport const vesselAngleAt = (orbit, ms) => vesselAngleAtSim(orbit, simTimeAt(ms));\n// …and as a point in the host's equatorial plane, in kubes: (x, z) of the plane before it is tipped to the equator.\nexport function vesselOffsetAt(orbit, ms) {\n  const a = vesselAngleAt(orbit, ms), r = (orbit && orbit.r) || 0;\n  return [Math.cos(a) * r, Math.sin(a) * r];\n}\n\n// ---- SPACING AND PLACEMENT --------------------------------------------------------------------------------------------\n// Two vessels' centres, in kubes, at the same moment. Since every vessel round a body turns at one rate (above), this is\n// the same at every moment, so the epoch angles compare directly.\nexport function vesselGap(a, b) {\n  const ta = a.angle0 || 0, tb = b.angle0 || 0;\n  return Math.sqrt(a.r * a.r + b.r * b.r - 2 * a.r * b.r * Math.cos(ta - tb));\n}\nexport const VESSEL_SPACING = 2 * VESSEL_REACH + VESSEL_MARGIN;\n// Why a vessel may not go at `orbit` round a body with this band and these other vessels: null when it may.\nexport function placementProblem(band, orbit, others) {\n  const spans = parkRanges(band);\n  if (!spans.length) return 'There is no room to park round this body: its neighbours are too close.';\n  if (!orbit || !Number.isFinite(orbit.r) || !Number.isFinite(orbit.angle0)) return 'Pick a spot in orbit.';\n  if (orbit.r < spans[0][0] - 1e-6) return 'That spot is too close to the body. Pick one farther out.';\n  if (orbit.r > spans[spans.length - 1][1] + 1e-6) return 'That spot is too far out: it is past halfway to the next body.';\n  if (!spans.some(([lo, hi]) => orbit.r >= lo - 1e-6 && orbit.r <= hi + 1e-6)) return 'That spot is in a moon\\u2019s path. Pick one nearer in or farther out.';\n  for (const o of others || []) if (o && o.orbit && vesselGap(orbit, o.orbit) < VESSEL_SPACING - 1e-6)\n    return 'Another vessel is parked too close to that spot.';\n  if (band.transient && (others || []).filter((o) => o && o.orbit).length >= TRANSIENT_MAX_VESSELS) return TRANSIENT_FULL;\n  return null;\n}\n// THE NEAREST FREE SPOT to the one asked for, round the same circle first, then stepping out and in: what the page offers\n// when the player's pick is taken, and what phase 3's end-of-pass move will use. null when the band is full.\nexport function nearestFree(band, wanted, others) {\n  if (!wanted) return null;\n  const r0 = nearestRadius(band, wanted.r);\n  if (r0 == null) return null;\n  const step = VESSEL_SPACING / 4;\n  for (let k = 0; k <= 200; k++) {\n    for (const dr of k ? [k * step, -k * step] : [0]) {\n      const r = nearestRadius(band, r0 + dr);\n      if (r == null || Math.abs(r - (r0 + dr)) > 1e-6) continue;\n      const steps = Math.max(8, Math.ceil(TAU * r / (VESSEL_SPACING / 2)));\n      for (let i = 0; i <= steps; i++) {\n        for (const s of i ? [1, -1] : [1]) {\n          const o = { r: Math.round(r * 100) / 100, angle0: wrap((wanted.angle0 || 0) + s * i * TAU / steps), w: VESSEL_OMEGA };\n          if (!placementProblem(band, o, others)) return o;\n        }\n      }\n    }\n  }\n  return null;\n}\n\n// ---- WHO MAY DO WHAT --------------------------------------------------------------------------------------------------\n// May this player park round this body? `claim` is the body's claim (or null), `isFriend` whether they are the claim\n// owner's accepted friend.\nexport function canParkOver(claim, uid, isFriend) {\n  if (!claim) return true;                                         // unclaimed worlds, moons, giants: anyone\n  if (!uid) return false;\n  if (claim.owner === uid) return true;\n  const mode = VESSEL_PARKING.includes(claim.vesselParking) ? claim.vesselParking : VESSEL_PARKING_DEFAULT;\n  const builder = Array.isArray(claim.builders) && claim.builders.includes(uid);\n  if (mode === 'anyone') return true;\n  if (mode === 'owner') return false;\n  if (mode === 'builders') return builder;\n  return builder || !!isFriend;                                    // 'friends'\n}\nexport function parkingRefusal(claim) {\n  const mode = VESSEL_PARKING.includes(claim && claim.vesselParking) ? claim.vesselParking : VESSEL_PARKING_DEFAULT;\n  return mode === 'owner' ? 'The owner of this world keeps its orbit to themselves.'\n       : mode === 'builders' ? 'Only this world’s builders may park in its orbit.'\n       : 'Only this world’s owner, their builders and their friends may park in its orbit.';\n}\n// Claims-style building (the spec): the owner and the builders build.\nexport function canBuildVessel(v, uid) {\n  return !!(v && uid && (v.owner === uid || (Array.isArray(v.builders) && v.builders.includes(uid))));\n}\nexport const visibilityOf = (v) => (v && VESSEL_VISIBILITY.includes(v.visibility) ? v.visibility : VESSEL_VISIBILITY_DEFAULT);\n// May this player see the vessel (and dock at its public docks)? Public to all; otherwise whoever the server listed in its\n// seers. (Nobody boards one since Oct 13: a vessel has no insides, and is ridden docked at a Suit Dock on its outside.)\nexport function canSeeVessel(v, uid) {\n  if (!v) return false;\n  if (visibilityOf(v) === 'public') return true;\n  return !!(uid && (v.owner === uid || (Array.isArray(v.seers) && v.seers.includes(uid))));\n}\n// Who the record lists as seers: the owner, the builders and, for a friends vessel, the owner's friends.\nexport function seersOf(owner, builders, visibility, friendUids) {\n  const out = new Set([owner]);\n  for (const u of builders || []) out.add(u);\n  if (visibility === 'friends') for (const u of friendUids || []) out.add(u);\n  return [...out].filter((u) => typeof u === 'string' && u).slice(0, VESSEL_SEERS_MAX);\n}\n// A builders list as the record keeps it: every friend for an all-friends vessel, else the owner's chosen few.\nexport const buildersOf = (allowAllFriends, chosen, friendUids) => (allowAllFriends ? (friendUids || []) : (chosen || [])).slice(0, VESSEL_SEERS_MAX);\n\n// ---- NAMES AND WORDS ----------------------------------------------------------------------------------------------------\n// The name a new vessel is given until its owner renames it.\nexport const defaultVesselName = (ign, n) => `${ign ? ign + '’s ' : ''}Station${n > 1 ? ' ' + n : ''}`.slice(0, VESSEL_NAME_MAX);\nexport const kindWord = (v) => ((v && v.kind) === 'ship' ? 'Ship' : 'Station');\n// \"2 stations\", \"1 station and 1 ship\": what a world's card and Nearby say about the vessels round it.\nexport function vesselCountText(list) {\n  const n = { station: 0, ship: 0 };\n  for (const v of list || []) n[(v && v.kind) === 'ship' ? 'ship' : 'station']++;\n  const part = (k) => (n[k] ? `${n[k]} ${k}${n[k] === 1 ? '' : 's'}` : null);\n  return [part('station'), part('ship')].filter(Boolean).join(' and ');\n}\nexport const visibilityWord = (vis) => (vis === 'private' ? 'Private' : vis === 'friends' ? 'Friends' : 'Public');\n\n// ---- RECORDS ----------------------------------------------------------------------------------------------------------\n// The vessels a page may draw, from a snapshot of records: well-formed, in this system, and visible to this player.\nexport function vesselsVisible(records, uid, system) {\n  const out = [];\n  for (const v of records || []) {\n    if (!v || !isVesselAddr(v.addr) || systemOf(v.addr) !== system || !(bodyOf(v.host) || isTransientAddr(v.host))) continue;\n    if (!v.orbit || !Number.isFinite(v.orbit.r) || !Number.isFinite(v.orbit.angle0)) continue;\n    if (!canSeeVessel(v, uid)) continue;\n    out.push(v);\n  }\n  return out;\n}\n// The vessels round one body, of those.\nexport const vesselsRound = (list, hostAddr) => (list || []).filter((v) => v && v.host === hostAddr);\n"],"mappings":"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","names":[],"file":"vessels.mjs"}