diff --git a/run.py b/run.py index 687fb10..a4ee2f0 100644 --- a/run.py +++ b/run.py @@ -198,6 +198,11 @@ def main(): now = time.time() for w in workers: # revive any worker that died if w["proc"].poll() is not None and now >= w["next_ok"]: + if w["proc"].returncode == 0: # exit 0 = a deliberate, polite goodbye + if not w.get("retired"): # (missing dep etc.) — don't churn it + w["retired"] = True + print(f" ({os.path.basename(w['path'])} exited cleanly; retired)") + continue print(f" ({os.path.basename(w['path'])} died; restarting)") np = launch(w["path"], w["extra"]) if np: diff --git a/viz/index.html b/viz/index.html index be6790c..49317a1 100644 --- a/viz/index.html +++ b/viz/index.html @@ -241,7 +241,7 @@ .seqnote { border-radius: 2px; background: #78a0ff; box-shadow: 0 0 6px rgba(120,160,255,0.5); pointer-events: none; position: relative; z-index: 2; } .seqgrid.big .seqnote { border-radius: 4px; } - .seqnote .rsz { position: absolute; right: 0; top: 0; bottom: 0; width: 5px; + .seqnote .rsz { position: absolute; right: 0; top: 0; bottom: 0; width: 3px; touch-action: none; cursor: ew-resize; pointer-events: auto; border-radius: 0 2px 2px 0; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4)); } .seqgrid.big .seqnote .rsz { width: 7px; } @@ -1145,12 +1145,12 @@
  • Click a wheel — go inside it (mind mode). In chakra view, click any chakra's core and the camera steps into the wheel: its mandala of petals turns around your whole field of view, and that chakra's feeds circle you — drag to look around the full 360°. A vector console appears under your hands: 12 keys of that wheel's own scale (C minor pentatonic at the root, rising key by key to B hirajoshi at the crown), played by clicking, by the Q] row, or by any connected MIDI keyboard — every note runs through the world's own reverb and grit, so you are jamming inside the instrument, in that chakra's mode, while the planet plays around you. travel the spine wheel to wheel; esc steps back out. And for powerful machines, right-click the sky and set graphics to ultra ✦: every orbiting feed grows a glowing comet trail, and the orbits breathe so the trails weave slow spirals.
  • Skins — the same spine, another cosmology. Right-click the sky and cycle skin. Beyond the seven chakras there is the planes: the Seven Planes & forty-nine sub-planes of the Cosmic Physical Plane from the Alice Bailey / D.K. teachings — I Divine (white light) down through Monadic (violet), Atmic, Buddhic, Mental (where the egoic lotus sits), Astral, to VII Physical (red). The spine becomes the 49-sub-plane spectrum ladder; the side-brackets mark the Monad, the Spiritual Triad and the Personality; and every feed climbs to the plane that matches its rarefaction — the sun's own wind at the Divine, orbits and the sky's geometry at the Monadic, the great cyclic wheels at the Atmic, omen and aspect at the Buddhic, abstract thought and money-as-idea at the Mental, desire and fire and felt sensation at the Astral, and the dense body of the world — weather, quakes, the mortal rates, your own hands — at the Physical. Go inside any plane (mind mode) and its keys play a scale pitched to its altitude: airy whole-tone up at the Divine, grounded minor-pentatonic down at the Physical.
  • One Source, many Spirits, one Matter. That is the whole architecture: the Source is the world's data, the same for everyone; the Spirit is the skin — the belief system through which you read it; the Matter is the sound. Choose your spirit, and the same planet plays through it — literally re-tuned: each tradition carries its own scale, so when you turn the wheel of skins the whole world re-harmonises into that cosmology's key (your grooves and the data's own melodies alike). The chakras sing one way, the Wu Xing another, the runes another still — one planet, many keys. The shelf so far, each rendered from its own published lore, literally and without frills: the Tree of Life (Hermetic Qabalah, Golden Dawn attributions — ten sephirot on three pillars, all twenty-two canonical paths, Mercury's data at Hod, the Moon's at Yesod, the body of the world at Malkut); the Lodge (from published Masonic sources only — Worshipful Master in the East, Wardens West and South, the North dark, Boaz and Jachin at the porch where your own hands stand as the candidate, the Blazing Star, the Volume of the Sacred Law as humanity's open book, and music given to the South, for Beauty holds the arts); the Hermetic ladder of the seven planetary gates, lead to silver; Yggdrasil and its nine worlds, the dwarves keeping the markets in Nidavellir; the Sufi lata'if, where the wind data literally breathes at Ruh, spirit-as-breath; and the Wu Xing, five phases in their generating ring and controlling star, the metal birds of the air routed to Metal; and the Spread — the Celtic Cross of the tarot (Waite's published layout), dealt live and never finished: the heavens in what crowns you, the deep debts and quake-roots in what is beneath you, the retrogrades in what is behind you, births and seasons in what is before you, money and fire in your hopes and fears — and your own hands in the Querent's place, because you are the one asking. Right-click the sky → skin to turn the wheel of traditions. Each map stands as its true figure — the Tree as a tree, the Lodge as a lodge — and every station opens into mind mode with its own scale under your hands. Newest on the shelf: the wheel — the astrologer's own view, twelve signs in a circle with 0° Aries at the ascendant's place, every feed routed to its sign by traditional rulership (wildfires to Aries, your holdings to Taurus, the word to Gemini, the far-travelling planes to Sagittarius…), the live planets riding the rim, your natal chart on the inner ring, and the aspects drawn as chords across the circle — red the squares and oppositions, green the trines and sextiles, gold the conjunctions, dashed where the living sky strikes your natal points. And everywhere else, those same aspects now draw as faint cables between the planets' own spheres in any skin — the sky's geometry made visible in whatever cosmology you're standing in. And every Spirit now speaks its own tongue: the sephirot in Hebrew (כתר, חכמה, בינה…), the chakras in Devanagari (सहस्रार, अनाहत…), the lata'if in Arabic (قلب, روح, سر…), the nine worlds in Old Norse (ásgarðr, miðgarðr, helheimr…), the planetary gates in their glyphs (☽ ☿ ♀ ☉ ♂ ♃ ♄), the five phases in their characters (木 火 土 金 水). The lore, in the letters it was written in. Two more Spirits joined the shelf: the runes — the Elder Futhark, all twenty-four in the Kylver-stone row, ringed as a rune-band, every feed seated by its rune-poem meaning (wealth's strife at ᚠ fehu, Odin's word at ᚨ ansuz, the giant's blow at ᚦ thurisaz for the quakes, need's narrow band at ᚾ nauthiz for hunger and want, the lot-cup ᛈ perthro holding your natal fate, and your raised hands at ᛉ algiz, the warding rune that is itself a figure with arms lifted); and the Wheel of the Year — the eight Celtic festivals at their true solar longitudes (the astronomical reckoning: quarters at the equinoxes and solstices, cross-quarters at fifteen degrees of the fixed signs), deaths and the deep things at Samhain where the veil thins, births at Imbolc, the fires and the dance and the bond at Beltane, the great fair of commerce and chatter at Lughnasadh — with the live sun walking the rim, showing exactly where the year stands today. Honesty note kept from the scholarship: the unified eightfold wheel is a modern synthesis of the attested Irish fire festivals and older solstice customs — true lore, honestly labeled. And newest of all, the first Spirit with no temple: the artist — Kandinsky's Concerning the Spiritual in Art (1911) taken at its word, his colour-sound table rendered as a wireframe easel. Six pigments squeezed round the palette — gelb the trumpet blown louder and louder (the news, the markets' mania), orange the Angelus bell (the clocks, the hours), rot the drumbeat (your heartbeat, the quakes, the flame), grün the violin's placid middle strings (the weather at rest), blau deepening from flute toward the organ (the whole heavenly circle), violett the bassoon (the retrogrades, Saturn, the sorrows) — with weiß, the great silence pregnant with possibility, waiting as the canvas itself (your unpainted chart lives there, and your own hands hold the brush), and schwarz, the pause after which nothing follows, at the palette's edge with the deaths and the debt. His four great contrasts strung dashed between the drops, and as each colour's data sings, its paint strokes itself up onto the canvas — the world painting, live. Labels in German, the tongue he wrote it in. And beside the easel now hangs the bagua — the I Ching's eight trigrams in the King Wen "Later Heaven" ring, south at the top as the old compasses have it, every feed seated by the Shuogua's own attributions: the earthquakes at ☳ zhèn, the trigram that literally means thunder-shock; the word at ☱ duì, for "Dui is the mouth and tongue"; the moon's data at ☵ kǎn, the sun's at ☲ lí; and your own hands at ☶ gèn, for the Shuogua says plainly: Gen is the hand. And at the centre, the oracle never rests: the two loudest trigrams of the moment stack into a live hexagram — the strongest below as the ground of the moment, the second above — drawn line by line in their own colours and named from all sixty-four by the King Wen table, re-cast as the world's weather shifts. When the planet falls silent it settles at hexagram 11, Peace: heaven below, earth above. The book that reads itself, forever. And the newest Spirit is the one with no lore at all — the god's eye — the world's own sensor picture read as the cosmology: a minimal equirectangular Earth with a 30° graticule, and every feed seated at its literal coordinates. The earthquakes flash where the ground actually moved; the ISS and the recon birds sweep as a moving point trailing light; the aircraft are a shimmer band across the busy mid-latitudes; the markets pulse at their financial capitals — New York, London, Tokyo; the solar wind washes the poles as an aurora; the word sparks at random civilisational coordinates; and your own hands are the ring at the venue, wherever on the map you happen to be. A cold watch-floor palette — near-black, cyan, warning amber — and a Bb minor-pentatonic retune, so stepping inside sounds like a surveillance room. Not a belief system but its opposite: the map with nothing hidden. The eye that hears.
  • -
  • The groove grids — sequence the voices. Open any voice's tab (shift-click it) and there's a 16-step grid on the godtime clock. The note voices (lead, bass) get a mini piano roll — eight rows of the scale, click to place a note, click again to clear, and drag a note's right edge to stretch it across the steps — real note length, from a staccato sixteenth to a bar-long hold — and while your groove is on, your melody replaces the data's: bitcoin lets go of the lead and you take it. Every other voice gets an 808-style gate row that rhythmically chops the world's signal — the reverb, the grit, the percussion breathing in your pattern while the planet still decides how hard. Grooves ride the godtime tempo (lock the dial, or let your heart drive it), and they save with your patch. And the sources get the grid too, with a different power: sample & hold — while a source's groove is on, the world's number is only sampled on your lit steps and held between them, so a smooth drifting feed becomes stepped, rhythmic, quantized in time. Bitcoin arpeggiating in your pattern. The wind as a sequence. If an out port is chosen in ⚙, godtime also sends MIDI clock (24 ppqn), so a DAW can slave its tempo to the planet — or to your pulse. And the grids play outward too: with an out port chosen, piano-roll notes go out as real MIDI on the lead and bass channels (each note as long as you drew it), and the mind-mode console plays on its own third channel — so your grooves and your chakra-scale jams drive Ableton, Strudel, or hardware exactly as they drive the built-in synth. The three mouths drink at once: ♪ for the browser synth, the out port for MIDI, both or neither — there is no switch, only where you point the sound.
  • +
  • The groove grids — sequence the voices. Open any voice's tab (shift-click it) and there's a 16-step grid on the godtime clock. The note voices (lead, bass) get a mini piano roll — eight rows of the scale, click to place a note, click again to clear, and drag a note's right edge to stretch it across the steps — real note length, from a staccato sixteenth to a bar-long hold (the built-in synth holds it on the standard pluck voice — 🎸 string and 🎛 GODSONIQ keep their own natural ring — and MIDI out always honors the drawn length) — and while your groove is on, your melody replaces the data's: bitcoin lets go of the lead and you take it. Every other voice gets an 808-style gate row that rhythmically chops the world's signal — the reverb, the grit, the percussion breathing in your pattern while the planet still decides how hard. Grooves ride the godtime tempo (lock the dial, or let your heart drive it), and they save with your patch. And the sources get the grid too, with a different power: sample & hold — while a source's groove is on, the world's number is only sampled on your lit steps and held between them, so a smooth drifting feed becomes stepped, rhythmic, quantized in time. Bitcoin arpeggiating in your pattern. The wind as a sequence. If an out port is chosen in ⚙, godtime also sends MIDI clock (24 ppqn), so a DAW can slave its tempo to the planet — or to your pulse. And the grids play outward too: with an out port chosen, piano-roll notes go out as real MIDI on the lead and bass channels (each note as long as you drew it), and the mind-mode console plays on its own third channel — so your grooves and your chakra-scale jams drive Ableton, Strudel, or hardware exactly as they drive the built-in synth. The three mouths drink at once: ♪ for the browser synth, the out port for MIDI, both or neither — there is no switch, only where you point the sound.
  • Press T — tracks, for making actual songs. A conventional tracks-and-clips view for anyone who finds the cathedral overwhelming: every voice is a track, eight bars run across, and each cell cycles ● live (the world plays that lane) → ABC (clip patterns on the groove grids — A and B come pre-seeded so it makes music immediately) → silent. Double-click any clip to edit its groove in the piano roll. Press and the arrangement loops on the godtime clock — the planet still plays through your song, because live lanes stay wired to the world. Arrangements save with your patch, play out over MIDI like everything else, and export .mid hands you a Standard MIDI File of the note lanes to drag into Ableton, Logic, anything. Not a DAW — a sketchpad where the world sits in on your session. The panel earns its keep like one, too: a 🎛 tracks button sits bottom-right always; drag the panel's top edge to resize (remembered); the top bar tabs between clips and grooves — the grooves tab is a full-size pattern editor (pick a track, pick a letter, big piano roll or 16 steps; double-clicking any clip jumps you straight there); and right-click any track name for that voice's full routing menu — every cable, every level, patch and unpatch without leaving the panel.
  • The Groove — one feel for the whole pattern. The grooves tab carries Ableton's Groove Pool, reduced to the 16-step grid: swing lays the off-beats back, humanize is a touch of random timing so nothing is machine-stiff, velocity lets each hit breathe in loudness, and an amount master scales the lot — with one-click presets (straight, swing 16, MPC 8-4, drunk). Under the roll sit three expression lanes: velocity (how hard each step hits), chance (how likely each step fires at all — drop a step to 40% and it plays four times in ten, so the loop varies itself), and ratchet (how many times a step re-fires, 1–4 — an instant roll or 32nd-note glitch from a single step, and if that step is a Play-One stack each sub-hit re-rolls the note). The feel and the per-step shaping save with your patch. And the texture voices (the filter, the glitch, the reverb, the drive) get their own lane: a ⬗ p-lock, the Pocket-Operator move — pin that voice to a blocky value on any step, so the filter jumps to a different cutoff on every hit, disjointed and alive; drag a step below the lane to hand it back to the world. And any lane takes a ⬢ Euclidean fill — say how many pulses to spread across the steps and they distribute themselves as evenly as possible (E(3,8) is the tresillo, E(5,8) the cinquillo, E(4,16) four-on-the-floor), with steps and rotate to taste: the oldest rhythms of the world fall straight out of the arithmetic.
  • ♪ One scale for the whole planet, and ⬛ Play One for the glitch. Set a root and scale at the top of the grooves tab and every melody you draw is in that key — the piano roll is only ever the scale's own notes, so nothing lands wrong; change the scale and your patterns re-harmonise in place. And a column isn't limited to one note: stack several and it turns blue — a Play One group, from which the sequencer picks a single note at random each time it fires. Stack a handful of in-key notes under a fast Euclidean fill and the line glitches itself into something new on every pass, always in tune. (Adapted from Ableton's Group Notes → Play One.)
  • ⤳ Follow actions — the song composes itself. Give any clip (A/B/C) a follow action in the grooves tab — play again, next, any, any other, stop — with a chance, and once the arrangement is playing that clip can hand off to another at the bar line instead of just repeating. Set two clips to "any other" and they trade the lane back and forth; sprinkle a low-chance divert and the piece drifts. As Ableton's manual puts it, "structures that repeat but can also be surprising… never exactly repeat." A ⤳ marks any clip that can divert. Nothing changes for arrangements without follow actions — they loop exactly as written.
  • -
  • 🥁 The beat — five drums on the godtime clock. Press B (or the 🥁 button) and the beat drawer opens: five lanes — kick, snare, clap, closed hat, open hat — synthesised from oscillators and noise like every other voice, so they ride the world's saturation, crush and reverb too. Click a step to draw; starts all five at once. They live on the same godtime clock as the grooves, and the feel — swing, humanize, velocity, amount — is the very same the tracks use (shared, not copied). The same three expression lanes shape each step (velocity, chance, ratchet), and ⬢ euclid spreads N pulses across the sixteen. Open the 🎛 kit drawer to voice each drum: tune, punch, decay, level, pan — and on the kick, duck: every kick parts the sea, dipping the pads and drone out from under itself so the thump lands in its own air. The beat saves with your patch; a mood never silences it — the world's hands change, but the beat belongs to you; and a beat drawn in zero counts as a build, so leaving zero offers to keep it.
  • +
  • 🥁 The beat — five drums on the godtime clock. Press B (or the 🥁 button) and the beat drawer opens: five lanes — kick, snare, clap, closed hat, open hat — synthesised from oscillators and noise like every other voice, so they ride the world's saturation, crush and reverb too. Click a step to draw; starts all five at once. They live on the same godtime clock as the grooves, and the feel — swing, humanize, velocity, amount — is the very same the tracks use (shared, not copied). The same three expression lanes shape each step (velocity, chance, ratchet), and ⬢ euclid spreads N pulses across the sixteen. Open the 🎛 kit drawer to voice each drum with its own knobs — tune or tone, punch or snap, decay, level, pan, as fits the voice — and on the kick, duck: every kick parts the sea, dipping the pads and drone out from under itself so the thump lands in its own air. The beat saves with your patch; a mood never silences it — the world's hands change, but the beat belongs to you; and a beat drawn in zero counts as a build, so leaving zero offers to keep it.
  • Keys 15 & ⏺ rec — finger-drum the kit. With the beat open the number row is the kit — 1 kick, 2 snare, 3 clap, 4 hat, 5 open hat — played by hand, live, straight into the world's FX. Hold for the ghost note, softer. Arm ⏺ rec and every tap writes itself into the grid, quantized to the nearest sixteenth — tap a hi-hat pattern into a quiet pocket and it's a lane — so you play the beat in rather than clicking it out.
  • 🌾 AMPLER — there is ample, for god is abundant. The SP-404 move, made of the planet. Right-click the sky → 🌾 AMPLER: ⏺ capture a stretch of the sounding world (1.5, 3 or 6 seconds of the live master — and since a fed tab is the world, that YouTube loop counts), then ✂ chop it into 8 or 16 slices. Tap a slice to audition it, to play it backwards, then seat a slice onto a drum voice — and that lane stops synthesising and starts playing the world. The slice rides the voice's own level and pan and, on the kick, its duck; a slice on the open hat still chokes under a closed one — the machine's rules don't care whether a hit is oscillator or sample. Sample anything playing on the machine, chop it, and it's a kit. Session-only for now: the slices don't travel with your patch (yet).
  • The beat leaves the building. The drum lanes ride MIDI out on channel 10 — General MIDI percussion, kick 36, snare 38, clap 39, closed hat 42, open hat 46 — so with an out port chosen in ⚙ they play your hardware and DAW exactly as the note lanes do. And they land in export .mid: draw a beat, hit export, and the Standard MIDI File carries a channel-10 drum track (looped across the arrangement's bars) alongside your lead and bass — drag it into Ableton and the planet's rhythm is already programmed.
  • @@ -1682,12 +1682,16 @@ }; const overlays = []; function startDrag(handle, stp) { - handle.addEventListener("mousedown", (e) => { + // pointer events (mouse AND touch); listeners live on window because + // renderNotes() rebuilds the overlay mid-drag — element capture would die + handle.addEventListener("pointerdown", (e) => { e.preventDefault(); e.stopPropagation(); // the handle never toggles the cell underneath const p = pitch(), startX = e.clientX, start = clamp(Math.round(lenArr[stp] || 1), 1, 16 - stp); const move = (e2) => { lenArr[stp] = clamp(start + Math.round((e2.clientX - startX) / p), 1, 16 - stp); renderNotes(); }; - const up = () => { window.removeEventListener("mousemove", move); window.removeEventListener("mouseup", up); if (onDrop) onDrop(); }; - window.addEventListener("mousemove", move); window.addEventListener("mouseup", up); + const up = () => { window.removeEventListener("pointermove", move); window.removeEventListener("pointerup", up); + window.removeEventListener("pointercancel", up); if (onDrop) onDrop(); }; + window.addEventListener("pointermove", move); window.addEventListener("pointerup", up); + window.addEventListener("pointercancel", up); }); } function renderNotes() { @@ -1891,12 +1895,21 @@ bytes.push(0, 0xff, 0x2f, 0x00); trks.push([bytes]); } - // channel-9 GM drum track — every lit drum step, the beat looped across the arrangement's bars + // channel-9 GM drum track — the beat looped across the arrangement's bars. + // Only lanes the user OWNS export: playing (on), or edited off the factory + // seed — merely opening the 🥁 panel (which seeds a demo pattern) must not + // smuggle a phantom groove into the .mid. Same test zeroHasBuild uses. { const dev = []; + const owned = (v, sq) => { + if (sq.on) return true; + const seed = DRUM_SEED[v]; + for (let i = 0; i < 16; i++) if ((sq.steps[i] ? 1 : 0) !== (seed.indexOf(i) >= 0 ? 1 : 0)) return true; + return false; + }; for (const v of DRUM_VOICES) { const sq = seqs["drum." + v]; - if (!sq) continue; + if (!sq || !owned(v, sq)) continue; const gm = DRUM_GM[v]; for (let b = 0; b < arr.bars; b++) { for (let s2 = 0; s2 < 16; s2++) { @@ -2702,13 +2715,18 @@ rec.title = "record — finger-drum hits (keys 1–5) tap into the grid, quantized to the nearest 16th"; const paintRec = () => rec.classList.toggle("rec-on", beat.rec); paintRec(); - rec.onclick = () => { beat.rec = !beat.rec; paintRec(); }; + rec.onclick = () => { beat.rec = !beat.rec; + // stopped-beat recording anchors step 0 to the arm moment — otherwise taps + // quantize against the free-running (and invisible) clock and land rotated + beat.recAnchor = (beat.rec && !anyOn()) ? seqPhase : 0; + paintRec(); }; const bpmL = document.createElement("span"); bpmL.className = "glbl"; bpmL.style.minWidth = "auto"; bpmL.textContent = "bpm"; const bpm = document.createElement("input"); bpm.type = "number"; bpm.className = "eunum"; bpm.min = "40"; bpm.max = "180"; bpm.value = String(Math.round(godtime.bpm)); - bpm.disabled = godtime.locked; - bpm.title = godtime.locked ? "godtime is locked (🔒) — unlock it on the dial" : "beats per minute (40–180)"; - bpm.onchange = () => { if (!godtime.locked) godtime.bpm = clamp(Math.round(+bpm.value || godtime.bpm), 40, 180); + // always editable — this mirrors the dial's write path. 🔒 lock means "the + // world's hands (heart, tempo.nudge) can't drive tempo", never "the user can't". + bpm.title = "beats per minute (40–180)"; + bpm.onchange = () => { godtime.bpm = clamp(Math.round(+bpm.value || godtime.bpm), 40, 180); bpm.value = String(Math.round(godtime.bpm)); }; const x2 = document.createElement("span"); x2.className = "aclose"; x2.textContent = "×"; x2.onclick = closeBeat; const hint = document.createElement("span"); hint.className = "albl"; @@ -2792,7 +2810,6 @@ paintGrid = () => { const playing = anyOn(); paintPlay(); - if (bpm.disabled !== godtime.locked) bpm.disabled = godtime.locked; const bv = String(Math.round(godtime.bpm)); if (document.activeElement !== bpm && bpm.value !== bv) bpm.value = bv; for (const L of lanes) { L.paintMute(); @@ -2806,9 +2823,14 @@ paintGrid(); beatRefresh = paintGrid; - play.onclick = () => { // master ▶ — flip all five lanes at once - const turnOn = !anyOn(); - lanes.forEach(L => { L.sq.on = turnOn; L.paintMute(); }); + play.onclick = () => { // master ▶/■ — remembers per-lane mutes + if (anyOn()) { // ■ — note who was playing, then stop all + beat.wasOn = lanes.filter(L => L.sq.on).map(L => L.sq); + lanes.forEach(L => { L.sq.on = false; L.paintMute(); }); + } else { // ▶ — restore exactly that set (muted lanes stay muted) + const keep = (beat.wasOn && beat.wasOn.length) ? new Set(beat.wasOn) : null; + lanes.forEach(L => { L.sq.on = keep ? keep.has(L.sq) : true; L.paintMute(); }); + } paintGrid(); }; @@ -2867,7 +2889,7 @@ kitTgl.onclick = () => { beat.kitOpen = !beat.kitOpen; openBeat(); }; kitRow.appendChild(kitTgl); if (beat.kitOpen) { - if (Synth.drumParamList && Synth.drumParam) { + { for (const v of DRUM_VOICES) { const box = document.createElement("div"); box.className = "bvoice"; const vn = document.createElement("div"); vn.className = "bvn"; vn.textContent = v; box.appendChild(vn); @@ -2884,10 +2906,6 @@ box.appendChild(au); kitRow.appendChild(box); } - } else { - const note = document.createElement("span"); note.className = "albl"; - note.textContent = "kit knobs appear once the drum engine (Agent A) is merged in"; - kitRow.appendChild(note); } } body.appendChild(kitRow); @@ -6135,6 +6153,11 @@ const ilat = sraw("sats.iss_lat"), ilon = sraw("sats.iss_lon"); if (ilat != null && ilon != null) { const c = xy(ilon, ilat), tr = godseye.satTrail; + // a stale trail chords straight across the ocean: clear it after an rAF nap + // (backgrounded tab — the ISS moves ~4°/min while we sleep) or a resize + const tnow = now(), rkey = R.x0 + "," + R.y0 + "," + rw; + if (tnow - (godseye._trailT || 0) > 5 || godseye._trailKey !== rkey) tr.length = 0; + godseye._trailT = tnow; godseye._trailKey = rkey; if (!tr.length || Math.hypot(c[0] - tr[tr.length - 1][0], c[1] - tr[tr.length - 1][1]) > 1.5) tr.push(c); if (tr.length > 44) tr.splice(0, tr.length - 44); ctx.strokeStyle = "rgba(120,210,255,0.4)"; ctx.lineWidth = 1; ctx.beginPath(); @@ -7204,7 +7227,7 @@ const name = DRUM_VOICES[di], vel = ev.shiftKey ? 0.5 : 0.9; // ⇧ = the ghost note fireDrum(name, vel, 60000 / Math.max(40, godtime.bpm) / 4 * 0.9); if (beat.rec) { // record: quantize the tap to the nearest 16th, write the step - const step = Math.round(seqPhase) % 16; // frac > .5 rounds to the upcoming 16th (raw grid; swing rides at playback) + const step = ((Math.round(seqPhase - (beat.recAnchor || 0)) % 16) + 16) % 16; // anchored at arm when stopped; raw grid, swing rides at playback const sq = drumSeqFor("drum." + name); sq.steps[step] = 1; sq.vel[step] = vel; if (beatRefresh) beatRefresh(); diff --git a/viz/manual.html b/viz/manual.html index e293c0d..c138099 100644 --- a/viz/manual.html +++ b/viz/manual.html @@ -245,7 +245,7 @@ .seqnote { border-radius: 2px; background: #78a0ff; box-shadow: 0 0 6px rgba(120,160,255,0.5); pointer-events: none; position: relative; z-index: 2; } .seqgrid.big .seqnote { border-radius: 4px; } - .seqnote .rsz { position: absolute; right: 0; top: 0; bottom: 0; width: 5px; + .seqnote .rsz { position: absolute; right: 0; top: 0; bottom: 0; width: 3px; touch-action: none; cursor: ew-resize; pointer-events: auto; border-radius: 0 2px 2px 0; background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4)); } .seqgrid.big .seqnote .rsz { width: 7px; } @@ -1125,12 +1125,12 @@
  • Click a wheel — go inside it (mind mode). In chakra view, click any chakra's core and the camera steps into the wheel: its mandala of petals turns around your whole field of view, and that chakra's feeds circle you — drag to look around the full 360°. A vector console appears under your hands: 12 keys of that wheel's own scale (C minor pentatonic at the root, rising key by key to B hirajoshi at the crown), played by clicking, by the Q] row, or by any connected MIDI keyboard — every note runs through the world's own reverb and grit, so you are jamming inside the instrument, in that chakra's mode, while the planet plays around you. travel the spine wheel to wheel; esc steps back out. And for powerful machines, right-click the sky and set graphics to ultra ✦: every orbiting feed grows a glowing comet trail, and the orbits breathe so the trails weave slow spirals.
  • Skins — the same spine, another cosmology. Right-click the sky and cycle skin. Beyond the seven chakras there is the planes: the Seven Planes & forty-nine sub-planes of the Cosmic Physical Plane from the Alice Bailey / D.K. teachings — I Divine (white light) down through Monadic (violet), Atmic, Buddhic, Mental (where the egoic lotus sits), Astral, to VII Physical (red). The spine becomes the 49-sub-plane spectrum ladder; the side-brackets mark the Monad, the Spiritual Triad and the Personality; and every feed climbs to the plane that matches its rarefaction — the sun's own wind at the Divine, orbits and the sky's geometry at the Monadic, the great cyclic wheels at the Atmic, omen and aspect at the Buddhic, abstract thought and money-as-idea at the Mental, desire and fire and felt sensation at the Astral, and the dense body of the world — weather, quakes, the mortal rates, your own hands — at the Physical. Go inside any plane (mind mode) and its keys play a scale pitched to its altitude: airy whole-tone up at the Divine, grounded minor-pentatonic down at the Physical.
  • One Source, many Spirits, one Matter. That is the whole architecture: the Source is the world's data, the same for everyone; the Spirit is the skin — the belief system through which you read it; the Matter is the sound. Choose your spirit, and the same planet plays through it — literally re-tuned: each tradition carries its own scale, so when you turn the wheel of skins the whole world re-harmonises into that cosmology's key (your grooves and the data's own melodies alike). The chakras sing one way, the Wu Xing another, the runes another still — one planet, many keys. The shelf so far, each rendered from its own published lore, literally and without frills: the Tree of Life (Hermetic Qabalah, Golden Dawn attributions — ten sephirot on three pillars, all twenty-two canonical paths, Mercury's data at Hod, the Moon's at Yesod, the body of the world at Malkut); the Lodge (from published Masonic sources only — Worshipful Master in the East, Wardens West and South, the North dark, Boaz and Jachin at the porch where your own hands stand as the candidate, the Blazing Star, the Volume of the Sacred Law as humanity's open book, and music given to the South, for Beauty holds the arts); the Hermetic ladder of the seven planetary gates, lead to silver; Yggdrasil and its nine worlds, the dwarves keeping the markets in Nidavellir; the Sufi lata'if, where the wind data literally breathes at Ruh, spirit-as-breath; and the Wu Xing, five phases in their generating ring and controlling star, the metal birds of the air routed to Metal; and the Spread — the Celtic Cross of the tarot (Waite's published layout), dealt live and never finished: the heavens in what crowns you, the deep debts and quake-roots in what is beneath you, the retrogrades in what is behind you, births and seasons in what is before you, money and fire in your hopes and fears — and your own hands in the Querent's place, because you are the one asking. Right-click the sky → skin to turn the wheel of traditions. Each map stands as its true figure — the Tree as a tree, the Lodge as a lodge — and every station opens into mind mode with its own scale under your hands. Newest on the shelf: the wheel — the astrologer's own view, twelve signs in a circle with 0° Aries at the ascendant's place, every feed routed to its sign by traditional rulership (wildfires to Aries, your holdings to Taurus, the word to Gemini, the far-travelling planes to Sagittarius…), the live planets riding the rim, your natal chart on the inner ring, and the aspects drawn as chords across the circle — red the squares and oppositions, green the trines and sextiles, gold the conjunctions, dashed where the living sky strikes your natal points. And everywhere else, those same aspects now draw as faint cables between the planets' own spheres in any skin — the sky's geometry made visible in whatever cosmology you're standing in. And every Spirit now speaks its own tongue: the sephirot in Hebrew (כתר, חכמה, בינה…), the chakras in Devanagari (सहस्रार, अनाहत…), the lata'if in Arabic (قلب, روح, سر…), the nine worlds in Old Norse (ásgarðr, miðgarðr, helheimr…), the planetary gates in their glyphs (☽ ☿ ♀ ☉ ♂ ♃ ♄), the five phases in their characters (木 火 土 金 水). The lore, in the letters it was written in. Two more Spirits joined the shelf: the runes — the Elder Futhark, all twenty-four in the Kylver-stone row, ringed as a rune-band, every feed seated by its rune-poem meaning (wealth's strife at ᚠ fehu, Odin's word at ᚨ ansuz, the giant's blow at ᚦ thurisaz for the quakes, need's narrow band at ᚾ nauthiz for hunger and want, the lot-cup ᛈ perthro holding your natal fate, and your raised hands at ᛉ algiz, the warding rune that is itself a figure with arms lifted); and the Wheel of the Year — the eight Celtic festivals at their true solar longitudes (the astronomical reckoning: quarters at the equinoxes and solstices, cross-quarters at fifteen degrees of the fixed signs), deaths and the deep things at Samhain where the veil thins, births at Imbolc, the fires and the dance and the bond at Beltane, the great fair of commerce and chatter at Lughnasadh — with the live sun walking the rim, showing exactly where the year stands today. Honesty note kept from the scholarship: the unified eightfold wheel is a modern synthesis of the attested Irish fire festivals and older solstice customs — true lore, honestly labeled. And newest of all, the first Spirit with no temple: the artist — Kandinsky's Concerning the Spiritual in Art (1911) taken at its word, his colour-sound table rendered as a wireframe easel. Six pigments squeezed round the palette — gelb the trumpet blown louder and louder (the news, the markets' mania), orange the Angelus bell (the clocks, the hours), rot the drumbeat (your heartbeat, the quakes, the flame), grün the violin's placid middle strings (the weather at rest), blau deepening from flute toward the organ (the whole heavenly circle), violett the bassoon (the retrogrades, Saturn, the sorrows) — with weiß, the great silence pregnant with possibility, waiting as the canvas itself (your unpainted chart lives there, and your own hands hold the brush), and schwarz, the pause after which nothing follows, at the palette's edge with the deaths and the debt. His four great contrasts strung dashed between the drops, and as each colour's data sings, its paint strokes itself up onto the canvas — the world painting, live. Labels in German, the tongue he wrote it in. And beside the easel now hangs the bagua — the I Ching's eight trigrams in the King Wen "Later Heaven" ring, south at the top as the old compasses have it, every feed seated by the Shuogua's own attributions: the earthquakes at ☳ zhèn, the trigram that literally means thunder-shock; the word at ☱ duì, for "Dui is the mouth and tongue"; the moon's data at ☵ kǎn, the sun's at ☲ lí; and your own hands at ☶ gèn, for the Shuogua says plainly: Gen is the hand. And at the centre, the oracle never rests: the two loudest trigrams of the moment stack into a live hexagram — the strongest below as the ground of the moment, the second above — drawn line by line in their own colours and named from all sixty-four by the King Wen table, re-cast as the world's weather shifts. When the planet falls silent it settles at hexagram 11, Peace: heaven below, earth above. The book that reads itself, forever. And the newest Spirit is the one with no lore at all — the god's eye — the world's own sensor picture read as the cosmology: a minimal equirectangular Earth with a 30° graticule, and every feed seated at its literal coordinates. The earthquakes flash where the ground actually moved; the ISS and the recon birds sweep as a moving point trailing light; the aircraft are a shimmer band across the busy mid-latitudes; the markets pulse at their financial capitals — New York, London, Tokyo; the solar wind washes the poles as an aurora; the word sparks at random civilisational coordinates; and your own hands are the ring at the venue, wherever on the map you happen to be. A cold watch-floor palette — near-black, cyan, warning amber — and a Bb minor-pentatonic retune, so stepping inside sounds like a surveillance room. Not a belief system but its opposite: the map with nothing hidden. The eye that hears.
  • -
  • The groove grids — sequence the voices. Open any voice's tab (shift-click it) and there's a 16-step grid on the godtime clock. The note voices (lead, bass) get a mini piano roll — eight rows of the scale, click to place a note, click again to clear, and drag a note's right edge to stretch it across the steps — real note length, from a staccato sixteenth to a bar-long hold — and while your groove is on, your melody replaces the data's: bitcoin lets go of the lead and you take it. Every other voice gets an 808-style gate row that rhythmically chops the world's signal — the reverb, the grit, the percussion breathing in your pattern while the planet still decides how hard. Grooves ride the godtime tempo (lock the dial, or let your heart drive it), and they save with your patch. And the sources get the grid too, with a different power: sample & hold — while a source's groove is on, the world's number is only sampled on your lit steps and held between them, so a smooth drifting feed becomes stepped, rhythmic, quantized in time. Bitcoin arpeggiating in your pattern. The wind as a sequence. If an out port is chosen in ⚙, godtime also sends MIDI clock (24 ppqn), so a DAW can slave its tempo to the planet — or to your pulse. And the grids play outward too: with an out port chosen, piano-roll notes go out as real MIDI on the lead and bass channels (each note as long as you drew it), and the mind-mode console plays on its own third channel — so your grooves and your chakra-scale jams drive Ableton, Strudel, or hardware exactly as they drive the built-in synth. The three mouths drink at once: ♪ for the browser synth, the out port for MIDI, both or neither — there is no switch, only where you point the sound.
  • +
  • The groove grids — sequence the voices. Open any voice's tab (shift-click it) and there's a 16-step grid on the godtime clock. The note voices (lead, bass) get a mini piano roll — eight rows of the scale, click to place a note, click again to clear, and drag a note's right edge to stretch it across the steps — real note length, from a staccato sixteenth to a bar-long hold (the built-in synth holds it on the standard pluck voice — 🎸 string and 🎛 GODSONIQ keep their own natural ring — and MIDI out always honors the drawn length) — and while your groove is on, your melody replaces the data's: bitcoin lets go of the lead and you take it. Every other voice gets an 808-style gate row that rhythmically chops the world's signal — the reverb, the grit, the percussion breathing in your pattern while the planet still decides how hard. Grooves ride the godtime tempo (lock the dial, or let your heart drive it), and they save with your patch. And the sources get the grid too, with a different power: sample & hold — while a source's groove is on, the world's number is only sampled on your lit steps and held between them, so a smooth drifting feed becomes stepped, rhythmic, quantized in time. Bitcoin arpeggiating in your pattern. The wind as a sequence. If an out port is chosen in ⚙, godtime also sends MIDI clock (24 ppqn), so a DAW can slave its tempo to the planet — or to your pulse. And the grids play outward too: with an out port chosen, piano-roll notes go out as real MIDI on the lead and bass channels (each note as long as you drew it), and the mind-mode console plays on its own third channel — so your grooves and your chakra-scale jams drive Ableton, Strudel, or hardware exactly as they drive the built-in synth. The three mouths drink at once: ♪ for the browser synth, the out port for MIDI, both or neither — there is no switch, only where you point the sound.
  • Press T — tracks, for making actual songs. A conventional tracks-and-clips view for anyone who finds the cathedral overwhelming: every voice is a track, eight bars run across, and each cell cycles ● live (the world plays that lane) → ABC (clip patterns on the groove grids — A and B come pre-seeded so it makes music immediately) → silent. Double-click any clip to edit its groove in the piano roll. Press and the arrangement loops on the godtime clock — the planet still plays through your song, because live lanes stay wired to the world. Arrangements save with your patch, play out over MIDI like everything else, and export .mid hands you a Standard MIDI File of the note lanes to drag into Ableton, Logic, anything. Not a DAW — a sketchpad where the world sits in on your session. The panel earns its keep like one, too: a 🎛 tracks button sits bottom-right always; drag the panel's top edge to resize (remembered); the top bar tabs between clips and grooves — the grooves tab is a full-size pattern editor (pick a track, pick a letter, big piano roll or 16 steps; double-clicking any clip jumps you straight there); and right-click any track name for that voice's full routing menu — every cable, every level, patch and unpatch without leaving the panel.
  • The Groove — one feel for the whole pattern. The grooves tab carries Ableton's Groove Pool, reduced to the 16-step grid: swing lays the off-beats back, humanize is a touch of random timing so nothing is machine-stiff, velocity lets each hit breathe in loudness, and an amount master scales the lot — with one-click presets (straight, swing 16, MPC 8-4, drunk). Under the roll sit three expression lanes: velocity (how hard each step hits), chance (how likely each step fires at all — drop a step to 40% and it plays four times in ten, so the loop varies itself), and ratchet (how many times a step re-fires, 1–4 — an instant roll or 32nd-note glitch from a single step, and if that step is a Play-One stack each sub-hit re-rolls the note). The feel and the per-step shaping save with your patch. And the texture voices (the filter, the glitch, the reverb, the drive) get their own lane: a ⬗ p-lock, the Pocket-Operator move — pin that voice to a blocky value on any step, so the filter jumps to a different cutoff on every hit, disjointed and alive; drag a step below the lane to hand it back to the world. And any lane takes a ⬢ Euclidean fill — say how many pulses to spread across the steps and they distribute themselves as evenly as possible (E(3,8) is the tresillo, E(5,8) the cinquillo, E(4,16) four-on-the-floor), with steps and rotate to taste: the oldest rhythms of the world fall straight out of the arithmetic.
  • ♪ One scale for the whole planet, and ⬛ Play One for the glitch. Set a root and scale at the top of the grooves tab and every melody you draw is in that key — the piano roll is only ever the scale's own notes, so nothing lands wrong; change the scale and your patterns re-harmonise in place. And a column isn't limited to one note: stack several and it turns blue — a Play One group, from which the sequencer picks a single note at random each time it fires. Stack a handful of in-key notes under a fast Euclidean fill and the line glitches itself into something new on every pass, always in tune. (Adapted from Ableton's Group Notes → Play One.)
  • ⤳ Follow actions — the song composes itself. Give any clip (A/B/C) a follow action in the grooves tab — play again, next, any, any other, stop — with a chance, and once the arrangement is playing that clip can hand off to another at the bar line instead of just repeating. Set two clips to "any other" and they trade the lane back and forth; sprinkle a low-chance divert and the piece drifts. As Ableton's manual puts it, "structures that repeat but can also be surprising… never exactly repeat." A ⤳ marks any clip that can divert. Nothing changes for arrangements without follow actions — they loop exactly as written.
  • -
  • 🥁 The beat — five drums on the godtime clock. Press B (or the 🥁 button) and the beat drawer opens: five lanes — kick, snare, clap, closed hat, open hat — synthesised from oscillators and noise like every other voice, so they ride the world's saturation, crush and reverb too. Click a step to draw; starts all five at once. They live on the same godtime clock as the grooves, and the feel — swing, humanize, velocity, amount — is the very same the tracks use (shared, not copied). The same three expression lanes shape each step (velocity, chance, ratchet), and ⬢ euclid spreads N pulses across the sixteen. Open the 🎛 kit drawer to voice each drum: tune, punch, decay, level, pan — and on the kick, duck: every kick parts the sea, dipping the pads and drone out from under itself so the thump lands in its own air. The beat saves with your patch; a mood never silences it — the world's hands change, but the beat belongs to you; and a beat drawn in zero counts as a build, so leaving zero offers to keep it.
  • +
  • 🥁 The beat — five drums on the godtime clock. Press B (or the 🥁 button) and the beat drawer opens: five lanes — kick, snare, clap, closed hat, open hat — synthesised from oscillators and noise like every other voice, so they ride the world's saturation, crush and reverb too. Click a step to draw; starts all five at once. They live on the same godtime clock as the grooves, and the feel — swing, humanize, velocity, amount — is the very same the tracks use (shared, not copied). The same three expression lanes shape each step (velocity, chance, ratchet), and ⬢ euclid spreads N pulses across the sixteen. Open the 🎛 kit drawer to voice each drum with its own knobs — tune or tone, punch or snap, decay, level, pan, as fits the voice — and on the kick, duck: every kick parts the sea, dipping the pads and drone out from under itself so the thump lands in its own air. The beat saves with your patch; a mood never silences it — the world's hands change, but the beat belongs to you; and a beat drawn in zero counts as a build, so leaving zero offers to keep it.
  • Keys 15 & ⏺ rec — finger-drum the kit. With the beat open the number row is the kit — 1 kick, 2 snare, 3 clap, 4 hat, 5 open hat — played by hand, live, straight into the world's FX. Hold for the ghost note, softer. Arm ⏺ rec and every tap writes itself into the grid, quantized to the nearest sixteenth — tap a hi-hat pattern into a quiet pocket and it's a lane — so you play the beat in rather than clicking it out.
  • 🌾 AMPLER — there is ample, for god is abundant. The SP-404 move, made of the planet. Right-click the sky → 🌾 AMPLER: ⏺ capture a stretch of the sounding world (1.5, 3 or 6 seconds of the live master — and since a fed tab is the world, that YouTube loop counts), then ✂ chop it into 8 or 16 slices. Tap a slice to audition it, to play it backwards, then seat a slice onto a drum voice — and that lane stops synthesising and starts playing the world. The slice rides the voice's own level and pan and, on the kick, its duck; a slice on the open hat still chokes under a closed one — the machine's rules don't care whether a hit is oscillator or sample. Sample anything playing on the machine, chop it, and it's a kit. Session-only for now: the slices don't travel with your patch (yet).
  • The beat leaves the building. The drum lanes ride MIDI out on channel 10 — General MIDI percussion, kick 36, snare 38, clap 39, closed hat 42, open hat 46 — so with an out port chosen in ⚙ they play your hardware and DAW exactly as the note lanes do. And they land in export .mid: draw a beat, hit export, and the Standard MIDI File carries a channel-10 drum track (looped across the arrangement's bars) alongside your lead and bass — drag it into Ableton and the planet's rhythm is already programmed.
  • diff --git a/workers/world_planes.py b/workers/world_planes.py index b7825a6..bf907f0 100644 --- a/workers/world_planes.py +++ b/workers/world_planes.py @@ -172,14 +172,14 @@ def main(): print(f"[{NAME}] HTTP 429 rate limited; backing off to {poll:.0f}s") else: print(f"[{NAME}] HTTP {ex.code}; retrying in {poll:.0f}s") - _stale = read_cache(None) # 429 -> a stale cache beats nothing - if _stale is not None: + _stale = read_cache(3600) # 429 -> a stale cache beats nothing, + if _stale is not None: # but a day-old sky beats neither last = reduce_states(filter_states_bbox(_stale[0], args.bbox)) print(f"[{NAME}] serving stale cache ({_stale[1]:.0f}s old)") except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError, ValueError) as ex: print(f"[{NAME}] fetch error: {ex}; retrying in {poll:.0f}s") - _stale = read_cache(None) # network down -> fall back to a stale cache + _stale = read_cache(3600) # network down -> fall back, bounded 1h if _stale is not None: last = reduce_states(filter_states_bbox(_stale[0], args.bbox)) print(f"[{NAME}] serving stale cache ({_stale[1]:.0f}s old)") diff --git a/workers/world_sats.py b/workers/world_sats.py index 4880077..b1a4e51 100644 --- a/workers/world_sats.py +++ b/workers/world_sats.py @@ -142,11 +142,13 @@ def load_grouped_tles(force_fetch=False): print(f"[{NAME}] using cached TLEs (< 24 h old)") return groups merged, recon_catnrs = [], set() + any_failed = False for url, is_recon in TLE_SOURCES: try: txt = fetch_url(url) except (urllib.error.URLError, OSError, ValueError) as e: print(f"[{NAME}] TLE fetch failed for {url.split('?')[-1]}: {e}") + any_failed = True continue for name, l1, l2 in parse_tles(txt): catnr = l1[2:7].strip() @@ -154,6 +156,14 @@ def load_grouped_tles(force_fetch=False): recon_catnrs.add(catnr) merged.append((name, l1, l2, is_recon)) if merged: + if any_failed: # partial fetch: backfill the failed groups + have = {l1[2:7].strip() for _n, l1, _l2, _r in merged} # from the old cache so a + old, _old_recon = _read_cache() # half-set never clobbers it + for name, l1, l2, is_recon in old: + if l1[2:7].strip() not in have: + merged.append((name, l1, l2, is_recon)) + if is_recon: + recon_catnrs.add(l1[2:7].strip()) _write_cache(merged, recon_catnrs) return merged groups, _ = _read_cache() # network down — serve stale @@ -247,8 +257,8 @@ def main(): ap.add_argument("--host", default="127.0.0.1") ap.add_argument("--port", type=int, default=9000) ap.add_argument("--interval", type=float, default=2.0) - ap.add_argument("--lat", type=float, default=0.0, help="venue latitude (run.py retargets this)") - ap.add_argument("--lon", type=float, default=0.0, help="venue longitude") + ap.add_argument("--lat", type=float, default=-27.47, help="venue latitude (run.py retargets this; default matches world_sky's Brisbane)") + ap.add_argument("--lon", type=float, default=153.02, help="venue longitude") args = ap.parse_args() if not HAVE_SGP4: @@ -257,8 +267,12 @@ def main(): sats = build_sats(load_grouped_tles()) if not sats: - print(f"[{NAME}] no TLEs (no cache and no network) — exiting politely.") - sys.exit(0) + # nap before exiting nonzero: run.py revives dead workers ~10s later, and + # without this pause a downed network would mean a Celestrak attempt every + # revive. 15 min keeps us polite and self-healing. + print(f"[{NAME}] no TLEs (no cache and no network) — napping 15 min, then retrying via the supervisor.") + time.sleep(900) + sys.exit(1) client = SimpleUDPClient(args.host, args.port) n_recon = sum(1 for s in sats if s.is_recon) @@ -272,6 +286,9 @@ def main(): last_refresh = time.time() fresh = build_sats(load_grouped_tles()) # refetches only if the cache is stale if fresh: + prev = {s.catnr: s.prev_elev for s in sats} # carry pass-detection state so a + for s in fresh: # refresh mid-pass can't eat the event + s.prev_elev = prev.get(s.catnr) sats = fresh have_recon = any(s.is_recon for s in sats) diff --git a/workers/world_sky.py b/workers/world_sky.py index 290ed16..f8c02f5 100644 --- a/workers/world_sky.py +++ b/workers/world_sky.py @@ -11,6 +11,8 @@ Emits: /gs/sky/elev solar elevation in degrees (-90 night .. +90 overhead) /gs/sky/day 0..1 smooth day/night (0 deep night, 1 broad daylight) /gs/sky/az solar azimuth in degrees (0=N, 90=E, 180=S, 270=W) + /gs/sky/lat the venue's own latitude (so the console can place + /gs/sky/lon the venue's own longitude "here" on the god's eye map) """ from __future__ import annotations @@ -69,7 +71,7 @@ def main(): args = ap.parse_args() client = SimpleUDPClient(args.host, args.port) - print(f"[sky] emitting /gs/sky/elev|day|az for ({args.lat},{args.lon}) " + print(f"[sky] emitting /gs/sky/elev|day|az|lat|lon for ({args.lat},{args.lon}) " f"every {args.interval:g}s") while True: