{"version":3,"sources":["energy.mjs"],"sourcesContent":["// Kubicor — the energy model. SHARED byte-for-byte between the Cloud Functions (the authority) and the client\n// (the HUD and the can-I-afford-this preview). generate-version-hash.js fails the build on drift, like synthesis.mjs.\n//\n// WHAT THIS REPLACES, AND WHY IT IS NOT THE SAME THING. `mineEnergy` was a RATE LIMITER dressed as a resource: a\n// 15-second budget refilling one per second, sized so that \"an honest player can never outpace it — a 10s block\n// costs 10 and takes 10 to mine\". It only ever bit a client submitting faster than wall-clock. Nothing about it\n// was a decision the player made.\n//\n// This is a resource. It is generated (the suit, and panels you place), stored (the batteries you carry), and\n// spent (mining, and travel). That difference has one consequence worth stating up front: THE ANTI-CHEAT\n// PROPERTY IS GONE. A player holding four charged batteries can legitimately spend 215 units in a second, which\n// is exactly what banking is for — so the submission rate limit has to live separately, as its own hidden check,\n// rather than being a side effect of the budget being small. See MINE_BURST_WINDOW below.\nimport { CORNER_UP_AXES, eclipseSetup, coveredAt, ECL_DESC_MAX } from './eclipse.mjs';   // what covers a sun, on every end (Sky and Eclipses, Oct 24, e4)\n\n// ---- STORAGE: what you carry -----------------------------------------------------------------------------\n// The suit cell is the floor and it is deliberately the OLD cap. A player who has never crafted a battery has\n// exactly the budget they had before this existed, so nothing about the early game regresses.\nexport const SUIT_CAPACITY = 15;\nexport const BATTERY_CAPACITY = 50;\nexport const BATTERY_NAME = 'Battery';\nexport const PANEL_NAME = 'Solar Panel';\n// CARRIED PANELS GENERATE NOTHING (the owner, Oct 3: \"carried panels shouldn't provide any additional power\"). What\n// charges you is the suit, in sunlight. Panels that generate where they STAND are built now (the owner's play review,\n// Oct 23, f7): a placed panel on your own claim charges your worn batteries by the sunlight on its face, through the\n// stance (panelSunAt and stanceRate, below), never through chargeRate's panel term, which every caller still passes none.\n// Capacity is a function of what is in the backpack, so it changes the moment you craft, drop or spend one. It\n// is NOT stored: a stored capacity is a second copy of the inventory that can disagree with it.\nexport function capacityFor(batteries) {\n  const n = Math.max(0, Math.floor(batteries || 0));\n  return SUIT_CAPACITY + BATTERY_CAPACITY * n;\n}\n\n// HOW FULL THE k-TH BATTERY IS, from the pooled bank alone.\n//\n// There is no per-cell number stored anywhere and there must not be: a second accounting of the same energy is a\n// second thing that can disagree with the server. But a battery now takes a slot to itself (products.json\n// `stackLimit`), and its FACE is a charge gauge — so each one has to read as its own cell, not as an average.\n//\n// The ordering does that for free. The bank fills batteries IN ORDER and therefore drains them in reverse: battery\n// k holds whatever is left once the k before it are full. One pooled number, N legible gauges, nothing stored.\n// `bank` is what the balance holds ABOVE the suit — everything below that is the suit's own reserve, which is what\n// makes \"every cell reads empty and you can still get home\" true rather than a promise.\nexport function batteryFill(bank, index) {\n  const b = Math.max(0, Number(bank) || 0);\n  const k = Math.max(0, Math.floor(Number(index) || 0));\n  return Math.max(0, Math.min(1, (b - k * BATTERY_CAPACITY) / BATTERY_CAPACITY));\n}\n\n// ---- GENERATION: the star, not a constant ----------------------------------------------------------------\n// Rates are quoted at REFERENCE INSOLATION — a one-solar-luminosity star at the centre of its habitable zone,\n// which is the one distance in this game where \"Earth-like\" has a definition. SUIT_RATE is the old 1/second, so\n// a player standing on a temperate world with no panels recharges at exactly the rate they always did.\nexport const SUIT_RATE = 1.0;\n// PANEL_RATE IS SET FROM WHAT THE WAIT SHOULD FEEL LIKE, not picked. A galaxy crossing costs 45, so on the suit\n// alone it is a 45-second wait — deliberately heavy, because that wait is the whole reason to build anything.\n// One panel makes it 15s, four makes it 5s, eight makes it under 3. That curve is the progression: the tech does\n// not raise a ceiling, it removes a friction. At 4.0 the curve collapsed — two panels filled a whole battery in\n// seven seconds and there was never a moment where storage mattered.\nexport const PANEL_RATE = 2.0;\n// Panels stop stacking after this. Not for balance — for honesty: past a point the limit is the battery you are\n// filling, and a hundred panels implying a hundredfold rate would be a number with nothing behind it.\nexport const PANEL_MAX = 8;\n\n// INSOLATION, from the same luminosity the system engine already computes (L proportional to R^2 T^4) and the\n// same habitable-zone scale it already places planets against. At the HZ centre this is exactly 1, which is\n// what makes the rates above mean anything: hzCenter is defined as the distance where a world gets Earth's\n// share, so (hzCenter / r)^2 IS the flux ratio. A world at half that distance gets four times the sun.\n//\n// Capped, because the inner planets of a blue giant would otherwise charge a battery in a heartbeat and the\n// number would stop reading as \"a lot of sun\" and start reading as broken.\nexport const INSOLATION_MAX = 6;\nexport function insolationAt(hzCenter, distance, daylight) {\n  if (!(hzCenter > 0) || !(distance > 0)) return 0;                 // a rogue with no star: no sun, no charge\n  const flux = (hzCenter / distance) * (hzCenter / distance);\n  const day = daylight == null ? 1 : Math.max(0, Math.min(1, daylight));\n  return Math.max(0, Math.min(INSOLATION_MAX, flux)) * day;\n}\n// WHAT FRACTION OF FULL SUN A SURFACE POINT IS SEEING, from the angle between its own up and the direction of\n// the star. Lambert's cosine law, with a soft terminator rather than a step: a hard cut would flick the charge\n// rate on and off as you walked along the dawn line, and the sky itself already fades across the same band.\nexport const TWILIGHT = 0.10;            // half-width of the terminator band, in cosine\nexport function daylightFrom(cosIncidence) {\n  const c = cosIncidence == null ? 1 : cosIncidence;\n  if (c <= -TWILIGHT) return 0;                                      // night, and past the terminator\n  if (c >= TWILIGHT) return Math.min(1, c);                          // day: Lambert's cosine law\n  const t = (c + TWILIGHT) / (2 * TWILIGHT);                         // 0 at the night edge, 1 at the day edge\n  return TWILIGHT * t * t;                                           // eased, and continuous with the branch above\n}\n// What the player is generating right now, in units per second.\n//\n// CAPPED AT INSOLATION_MAX. Near a hot star the summed flux runs well past 6, and a number that large stops reading as\n// \"a lot of sun\" and starts reading as broken. Capped here, in the one function every side calls, no end can price a\n// second of sunlight differently from another. (Until Oct 7 the servers charged at this cap everywhere, day or night;\n// they now charge by the stance's sunlight, energyAfterSun below.)\nexport function chargeRate(insolation, panels) {\n  const p = Math.max(0, Math.min(PANEL_MAX, Number(panels) || 0));   // not floored: three carried bits are three quarters of a panel\n  return (SUIT_RATE + PANEL_RATE * p) * Math.max(0, Math.min(INSOLATION_MAX, insolation || 0));\n}\n\n// ---- THE BALANCE, recomputed from a timestamp rather than ticked -------------------------------------------\n// The server stores (value, at) and both ends recompute from elapsed time, so a client that was closed for an\n// hour comes back correctly full instead of having missed 3,600 ticks. This is the same shape the old mining\n// budget used; what is new is that the RATE is an argument, because it now depends on where you are standing.\n//\n// `value == null` means an identity that has never spent anything, which is full by definition.\nexport function energyAfter(value, atMs, nowMs, cap, rate) {\n  if (value == null) return cap;\n  const dt = Math.max(0, (nowMs - atMs) / 1000);\n  return Math.max(0, Math.min(cap, value + dt * Math.max(0, rate || 0)));\n}\n\n// ---- SUNLIGHT: ONE RULE, EVALUATED THE SAME WAY BY THE CLIENT AND BOTH SERVERS (the owner, Oct 7) -----------------\n// \"Energy charges ONLY IN SUNLIGHT — on both client and server.\" Until Oct 7 the two ends answered different\n// questions: the gauge read the live sky (zero on the night side), while submitEdits and spendEnergy refilled at the\n// best case, INSOLATION_MAX, everywhere, because they could not see a sky. In shade the client then held breaks it\n// thought it could not pay for while the server sat on 11.8 of 15 — minutes of mining waiting on a number only one side\n// believed. Now neither side looks at a sky. Both read the same stored SUN DESCRIPTOR and integrate it with the same\n// function below, so for the same inputs they arrive at the same balance, to the bit.\n//\n// WHAT \"SUNLIGHT\" MEANS, exactly (World Seasons phase 2, the owner's \"energy = direct\", Oct 11). The suit charges by the\n// starlight that actually reaches it: for each of the system's lighting stars (at most two, as the sky draws them),\n//   that star's flux at the world (temperature.mjs: the distance and luminosity the W/m² readout uses, 1 at the\n//   habitable-zone centre)  ×  max(0, sin of its elevation above the FACE you stand on)  ×  the air's transmission\n// summed, times SUN_GAIN. Twice the light is twice the rate; a sun 30° up gives half what one overhead gives; under the\n// horizon, nothing, so polar night charges nothing at all. The world's position, its spin, its tidal lock and now the\n// lean of its axis (seasons.mjs axisLean: the sun climbs higher in summer, and round the solstices a high-tilt world's\n// faces near its poles see it all day or not at all) are the renderer's own, read off the wall clock (system-view.js\n// SIM_EPOCH and SIM_RATE), so the day you see is the day you charge in.\n// UNTIL OCT 11 a face charged at the star's full flux whenever its sun stood a few degrees up (a smoothstep of the\n// sun's height over the sky's horizon band, -0.18 to 0.10 in cosine), so the morning sun charged as fast as noon's.\n// The ramp was there so walking along the dawn line did not flick the rate on and off; max(0, sin) is continuous\n// through the horizon (0 there, rising from it), so it needs none.\n// What it leaves out, on purpose: shade from terrain, overhangs and caves (a server would need every chunk to know),\n// and the rounded up near a cube's edge (the sky's up rolls over an edge; this one is the face's, constant across it).\n// ECLIPSES ARE IN since sky 5 (the owner's Sky and Eclipses picks, Oct 24, e4): the descriptor carries the moons and planets\n// round the world (`ec`, sunlight.mjs eclipseTermsFor), and the share of each sun their cubes hide from the face's centre\n// dims it (eclipseCoverAt, below), so a panel in the umbra stops charging on every end at once. OFF A SURFACE — orbit, flight, between stars — a\n// suit is in unobstructed sunlight and charges at reference insolation, exactly what the gauge always used there.\n//\n// THE CALIBRATION: SUN_GAIN = π / (2·√(2/3)) ≈ 1.92, which keeps A WHOLE DAY'S CHARGE what it was (Oct 11: the game\n// should not get much harder or easier on average). A world stands corner-up (its spin axis through two opposite\n// corners), so the face you land on is tilted 35.26° off the equator: at noon on an equinox its sun stands 54.74° up\n// (sin = √(2/3)). The old rule charged the full 1/s for every lit second, a little under half the day, about 0.49 per\n// second of the day at the habitable-zone centre; proportional light averages (1/π)·√(2/3)·gain over that equinox day,\n// so this gain gives the same 0.49. Noon then charges 1.57/s, an overhead sun 1.92/s, one 30° up 0.96/s (half of\n// overhead), dawn and dusk little, night nothing; orbit is 1/s as ever. Summer days on the sunward hemisphere give\n// more, winter days less. Calibrating to noon instead (√(3/2), noon 1/s) would cut a day's charge by about a third.\n// THE AIR'S TRANSMISSION is AIR_TRANSMISSION, 1. The sunlight model this rule is built from has no air in it (the\n// flux is the star's at the world, the figure the starlight readout quotes), and the servers that price a stance\n// cannot work out a world's air (the client builds it from the generated crust), so there is nothing yet to dim by. It\n// is one named factor, in the one function every end prices a sun with (starlightOn), so the day the stance can carry\n// its air, a thick one dims the charge on every end at once.\n//\n// THE DESCRIPTOR is everything needed to answer that for one stance, flattened to numbers so it can be stored on the\n// user doc (server-written only; firestore.rules keeps it out of the client's keys) and evaluated with no system\n// engine at all — the zone server has none. sunlight.mjs builds it from the generated system. Its shape:\n//   { v: 1, o, key: '<world addr>|<face 0-5>', nx, ny, nz, c0, c1, b: [r, a, w, ...], s: [{ f, p: [r, a, w, ...] }, ...], t?, bi? }\n//   b / p   phasor sums for the world's and each star's position in the orbital plane: Σ r·(cos(a − w·T), sin(a − w·T))\n//   bi      the part of the world's position that is OUT of that plane (the owner's Worlds and Axes decisions, Oct 11: a\n//           moon circles in its planet's equator, and each of a pair in theirs, which a leaning planet tips): terms of\n//           nine, [r, a, w, e1x, e1y, e1z, e2x, e2y, e2z], each adding r·(cos(a − w·T)·e1 + sin(a − w·T)·e2), where e1\n//           and e2 are the tipped circle's two axes (sunlight.mjs equatorOf). Absent: the world is in the plane, as every\n//           world was before sky 4 and a moon of an upright planet, or of one locked to its star, still is.\n//   c0, c1  the world's spin: rotation.y = c0 + c1·T (a tidal lock is a spin too, locked to the orbit's own rate)\n//   nx, ny, nz  the face's outward normal in the world's corner-up frame (ny met no sun until the axis could lean)\n//   f       that star's flux at the world, 1 at the habitable-zone centre\n//   t       the axis's lean (seasons.mjs axisLean), on a world with seasons: [g0, g1, g2, d0, d1], leaning by\n//           g0·sin(g1 + g2·T) radians toward the in-plane bearing d0 + d1·T. A turning world's lean is fixed\n//           ([tilt, π/2, 0, bearing, 0]); a locked world's nods with its rock toward its star. Absent: no lean, the\n//           sky of a world without seasons, and of every descriptor stored before phase 2.\n//   ec      what can cover its suns (sky 5, the Sky and Eclipses picks, Oct 24): { h: the world's half-edge (the face's\n//           centre is h out along its normal), s: [{ e: the star's cube edge, c0, c1: its spin }, …] beside `s`, o: [{ p,\n//           bi?, e, c0, c1, t? }, …] the moons and planets round the world, each placed, spun and leant as the world is }.\n//           Absent: no eclipses, every descriptor stored before sky 5.\n// The evaluated shape (v: 1) only gained fields an older descriptor reads as absent, so every reader's v === 1 check\n// stands; which sky a descriptor prices is its `o` (sunlight.mjs SKY_MODEL), and an older one is re-anchored.\n// null is OFF A SURFACE.\nexport const OFF_SURFACE_INSOLATION = 1;\nexport const SIM_EPOCH_MS = Date.UTC(2025, 0, 1, 0, 0, 0);          // system-view.js SIM_EPOCH\nexport const SIM_RATE = (2 * Math.PI) / (1000 * 1800 * 1500);       // system-view.js SIM_RATE: 2π / (1000 · PLANET_DAY · ROT_VIS.Rocky)\nexport const SUN_GAIN = Math.PI / (2 * Math.sqrt(2 / 3));          // see THE CALIBRATION above\nexport const AIR_TRANSMISSION = 1;                                 // see THE AIR'S TRANSMISSION above\n// ONE STAR'S SHARE, the one place a sun is priced: its flux at the world and the sine of its elevation above the\n// surface (the cosine of its angle from the up). The renderer's live gauge (system-view.js updateSky, for a guest)\n// calls this too, so a sky-read rate and a descriptor-read one are the same arithmetic.\nexport function starlightOn(flux, sinElev) {\n  return flux > 0 && sinElev > 0 ? flux * Math.min(1, sinElev) * SUN_GAIN * AIR_TRANSMISSION : 0;\n}\nfunction phasorSum(p, T, out) {\n  let x = 0, z = 0;\n  if (Array.isArray(p)) for (let i = 0; i + 2 < p.length; i += 3) { const ang = p[i + 1] - p[i + 2] * T; x += p[i] * Math.cos(ang); z += p[i] * Math.sin(ang); }\n  out[0] = x; out[1] = z; return out;\n}\nconst _pb = [0, 0], _ps = [0, 0];\n// The starlight a stance sees at wall-clock time `ms` — the number chargeRate prices.\nexport function sunlightAt(desc, ms) {\n  if (!desc || typeof desc !== 'object') return OFF_SURFACE_INSOLATION;\n  return starlightThrough(desc, ms, +desc.nx || 0, +desc.ny || 0, +desc.nz || 0, starlightOn);\n}\n// …ITS SUM, STAR BY STAR, PRICED BY `price(flux, sinElev)`: starlightOn for the suit (above), and the tree seedlings' own\n// share of light (seedlings.mjs growLightAt, the owner's Tree Light picks of Oct 23: \"tree growth rate to match how much\n// light the tree has received\", a twilight band counting by its share, daylightFrom). One walk of the sky for both, so a\n// seedling's sun and the suit's are the same sun on every end; the suit's arithmetic is the one it always did, to the bit.\nexport function starlightSum(desc, ms, price) {\n  return starlightThrough(desc, ms, +desc.nx || 0, +desc.ny || 0, +desc.nz || 0, price);\n}\n// …ON ANY FACE OF THE SAME WORLD AT THE SAME MOMENT: the world's place, its spin, its lean and its stars are the descriptor's;\n// only the face's outward normal (nx, ny, nz, the corner-up frame's) is the caller's. sunlightAt asks it for the face the\n// player stands on, panelSunAt for each face a placed Solar Panel stands on (PLACED PANELS, below). Same arithmetic, in the\n// same order, as the one function it replaced, so every descriptor stored before panels prices to the bit as it did.\nfunction starlightThrough(desc, ms, nx, ny, nz, price = starlightOn) {\n  const T = (ms - SIM_EPOCH_MS) * SIM_RATE;\n  phasorSum(desc.b, T, _pb);\n  // …AND OUT OF THE PLANE (`bi`, sky 4): a moon on its planet's tipped equator. by stays 0 without it, and every sum below\n  // then adds an exact zero, so a descriptor stored before sky 4 is priced to the bit as it always was.\n  let bx = _pb[0], by = 0, bz = _pb[1];\n  const bi = desc.bi;\n  if (Array.isArray(bi)) for (let i = 0; i + 8 < bi.length; i += 9) {\n    const ang = (+bi[i + 1] || 0) - (+bi[i + 2] || 0) * T, r = +bi[i] || 0, c = r * Math.cos(ang), s = r * Math.sin(ang);\n    bx += c * (+bi[i + 3] || 0) + s * (+bi[i + 6] || 0);\n    by += c * (+bi[i + 4] || 0) + s * (+bi[i + 7] || 0);\n    bz += c * (+bi[i + 5] || 0) + s * (+bi[i + 8] || 0);\n  }\n  const th = (+desc.c0 || 0) + (+desc.c1 || 0) * T, cs = Math.cos(th), sn = Math.sin(th);\n  let ux = nx * cs + nz * sn, uy = ny, uz = -nx * sn + nz * cs;     // three's rotation.y applied to the face normal (its y is kept)\n  // THE LEAN (seasons.mjs axisLean): the spun normal turned by g about the in-plane axis k = (sin d, 0, −cos d), which\n  // carries +Y toward the bearing d: three's setFromAxisAngle(k, g), which system-view.js setSpin puts before the spin.\n  const t = desc.t;\n  if (Array.isArray(t) && t.length >= 5) {\n    const g = (+t[0] || 0) * Math.sin((+t[1] || 0) + (+t[2] || 0) * T), d = (+t[3] || 0) + (+t[4] || 0) * T;\n    if (g !== 0) {\n      const kx = Math.sin(d), kz = -Math.cos(d), cg = Math.cos(g), sg = Math.sin(g), kd = (kx * ux + kz * uz) * (1 - cg);\n      const vx = ux * cg - kz * ny * sg + kx * kd, vz = uz * cg + kx * ny * sg + kz * kd;   // Rodrigues: u cos g + (k × u) sin g + k (k·u)(1 − cos g)\n      uy = ny * cg + (kz * ux - kx * uz) * sg;                                              // …its height too, which a sun out of the plane now reads\n      ux = vx; uz = vz;\n    }\n  }\n  let total = 0;\n  // …AND WHAT COVERS IT (the owner's Sky and Eclipses picks, Oct 24, e4: \"the server prices sunlight the same way\"). A\n  // descriptor of sky 5 carries `ec`, the cubes that can pass in front of its suns; the share of each sun they hide from the\n  // face's centre (the stance is a face: the point a suit or a panel there is priced at) dims that sun, as the shadow on\n  // screen does. Without `ec` every share is 1 and the sum is the one it always was, to the bit.\n  const ec = desc.ec && typeof desc.ec === 'object' && Array.isArray(desc.ec.o) && desc.ec.o.length ? desc.ec : null;\n  const h = ec ? (+ec.h || 0) : 0, P = ec ? [bx + ux * h, by + uy * h, bz + uz * h] : null;\n  const stars = Array.isArray(desc.s) ? desc.s : [];\n  for (let i = 0; i < stars.length; i++) {\n    const st = stars[i];\n    if (!st || !(st.f > 0)) continue;\n    phasorSum(st.p, T, _ps);\n    const dx = _ps[0] - bx, dy = -by, dz = _ps[1] - bz, L = Math.sqrt(dx * dx + dy * dy + dz * dz);   // every star is in the plane\n    if (!(L > 0)) continue;\n    const sinElev = (dx * ux + dy * uy + dz * uz) / L;\n    total += ec && sinElev > 0 ? price(st.f * (1 - eclipseCoverAt(ec, i, T, P, _ps[0], _ps[1])), sinElev) : price(st.f, sinElev);\n  }\n  return total;\n}\n// THE SHARE OF STAR `i` THE DESCRIPTOR'S OCCLUDERS HIDE FROM POINT P at sim time T (eclipse.mjs: their true cube outlines). Each\n// body is put where the renderer puts it (its phasors, and its tipped circle `bi`) and turned as setSpin turns it (rotation.y\n// = c0 + c1·T, then its lean `t`), after the corner-up turn every body is drawn with. A body nowhere near the sun's line from\n// P is passed over before it is turned.\nconst _eo = [0, 0];\nfunction eclipseCoverAt(ec, i, T, P, sx, sz) {\n  const se = Array.isArray(ec.s) ? ec.s[i] : null;\n  if (!se || !(se.e > 0)) return 0;\n  const sun = { c: [sx, 0, sz], axes: turnedAxes(+se.c0 || 0, +se.c1 || 0, null, T), half: se.e / 2 };\n  const ddx = sx - P[0], ddy = -P[1], ddz = sz - P[2], DS = Math.sqrt(ddx * ddx + ddy * ddy + ddz * ddz);\n  if (!(DS > 0)) return 0;\n  const rS = sun.half * 1.7320508 / DS, occ = [];\n  for (const o of ec.o) {\n    if (!o || !(o.e > 0)) continue;\n    const c = placeOf3(o.p, o.bi, T);\n    const qx = c[0] - P[0], qy = c[1] - P[1], qz = c[2] - P[2], D = (qx * ddx + qy * ddy + qz * ddz) / DS;\n    if (!(D > 0) || !(D < DS)) continue;\n    const px = qx - D * ddx / DS, py = qy - D * ddy / DS, pz = qz - D * ddz / DS;\n    if (Math.sqrt(px * px + py * py + pz * pz) / D > rS + o.e * 0.8660254 / D) continue;   // its outline cannot meet the sun's\n    occ.push({ c, axes: turnedAxes(+o.c0 || 0, +o.c1 || 0, o.t, T), half: o.e / 2 });\n  }\n  if (!occ.length) return 0;\n  return coveredAt(eclipseSetup({ origin: P, sun, occluders: occ, reach: 0, max: ECL_DESC_MAX }), P);\n}\n// Where a body is: Σ r·(cos(a − w·T), 0, sin(a − w·T)) in the plane, plus its tipped circle's terms (`bi`), as for the world.\nfunction placeOf3(p, bi, T) {\n  phasorSum(p, T, _eo);\n  let x = _eo[0], y = 0, z = _eo[1];\n  if (Array.isArray(bi)) for (let i = 0; i + 8 < bi.length; i += 9) {\n    const ang = (+bi[i + 1] || 0) - (+bi[i + 2] || 0) * T, r = +bi[i] || 0, c = r * Math.cos(ang), s = r * Math.sin(ang);\n    x += c * (+bi[i + 3] || 0) + s * (+bi[i + 6] || 0); y += c * (+bi[i + 4] || 0) + s * (+bi[i + 7] || 0); z += c * (+bi[i + 5] || 0) + s * (+bi[i + 8] || 0);\n  }\n  return [x, y, z];\n}\n// A body's cube axes at T: the corner-up axes, spun (rotation.y = c0 + c1·T) and leant by `t` (seasons.mjs axisLean, as above).\nfunction turnedAxes(c0, c1, t, T) {\n  const th = c0 + c1 * T, cs = Math.cos(th), sn = Math.sin(th);\n  let g = 0, kx = 0, kz = 0;\n  if (Array.isArray(t) && t.length >= 5) { g = (+t[0] || 0) * Math.sin((+t[1] || 0) + (+t[2] || 0) * T); const d = (+t[3] || 0) + (+t[4] || 0) * T; kx = Math.sin(d); kz = -Math.cos(d); }\n  const cg = Math.cos(g), sg = Math.sin(g);\n  return CORNER_UP_AXES.map((v) => {\n    const ux = v[0] * cs + v[2] * sn, uy = v[1], uz = -v[0] * sn + v[2] * cs;\n    if (g === 0) return [ux, uy, uz];\n    const kd = (kx * ux + kz * uz) * (1 - cg);\n    return [ux * cg - kz * uy * sg + kx * kd, uy * cg + (kz * ux - kx * uz) * sg, uz * cg + kx * uy * sg + kz * kd];\n  });\n}\n// QA and tests: the cubes a descriptor puts in its sky at `ms` — the stance's point, each star's and each occluder's centre,\n// axes and half-edge — and each sun's covered share from the point. Null for a descriptor with no occluders.\nexport function eclipseBodiesAt(desc, ms) {\n  const ec = desc && desc.ec && Array.isArray(desc.ec.o) && desc.ec.o.length ? desc.ec : null;\n  if (!ec) return null;\n  const T = (ms - SIM_EPOCH_MS) * SIM_RATE;\n  const out = { T, world: placeOf3(desc.b, desc.bi, T), stars: [], occ: ec.o.map((o) => ({ c: placeOf3(o.p, o.bi, T), axes: turnedAxes(+o.c0 || 0, +o.c1 || 0, o.t, T), half: o.e / 2 })) };\n  (Array.isArray(desc.s) ? desc.s : []).forEach((st, i) => { phasorSum(st.p, T, _eo); const se = ec.s && ec.s[i];\n    out.stars.push({ c: [_eo[0], 0, _eo[1]], axes: se ? turnedAxes(+se.c0 || 0, +se.c1 || 0, null, T) : null, half: se ? se.e / 2 : 0 }); });\n  return out;\n}\n\n// ---- PLACED PANELS: SUNLIGHT STORED FOR THE NIGHT (the owner's play review, Oct 23, f7) ------------------------------\n// \"Night side means no energy … a player on a world's night side cannot mine for hours\"; the pick: \"Worn batteries and a\n// placed Solar Panel store sunlight for later; mining at night draws on what was stored.\" Worn batteries already hold what\n// the day charged (the bank is one number, capacityFor). What was missing is a source that works while YOU are in the dark:\n// a Solar Panel placed on a claim you own charges at PANEL_RATE times the sunlight on ITS OWN FACE of the world, so a panel\n// on the day face fills your batteries while you mine on the night face. The rules, each one a decision:\n//   ONLY INTO BATTERIES. The suit itself still charges only by the sunlight it stands in (the owner's sunlight-only rule,\n//     Oct 7): without a worn battery (cap above SUIT_CAPACITY) a panel gives nothing. The bank is one pooled number, as\n//     batteryFill explains, so \"into the batteries\" is the gate, not a second account.\n//   ONLY ON THAT WORLD, standing on it: the stance's descriptor carries the panels (`pv`), and a stance off a surface (orbit,\n//     flight, another world) carries none. Leaving is a checkpoint like any change of stance.\n//   ONLY YOUR OWN CLAIM: the Functions put `pv` on the descriptor only for the claim's owner (index.js chargeStance), from\n//     the claim's panel count (claims/{addr}.panels, kept by submitEdits as panels are placed and broken).\n//   AT MOST PANEL_MAX panels count, the same ceiling the carried-panel rule had, for the same honesty.\n// `pv` is flat numbers, like the rest of the descriptor: [nx, ny, nz, count, …] per face that holds panels, the face's\n// outward normal in the corner-up frame (sunlight.mjs panelTerms builds it). Shade is left out, as it is for the suit.\nexport function panelSunAt(desc, ms) {\n  const pv = desc && desc.pv;\n  if (!Array.isArray(pv) || !pv.length) return 0;\n  let total = 0, left = PANEL_MAX;\n  for (let i = 0; i + 3 < pv.length && left > 0; i += 4) {\n    const n = Math.min(left, Math.max(0, Math.floor(+pv[i + 3] || 0)));\n    if (!n) continue;\n    left -= n;\n    total += n * Math.min(INSOLATION_MAX, starlightThrough(desc, ms, +pv[i] || 0, +pv[i + 1] || 0, +pv[i + 2] || 0));\n  }\n  return total;\n}\n// WHAT A STANCE CHARGES AT, the one rate every end integrates: the suit by its own sunlight, plus the placed panels' when a\n// battery is worn to take it. `cap` is the capacity the caller already priced the balance with (capacityFor).\nexport function stanceRate(desc, ms, cap) {\n  const suit = chargeRate(sunlightAt(desc, ms));\n  if (!desc || !(cap > SUIT_CAPACITY + 1e-9)) return suit;\n  return suit + PANEL_RATE * panelSunAt(desc, ms);\n}\n// INTEGRATED OVER THE INTERVAL, not sampled at its end. energyAfter assumes one rate from the anchor to now, which is\n// exact only while the rate holds — and here the world turns under you, so a balance anchored at noon and read at\n// midnight has a sunset in between. The interval is walked on a fixed SUN_STEP_MS grid from the anchor, each step\n// priced at its midpoint (the sun climbs over minutes, so two-second steps resolve a dawn to well under a unit), and\n// stops the moment the tank is full — charging can only fill, so nothing later can change the answer. The grid starts\n// at the ANCHOR, never at a round number, so any two parties with the same anchor walk exactly the same steps.\n// A VERY long gap (days offline on a night face) is walked for SUN_MAX_STEPS and the rest is credited at the average\n// rate of what was walked: still deterministic, and a fixed price however long the account was away.\n// `cache` (optional, the client's) remembers the walk so far for one anchor; with it or without it the answer is the\n// same sequence of additions, so the gauge can ask every frame without re-walking an afternoon each time.\nexport const SUN_STEP_MS = 2000;\nexport const SUN_MAX_STEPS = 100000;\nexport function energyAfterSun(value, atMs, nowMs, cap, desc, cache) {\n  if (value == null) return cap;\n  if (!desc) return energyAfter(value, atMs, nowMs, cap, chargeRate(OFF_SURFACE_INSOLATION));\n  if (!(nowMs > atMs)) return Math.max(0, Math.min(cap, value));\n  let c = cache;\n  if (!c || c.value !== value || c.atMs !== atMs || c.cap !== cap || c.desc !== desc) {\n    c = { value, atMs, cap, desc, t: atMs, v: value, n: 0, sum: 0 };\n    if (cache) Object.assign(cache, c), c = cache;\n  }\n  if (c.v >= cap) return cap;\n  // Whole steps first (these are what the cache keeps), then the part-step to `now`, which is never kept.\n  while (c.n < SUN_MAX_STEPS && c.t + SUN_STEP_MS <= nowMs) {\n    const r = stanceRate(desc, c.t + SUN_STEP_MS / 2, cap);\n    c.v += r * (SUN_STEP_MS / 1000); c.sum += r; c.n++; c.t += SUN_STEP_MS;\n    if (c.v >= cap) { c.v = cap; return cap; }\n  }\n  if (nowMs < c.t) return Math.max(0, Math.min(cap, value));       // a clock read from before the cached walk: as if no time passed\n  let v = c.v;\n  const rest = nowMs - c.t;\n  if (c.n >= SUN_MAX_STEPS) v += (c.sum / c.n) * (rest / 1000);\n  else if (rest > 0) v += stanceRate(desc, c.t + rest / 2, cap) * (rest / 1000);\n  return Math.max(0, Math.min(cap, v));\n}\n// HOW LONG UNTIL `need` IS AFFORDABLE, by walking forward the same way (seconds; Infinity past `horizonMs`). What a\n// refusal quotes: \"ready in 40s\" on a sunlit face, and honestly \"not before sunrise\" on a night one.\nexport function secondsUntil(need, value, atMs, nowMs, cap, desc, horizonMs = 6 * 3600 * 1000) {\n  if (!(need > 0)) return 0;\n  if (need > cap + 1e-6) return Infinity;\n  const have = energyAfterSun(value, atMs, nowMs, cap, desc);\n  if (have + 1e-6 >= need) return 0;\n  let v = have, t = nowMs;\n  const step = desc ? SUN_STEP_MS : 1000;\n  while (t - nowMs < horizonMs) {\n    v += stanceRate(desc, t + step / 2, cap) * (step / 1000); t += step;\n    if (v + 1e-6 >= need) return Math.ceil((t - nowMs) / 1000);\n  }\n  return Infinity;\n}\n\n// ---- SPEND: MINING IS A DURATION, TRAVEL IS A DISTANCE -------------------------------------------------------\n// Mining keeps its existing price — the density-derived mine time, in seconds — so the numbers a player has already\n// learned do not move, and the gauge draining over the hold IS the mine timer. What changes is that the budget can\n// now be far larger than the block.\n//\n// TRAVEL IS PRICED BY HOW FAR IT ACTUALLY IS. A flat fare made the map's geometry decorative: hopping to the star\n// next door and hopping to the far rim cost the same, so the cosmic-web filaments — the whole point of the universe\n// layout — said nothing about the journey. Distance pricing makes travelling ALONG a filament cheap and crossing a\n// void expensive, which is the shape of the thing the player is looking at.\n//\n// THE CONSTANTS BELOW ARE SET FROM MEASURED SEPARATIONS, not chosen. Sampling 1,500 stars in each of five galaxies\n// and 343 galaxies out to cell radius 3:\n//\n//   within a galaxy (parsecs — the unit the info panel already prints)\n//     nearest star   0.38 – 0.51      a hop to the neighbour you can see\n//     median pair    8.6  – 17.6      an ordinary journey across your neighbourhood\n//     90th pair      15.8 – 31.1\n//     corner to corner 29.4 – 46.9    the longest trip a galaxy can ask for\n//\n//   between galaxies (lattice cells, AFTER the cosmic-web flow displaces them)\n//     nearest neighbour  p50 0.54, p90 0.91, max 1.69\n//     any pair in reach  p50 5.92, p90 8.29\n//\n// Read against SUIT_CAPACITY (15), the rates below mean: almost every hop is affordable on a bare suit, the longest\n// tenth need banked storage, and every galaxy's NEAREST neighbour stays reachable with nothing built — measured, and\n// pinned by a test, because \"sealed into your home galaxy with no way out\" is not a cost, it is a dead end.\nexport const HOP_BASE = 1.0;         // any hop at all, however short: undocking is work\n// …plus this per parsec. RETUNED FROM 0.6, and the old number is worth recording because it was not wrong, it was\n// answering a different question: at 0.6 a hop was priced so the 90th-percentile one just outgrew the bare suit.\n// That makes distance a cost. It does not make it a RANGE, and the galaxy map now draws one.\n//\n// Measured on the live build: a galaxy is 20.3 pc in radius, 40.6 pc across. At 0.6/pc the bare suit reached\n// 23.3 pc — further than the radius — so from anywhere near the centre every one of the galaxy's ~5,700 stars was\n// already in reach, and with a battery (106.7 pc) nothing anywhere could ever be out of it. The drive radius was\n// drawn correctly and bounded nothing.\n//\n// 2.8 was the figure that gave a 5.0 pc suit range, and it is NOT SAFE: measured across generated galaxies, the\n// most isolated star in the game is 5.57 pc from its nearest neighbour, and at 2.8 reaching it costs 16.6 against a\n// bare suit of 15. A player who spawned there could never reach ANY star — and \"hard stop means wait\" is the one\n// promise the whole energy design rests on, so it cannot be spent on a drive radius.\n//\n// That worst case is the real ceiling: HOP_BASE + k·5.57 <= SUIT_CAPACITY gives k <= 2.51. At 2.4 the suit reaches\n// 5.83 pc — still about a quarter of a 20.3 pc radius, so the map is worth planning a route across — and the worst\n// nearest neighbour costs 14.4, inside a bare suit with a little to spare. A test derives that ceiling from the\n// generated map rather than trusting this comment, so a future retune cannot strand anyone.\nexport const HOP_PER_PC = 2.4;\nexport const CROSS_BASE = 3.0;       // leaving a galaxy at all\nexport const CROSS_PER_CELL = 6.5;   // …plus this per lattice cell, so a void crossing costs many times a filament step\n// TWO BATTERIES GO ANYWHERE. A cap is needed or a far enough pair prices a trip no amount of storage can buy; this\n// one is stated as a property a player can learn and rely on rather than as a round number.\nexport const TRAVEL_MAX = 100;       // < capacityFor(2) = 115\nexport const ORBIT_MINE_PER_CELL = 1.5;   // one cell of an asteroid or comet, from the flyer\n// PAST THE BARE SUIT'S REACH, A PARSEC COSTS LESS (the owner's play review, Oct 23, f1: \"Energy traps new players in their\n// system … a trip to the nearest alien town costs 28.6 suit energy; a bare suit holds 15\"; the pick: \"Short hops cost less\n// than the suit holds, and a first quest gives a Battery, so the second session can travel\"). A hop is priced as before up\n// to HOP_KNEE_PC, the furthest a full bare suit reaches — DERIVED, not chosen, so every guarantee above (the most isolated\n// star's nearest neighbour, the drive radius a bare suit draws, a median pair being a route rather than a hop) is exactly\n// what it was. Beyond it each further parsec costs HOP_TAPER of the rate. What that buys is the first battery: before, a\n// battery's 50 bought 20.8 more parsecs; now it buys 34.7, so the nearest town worth visiting is one trip with the way\n// home in hand rather than two waits and a gamble. The measured numbers (tests/energy.test.mjs, tests/travel-fare.test.mjs):\n//   the 11.5 pc hop that cost 28.6 now costs 23.2 — still past a bare suit (the trap was the suit, and the Battery from\n//   the first quest is what lifts it), and a starter Battery (65) holds it there and back;\n//   the longest hop a galaxy can ask for (~47 pc) still needs more than one battery (74 against 65): storage still matters.\n// 0.6 is the floor that keeps that last property with margin (it fails below 0.51); a gentler taper hands the whole galaxy\n// to the first battery. GALAXY CROSSINGS ARE NOT TAPERED: a void crossing is meant to be many filament steps' worth, and\n// the never-sealed-in guarantee is about the NEAREST galaxy, which is inside a bare suit already.\nexport const HOP_KNEE_PC = (SUIT_CAPACITY - HOP_BASE) / HOP_PER_PC;   // 5.83 pc: a full bare suit's reach\nexport const HOP_TAPER = 0.6;\n// [base, per unit, knee (units), per unit past the knee as a fraction of the rate]; a kind without a knee is linear.\nexport const TRAVEL_KINDS = { system: [HOP_BASE, HOP_PER_PC, HOP_KNEE_PC, HOP_TAPER], galaxy: [CROSS_BASE, CROSS_PER_CELL] };\n// `distance` is PARSECS for a system hop and LATTICE CELLS for a galaxy crossing — the two natural units of the two\n// views, and both are what their own readouts already display. An unknown kind is refused rather than priced at zero.\nexport function travelCost(kind, distance) {\n  const k = TRAVEL_KINDS[kind];\n  if (!k) return null;\n  const d = Math.max(0, Number(distance) || 0);\n  const knee = k[2] > 0 ? k[2] : Infinity;\n  const units = d <= knee ? d : knee + (d - knee) * k[3];\n  return Math.min(TRAVEL_MAX, k[0] + k[1] * units);\n}\n// HOW FAR THAT BUDGET REACHES — travelCost read backwards. The galaxy map has to answer \"which of these 5,744\n// stars can I get to\" every quarter second, and asking the price of each one in turn is 5,744 answers to a question\n// that has exactly one: a radius. Inverted once, the map is a distance comparison.\n//\n// Three answers that are not a number, and each of them matters:\n//   Infinity  the fare is CAPPED at TRAVEL_MAX, so once you can afford the cap every distance is affordable.\n//   -1        you cannot even afford the base fare, so nothing is reachable — distinct from \"only zero distance is\".\n//   Infinity  a kind with no per-distance term is flat-rate; if you can pay it at all you can pay it anywhere.\nexport function travelRange(kind, budget) {\n  const k = TRAVEL_KINDS[kind];\n  if (!k) return null;\n  const b = Math.max(0, Number(budget) || 0);\n  if (b + 1e-6 >= TRAVEL_MAX) return Infinity;\n  if (b + 1e-6 < k[0]) return -1;\n  if (!(k[1] > 0)) return Infinity;\n  const units = (b - k[0]) / k[1], knee = k[2] > 0 ? k[2] : Infinity;   // …past the knee, each unit of budget goes 1/taper as far\n  return units <= knee ? units : knee + (units - knee) / k[3];\n}\n\n// HARD STOP, and the suit is why that is not a trap. Every refusal is recoverable: by waiting, on a face that turns\n// into daylight, and on one that never does (the night face of a world locked to its star, a rogue's, or a face in\n// its polar night, which lasts until the season turns) by stepping into orbit, where the suit always charges at reference insolation — so \"you cannot afford this\" never means \"you can\n// never afford it\".\nexport function canAfford(balance, cost) { return (balance || 0) + 1e-6 >= (cost || 0); }\n\n// ---- THE RATE LIMIT THAT USED TO BE FREE --------------------------------------------------------------------\n// The old budget stopped a client submitting mines faster than they could physically be mined, purely because it was\n// too small to bank. It is not too small any more, so that check is stated on its own terms — as a LEAKY BUCKET over\n// claimed mine-seconds, which is a sliding window without having to store the window.\n//\n// TWO NUMBERS, BECAUSE THEY ARE TWO DIFFERENT LIMITS, and the first version of this conflated them into one\n// (\"a 60-second window admitting 180 mine-seconds\") and was wrong as a result. What the conflated version refused\n// was an HONEST player: edits queue client-side while offline and flush in one call, so a legitimate backlog of a\n// few minutes' mining arrives as a single submission worth far more than 180 seconds, and was rejected outright.\n//\n//   THE BUCKET is how much may land AT ONCE, and it has to be at least one maximal batch or the validator refuses\n//   a call its own MAX_EDITS allows: 120 ops x MINE_DUR_MAX 10s. That relationship is pinned by a test, not left\n//   to this comment.\n//\n//   THE DRAIN is the SUSTAINED limit, and it is the one actually doing the work: a player mining continuously\n//   produces one claimed second per real second, so three per real second leaves room for batching jitter and an\n//   offline catch-up while still making a script claiming a thousand blocks a second impossible to keep up.\nexport const MINE_BURST_BUCKET = 1200;   // most that may be claimed in one go — MAX_EDITS x MINE_DUR_MAX\nexport const MINE_BURST_RATE = 3;        // …and claimed mine-seconds drain at this multiple of real time\nexport function burstExceeded(spentInBucket, cost) {\n  return (spentInBucket || 0) + (cost || 0) > MINE_BURST_BUCKET + 1e-6;\n}\n"],"mappings":"AAaA,OAAS,eAAgB,aAAc,UAAW,iBAAoB,gBAK/D,aAAM,cAAgB,GAChB,iBAAmB,GACnB,aAAe,UACf,WAAa,cAOnB,gBAAS,YAAY,UAAW,CACrC,MAAM,EAAI,KAAK,IAAI,EAAG,KAAK,MAAM,WAAa,CAAC,CAAC,EAChD,OAAO,cAAgB,iBAAmB,CAC5C,CAYO,gBAAS,YAAY,KAAM,MAAO,CACvC,MAAM,EAAI,KAAK,IAAI,EAAG,OAAO,IAAI,GAAK,CAAC,EACjC,EAAI,KAAK,IAAI,EAAG,KAAK,MAAM,OAAO,KAAK,GAAK,CAAC,CAAC,EACpD,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,GAAI,EAAI,EAAI,kBAAoB,gBAAgB,CAAC,CAC/E,CAMO,aAAM,UAAY,EAMZ,WAAa,EAGb,UAAY,EASZ,eAAiB,EACvB,gBAAS,aAAa,SAAU,SAAU,SAAU,CACzD,GAAI,EAAE,SAAW,IAAM,EAAE,SAAW,GAAI,MAAO,GAC/C,MAAM,KAAQ,SAAW,UAAa,SAAW,UAC3C,IAAM,UAAY,KAAO,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,EAAG,QAAQ,CAAC,EACpE,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,eAAgB,IAAI,CAAC,EAAI,GACvD,CAIO,aAAM,SAAW,GACjB,gBAAS,aAAa,aAAc,CACzC,MAAM,EAAI,cAAuB,EACjC,GAAI,GAAK,CAAC,SAAU,MAAO,GAC3B,GAAI,GAAK,SAAU,OAAO,KAAK,IAAI,EAAG,CAAC,EACvC,MAAM,GAAK,EAAI,WAAa,EAAI,UAChC,OAAO,SAAW,EAAI,CACxB,CAOO,gBAAS,WAAW,WAAY,OAAQ,CAC7C,MAAM,EAAI,KAAK,IAAI,EAAG,KAAK,IAAI,UAAW,OAAO,MAAM,GAAK,CAAC,CAAC,EAC9D,OAAQ,UAAY,WAAa,GAAK,KAAK,IAAI,EAAG,KAAK,IAAI,eAAgB,YAAc,CAAC,CAAC,CAC7F,CAQO,gBAAS,YAAY,MAAO,KAAM,MAAO,IAAK,KAAM,CACzD,GAAI,OAAS,KAAM,OAAO,IAC1B,MAAM,GAAK,KAAK,IAAI,GAAI,MAAQ,MAAQ,GAAI,EAC5C,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,IAAK,MAAQ,GAAK,KAAK,IAAI,EAAG,MAAQ,CAAC,CAAC,CAAC,CACvE,CAoEO,aAAM,uBAAyB,EACzB,aAAe,KAAK,IAAI,KAAM,EAAG,EAAG,EAAG,EAAG,CAAC,EAC3C,SAAY,EAAI,KAAK,IAAO,IAAO,KAAO,MAC1C,SAAW,KAAK,IAAM,EAAI,KAAK,KAAK,EAAI,CAAC,GACzC,iBAAmB,EAIzB,gBAAS,YAAY,KAAM,QAAS,CACzC,OAAO,KAAO,GAAK,QAAU,EAAI,KAAO,KAAK,IAAI,EAAG,OAAO,EAAI,SAAW,iBAAmB,CAC/F,CACA,SAAS,UAAU,EAAG,EAAG,IAAK,CAC5B,IAAI,EAAI,EAAG,EAAI,EACf,GAAI,MAAM,QAAQ,CAAC,EAAG,QAAS,EAAI,EAAG,EAAI,EAAI,EAAE,OAAQ,GAAK,EAAG,CAAE,MAAM,IAAM,EAAE,EAAI,CAAC,EAAI,EAAE,EAAI,CAAC,EAAI,EAAG,GAAK,EAAE,CAAC,EAAI,KAAK,IAAI,GAAG,EAAG,GAAK,EAAE,CAAC,EAAI,KAAK,IAAI,GAAG,CAAG,CAC7J,WAAI,CAAC,EAAI,EAAG,IAAI,CAAC,EAAI,EAAU,GACjC,CACA,MAAM,IAAM,CAAC,EAAG,CAAC,EAAG,IAAM,CAAC,EAAG,CAAC,EAExB,gBAAS,WAAW,KAAM,GAAI,CACnC,MAAI,CAAC,MAAQ,OAAO,MAAS,SAAiB,uBACvC,iBAAiB,KAAM,GAAI,CAAC,KAAK,IAAM,EAAG,CAAC,KAAK,IAAM,EAAG,CAAC,KAAK,IAAM,EAAG,WAAW,CAC5F,CAKO,gBAAS,aAAa,KAAM,GAAI,MAAO,CAC5C,OAAO,iBAAiB,KAAM,GAAI,CAAC,KAAK,IAAM,EAAG,CAAC,KAAK,IAAM,EAAG,CAAC,KAAK,IAAM,EAAG,KAAK,CACtF,CAKA,SAAS,iBAAiB,KAAM,GAAI,GAAI,GAAI,GAAI,MAAQ,YAAa,CACnE,MAAM,GAAK,GAAK,cAAgB,SAChC,UAAU,KAAK,EAAG,EAAG,GAAG,EAGxB,IAAI,GAAK,IAAI,CAAC,EAAG,GAAK,EAAG,GAAK,IAAI,CAAC,EACnC,MAAM,GAAK,KAAK,GAChB,GAAI,MAAM,QAAQ,EAAE,EAAG,QAAS,EAAI,EAAG,EAAI,EAAI,GAAG,OAAQ,GAAK,EAAG,CAChE,MAAM,KAAO,CAAC,GAAG,EAAI,CAAC,GAAK,IAAM,CAAC,GAAG,EAAI,CAAC,GAAK,GAAK,EAAG,EAAI,CAAC,GAAG,CAAC,GAAK,EAAG,EAAI,EAAI,KAAK,IAAI,GAAG,EAAG,EAAI,EAAI,KAAK,IAAI,GAAG,EACnH,IAAM,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GACjD,IAAM,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GACjD,IAAM,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,EACnD,CACA,MAAM,IAAM,CAAC,KAAK,IAAM,IAAM,CAAC,KAAK,IAAM,GAAK,EAAG,GAAK,KAAK,IAAI,EAAE,EAAG,GAAK,KAAK,IAAI,EAAE,EACrF,IAAI,GAAK,GAAK,GAAK,GAAK,GAAI,GAAK,GAAI,GAAK,CAAC,GAAK,GAAK,GAAK,GAG1D,MAAM,EAAI,KAAK,EACf,GAAI,MAAM,QAAQ,CAAC,GAAK,EAAE,QAAU,EAAG,CACrC,MAAM,GAAK,CAAC,EAAE,CAAC,GAAK,GAAK,KAAK,KAAK,CAAC,EAAE,CAAC,GAAK,IAAM,CAAC,EAAE,CAAC,GAAK,GAAK,CAAC,EAAG,GAAK,CAAC,EAAE,CAAC,GAAK,IAAM,CAAC,EAAE,CAAC,GAAK,GAAK,EACtG,GAAI,IAAM,EAAG,CACX,MAAM,GAAK,KAAK,IAAI,CAAC,EAAG,GAAK,CAAC,KAAK,IAAI,CAAC,EAAG,GAAK,KAAK,IAAI,CAAC,EAAG,GAAK,KAAK,IAAI,CAAC,EAAG,IAAM,GAAK,GAAK,GAAK,KAAO,EAAI,IACzG,GAAK,GAAK,GAAK,GAAK,GAAK,GAAK,GAAK,GAAI,GAAK,GAAK,GAAK,GAAK,GAAK,GAAK,GAAK,GAChF,GAAK,GAAK,IAAM,GAAK,GAAK,GAAK,IAAM,GACrC,GAAK,GAAI,GAAK,EAChB,CACF,CACA,IAAI,MAAQ,EAKZ,MAAM,GAAK,KAAK,IAAM,OAAO,KAAK,IAAO,UAAY,MAAM,QAAQ,KAAK,GAAG,CAAC,GAAK,KAAK,GAAG,EAAE,OAAS,KAAK,GAAK,KACxG,EAAI,IAAM,CAAC,GAAG,GAAK,EAAQ,EAAI,GAAK,CAAC,GAAK,GAAK,EAAG,GAAK,GAAK,EAAG,GAAK,GAAK,CAAC,EAAI,KAC9E,MAAQ,MAAM,QAAQ,KAAK,CAAC,EAAI,KAAK,EAAI,CAAC,EAChD,QAAS,EAAI,EAAG,EAAI,MAAM,OAAQ,IAAK,CACrC,MAAM,GAAK,MAAM,CAAC,EAClB,GAAI,CAAC,IAAM,EAAE,GAAG,EAAI,GAAI,SACxB,UAAU,GAAG,EAAG,EAAG,GAAG,EACtB,MAAM,GAAK,IAAI,CAAC,EAAI,GAAI,GAAK,CAAC,GAAI,GAAK,IAAI,CAAC,EAAI,GAAI,EAAI,KAAK,KAAK,GAAK,GAAK,GAAK,GAAK,GAAK,EAAE,EAC7F,GAAI,EAAE,EAAI,GAAI,SACd,MAAM,SAAW,GAAK,GAAK,GAAK,GAAK,GAAK,IAAM,EAChD,OAAS,IAAM,QAAU,EAAI,MAAM,GAAG,GAAK,EAAI,eAAe,GAAI,EAAG,EAAG,EAAG,IAAI,CAAC,EAAG,IAAI,CAAC,CAAC,GAAI,OAAO,EAAI,MAAM,GAAG,EAAG,OAAO,CAC7H,CACA,OAAO,KACT,CAKA,MAAM,IAAM,CAAC,EAAG,CAAC,EACjB,SAAS,eAAe,GAAI,EAAG,EAAG,EAAG,GAAI,GAAI,CAC3C,MAAM,GAAK,MAAM,QAAQ,GAAG,CAAC,EAAI,GAAG,EAAE,CAAC,EAAI,KAC3C,GAAI,CAAC,IAAM,EAAE,GAAG,EAAI,GAAI,MAAO,GAC/B,MAAM,IAAM,CAAE,EAAG,CAAC,GAAI,EAAG,EAAE,EAAG,KAAM,WAAW,CAAC,GAAG,IAAM,EAAG,CAAC,GAAG,IAAM,EAAG,KAAM,CAAC,EAAG,KAAM,GAAG,EAAI,CAAE,EAC5F,IAAM,GAAK,EAAE,CAAC,EAAG,IAAM,CAAC,EAAE,CAAC,EAAG,IAAM,GAAK,EAAE,CAAC,EAAG,GAAK,KAAK,KAAK,IAAM,IAAM,IAAM,IAAM,IAAM,GAAG,EACrG,GAAI,EAAE,GAAK,GAAI,MAAO,GACtB,MAAM,GAAK,IAAI,KAAO,UAAY,GAAI,IAAM,CAAC,EAC7C,UAAW,KAAK,GAAG,EAAG,CACpB,GAAI,CAAC,GAAK,EAAE,EAAE,EAAI,GAAI,SACtB,MAAM,EAAI,SAAS,EAAE,EAAG,EAAE,GAAI,CAAC,EACzB,GAAK,EAAE,CAAC,EAAI,EAAE,CAAC,EAAG,GAAK,EAAE,CAAC,EAAI,EAAE,CAAC,EAAG,GAAK,EAAE,CAAC,EAAI,EAAE,CAAC,EAAG,GAAK,GAAK,IAAM,GAAK,IAAM,GAAK,KAAO,GACnG,GAAI,EAAE,EAAI,IAAM,EAAE,EAAI,IAAK,SAC3B,MAAM,GAAK,GAAK,EAAI,IAAM,GAAI,GAAK,GAAK,EAAI,IAAM,GAAI,GAAK,GAAK,EAAI,IAAM,GACtE,KAAK,KAAK,GAAK,GAAK,GAAK,GAAK,GAAK,EAAE,EAAI,EAAI,GAAK,EAAE,EAAI,SAAY,GACxE,IAAI,KAAK,CAAE,EAAG,KAAM,WAAW,CAAC,EAAE,IAAM,EAAG,CAAC,EAAE,IAAM,EAAG,EAAE,EAAG,CAAC,EAAG,KAAM,EAAE,EAAI,CAAE,CAAC,CACjF,CACA,OAAK,IAAI,OACF,UAAU,aAAa,CAAE,OAAQ,EAAG,IAAK,UAAW,IAAK,MAAO,EAAG,IAAK,YAAa,CAAC,EAAG,CAAC,EADzE,CAE1B,CAEA,SAAS,SAAS,EAAG,GAAI,EAAG,CAC1B,UAAU,EAAG,EAAG,GAAG,EACnB,IAAI,EAAI,IAAI,CAAC,EAAG,EAAI,EAAG,EAAI,IAAI,CAAC,EAChC,GAAI,MAAM,QAAQ,EAAE,EAAG,QAAS,EAAI,EAAG,EAAI,EAAI,GAAG,OAAQ,GAAK,EAAG,CAChE,MAAM,KAAO,CAAC,GAAG,EAAI,CAAC,GAAK,IAAM,CAAC,GAAG,EAAI,CAAC,GAAK,GAAK,EAAG,EAAI,CAAC,GAAG,CAAC,GAAK,EAAG,EAAI,EAAI,KAAK,IAAI,GAAG,EAAG,EAAI,EAAI,KAAK,IAAI,GAAG,EACnH,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GAAI,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GAAI,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,GAAK,GAAK,CAAC,GAAG,EAAI,CAAC,GAAK,EAC1J,CACA,MAAO,CAAC,EAAG,EAAG,CAAC,CACjB,CAEA,SAAS,WAAW,GAAI,GAAI,EAAG,EAAG,CAChC,MAAM,GAAK,GAAK,GAAK,EAAG,GAAK,KAAK,IAAI,EAAE,EAAG,GAAK,KAAK,IAAI,EAAE,EAC3D,IAAI,EAAI,EAAG,GAAK,EAAG,GAAK,EACxB,GAAI,MAAM,QAAQ,CAAC,GAAK,EAAE,QAAU,EAAG,CAAE,GAAK,CAAC,EAAE,CAAC,GAAK,GAAK,KAAK,KAAK,CAAC,EAAE,CAAC,GAAK,IAAM,CAAC,EAAE,CAAC,GAAK,GAAK,CAAC,EAAG,MAAM,GAAK,CAAC,EAAE,CAAC,GAAK,IAAM,CAAC,EAAE,CAAC,GAAK,GAAK,EAAG,GAAK,KAAK,IAAI,CAAC,EAAG,GAAK,CAAC,KAAK,IAAI,CAAC,CAAG,CACvL,MAAM,GAAK,KAAK,IAAI,CAAC,EAAG,GAAK,KAAK,IAAI,CAAC,EACvC,OAAO,eAAe,IAAK,GAAM,CAC/B,MAAM,GAAK,EAAE,CAAC,EAAI,GAAK,EAAE,CAAC,EAAI,GAAI,GAAK,EAAE,CAAC,EAAG,GAAK,CAAC,EAAE,CAAC,EAAI,GAAK,EAAE,CAAC,EAAI,GACtE,GAAI,IAAM,EAAG,MAAO,CAAC,GAAI,GAAI,EAAE,EAC/B,MAAM,IAAM,GAAK,GAAK,GAAK,KAAO,EAAI,IACtC,MAAO,CAAC,GAAK,GAAK,GAAK,GAAK,GAAK,GAAK,GAAI,GAAK,IAAM,GAAK,GAAK,GAAK,IAAM,GAAI,GAAK,GAAK,GAAK,GAAK,GAAK,GAAK,EAAE,CAChH,CAAC,CACH,CAGO,gBAAS,gBAAgB,KAAM,GAAI,CACxC,MAAM,GAAK,MAAQ,KAAK,IAAM,MAAM,QAAQ,KAAK,GAAG,CAAC,GAAK,KAAK,GAAG,EAAE,OAAS,KAAK,GAAK,KACvF,GAAI,CAAC,GAAI,OAAO,KAChB,MAAM,GAAK,GAAK,cAAgB,SAC1B,IAAM,CAAE,EAAG,MAAO,SAAS,KAAK,EAAG,KAAK,GAAI,CAAC,EAAG,MAAO,CAAC,EAAG,IAAK,GAAG,EAAE,IAAK,IAAO,CAAE,EAAG,SAAS,EAAE,EAAG,EAAE,GAAI,CAAC,EAAG,KAAM,WAAW,CAAC,EAAE,IAAM,EAAG,CAAC,EAAE,IAAM,EAAG,EAAE,EAAG,CAAC,EAAG,KAAM,EAAE,EAAI,CAAE,EAAE,CAAE,EACxL,OAAC,MAAM,QAAQ,KAAK,CAAC,EAAI,KAAK,EAAI,CAAC,GAAG,QAAQ,CAAC,GAAI,IAAM,CAAE,UAAU,GAAG,EAAG,EAAG,GAAG,EAAG,MAAM,GAAK,GAAG,GAAK,GAAG,EAAE,CAAC,EAC3G,IAAI,MAAM,KAAK,CAAE,EAAG,CAAC,IAAI,CAAC,EAAG,EAAG,IAAI,CAAC,CAAC,EAAG,KAAM,GAAK,WAAW,CAAC,GAAG,IAAM,EAAG,CAAC,GAAG,IAAM,EAAG,KAAM,CAAC,EAAI,KAAM,KAAM,GAAK,GAAG,EAAI,EAAI,CAAE,CAAC,CAAG,CAAC,EAClI,GACT,CAkBO,gBAAS,WAAW,KAAM,GAAI,CACnC,MAAM,GAAK,MAAQ,KAAK,GACxB,GAAI,CAAC,MAAM,QAAQ,EAAE,GAAK,CAAC,GAAG,OAAQ,MAAO,GAC7C,IAAI,MAAQ,EAAG,KAAO,UACtB,QAAS,EAAI,EAAG,EAAI,EAAI,GAAG,QAAU,KAAO,EAAG,GAAK,EAAG,CACrD,MAAM,EAAI,KAAK,IAAI,KAAM,KAAK,IAAI,EAAG,KAAK,MAAM,CAAC,GAAG,EAAI,CAAC,GAAK,CAAC,CAAC,CAAC,EAC5D,IACL,MAAQ,EACR,OAAS,EAAI,KAAK,IAAI,eAAgB,iBAAiB,KAAM,GAAI,CAAC,GAAG,CAAC,GAAK,EAAG,CAAC,GAAG,EAAI,CAAC,GAAK,EAAG,CAAC,GAAG,EAAI,CAAC,GAAK,CAAC,CAAC,EACjH,CACA,OAAO,KACT,CAGO,gBAAS,WAAW,KAAM,GAAI,IAAK,CACxC,MAAM,KAAO,WAAW,WAAW,KAAM,EAAE,CAAC,EAC5C,MAAI,CAAC,MAAQ,EAAE,IAAM,cAAgB,MAAc,KAC5C,KAAO,WAAa,WAAW,KAAM,EAAE,CAChD,CAWO,aAAM,YAAc,IACd,cAAgB,IACtB,gBAAS,eAAe,MAAO,KAAM,MAAO,IAAK,KAAM,MAAO,CACnE,GAAI,OAAS,KAAM,OAAO,IAC1B,GAAI,CAAC,KAAM,OAAO,YAAY,MAAO,KAAM,MAAO,IAAK,WAAW,sBAAsB,CAAC,EACzF,GAAI,EAAE,MAAQ,MAAO,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,IAAK,KAAK,CAAC,EAC5D,IAAI,EAAI,MAKR,IAJI,CAAC,GAAK,EAAE,QAAU,OAAS,EAAE,OAAS,MAAQ,EAAE,MAAQ,KAAO,EAAE,OAAS,QAC5E,EAAI,CAAE,MAAO,KAAM,IAAK,KAAM,EAAG,KAAM,EAAG,MAAO,EAAG,EAAG,IAAK,CAAE,EAC1D,QAAO,OAAO,OAAO,MAAO,CAAC,EAAG,EAAI,QAEtC,EAAE,GAAK,IAAK,OAAO,IAEvB,KAAO,EAAE,EAAI,eAAiB,EAAE,EAAI,aAAe,OAAO,CACxD,MAAM,EAAI,WAAW,KAAM,EAAE,EAAI,YAAc,EAAG,GAAG,EAErD,GADA,EAAE,GAAK,GAAK,YAAc,KAAO,EAAE,KAAO,EAAG,EAAE,IAAK,EAAE,GAAK,YACvD,EAAE,GAAK,IAAO,SAAE,EAAI,IAAY,GACtC,CACA,GAAI,MAAQ,EAAE,EAAG,OAAO,KAAK,IAAI,EAAG,KAAK,IAAI,IAAK,KAAK,CAAC,EACxD,IAAI,EAAI,EAAE,EACV,MAAM,KAAO,MAAQ,EAAE,EACvB,OAAI,EAAE,GAAK,cAAe,GAAM,EAAE,IAAM,EAAE,GAAM,KAAO,KAC9C,KAAO,IAAG,GAAK,WAAW,KAAM,EAAE,EAAI,KAAO,EAAG,GAAG,GAAK,KAAO,MACjE,KAAK,IAAI,EAAG,KAAK,IAAI,IAAK,CAAC,CAAC,CACrC,CAGO,gBAAS,aAAa,KAAM,MAAO,KAAM,MAAO,IAAK,KAAM,UAAY,EAAI,KAAO,IAAM,CAC7F,GAAI,EAAE,KAAO,GAAI,MAAO,GACxB,GAAI,KAAO,IAAM,KAAM,MAAO,KAC9B,MAAM,KAAO,eAAe,MAAO,KAAM,MAAO,IAAK,IAAI,EACzD,GAAI,KAAO,MAAQ,KAAM,MAAO,GAChC,IAAI,EAAI,KAAM,EAAI,MAClB,MAAM,KAAO,KAAO,YAAc,IAClC,KAAO,EAAI,MAAQ,WAEjB,GADA,GAAK,WAAW,KAAM,EAAI,KAAO,EAAG,GAAG,GAAK,KAAO,KAAO,GAAK,KAC3D,EAAI,MAAQ,KAAM,OAAO,KAAK,MAAM,EAAI,OAAS,GAAI,EAE3D,MAAO,IACT,CA4BO,aAAM,SAAW,EAmBX,WAAa,IACb,WAAa,EACb,eAAiB,IAGjB,WAAa,IACb,oBAAsB,IAetB,aAAe,cAAgB,UAAY,WAC3C,UAAY,GAEZ,aAAe,CAAE,OAAQ,CAAC,SAAU,WAAY,YAAa,SAAS,EAAG,OAAQ,CAAC,WAAY,cAAc,CAAE,EAGpH,gBAAS,WAAW,KAAM,SAAU,CACzC,MAAM,EAAI,aAAa,IAAI,EAC3B,GAAI,CAAC,EAAG,OAAO,KACf,MAAM,EAAI,KAAK,IAAI,EAAG,OAAO,QAAQ,GAAK,CAAC,EACrC,KAAO,EAAE,CAAC,EAAI,EAAI,EAAE,CAAC,EAAI,IACzB,MAAQ,GAAK,KAAO,EAAI,MAAQ,EAAI,MAAQ,EAAE,CAAC,EACrD,OAAO,KAAK,IAAI,WAAY,EAAE,CAAC,EAAI,EAAE,CAAC,EAAI,KAAK,CACjD,CASO,gBAAS,YAAY,KAAM,OAAQ,CACxC,MAAM,EAAI,aAAa,IAAI,EAC3B,GAAI,CAAC,EAAG,OAAO,KACf,MAAM,EAAI,KAAK,IAAI,EAAG,OAAO,MAAM,GAAK,CAAC,EACzC,GAAI,EAAI,MAAQ,WAAY,MAAO,KACnC,GAAI,EAAI,KAAO,EAAE,CAAC,EAAG,MAAO,GAC5B,GAAI,EAAE,EAAE,CAAC,EAAI,GAAI,MAAO,KACxB,MAAM,OAAS,EAAI,EAAE,CAAC,GAAK,EAAE,CAAC,EAAG,KAAO,EAAE,CAAC,EAAI,EAAI,EAAE,CAAC,EAAI,IAC1D,OAAO,OAAS,KAAO,MAAQ,MAAQ,MAAQ,MAAQ,EAAE,CAAC,CAC5D,CAMO,gBAAS,UAAU,QAAS,KAAM,CAAE,OAAQ,SAAW,GAAK,OAAS,MAAQ,EAAI,CAmBjF,aAAM,kBAAoB,KACpB,gBAAkB,EACxB,gBAAS,cAAc,cAAe,KAAM,CACjD,OAAQ,eAAiB,IAAM,MAAQ,GAAK,kBAAoB,IAClE","names":[],"file":"energy.mjs"}