From eaba76edcac9bb8cc94dae8e4089d6018c54b5de Mon Sep 17 00:00:00 2001 From: type-two Date: Sun, 5 Jul 2026 19:35:03 +1000 Subject: [PATCH] =?UTF-8?q?viz:=20skin=20system=20=E2=80=94=208=20traditio?= =?UTF-8?q?ns=20on=20one=20engine=20(Source=20/=20Spirit=20/=20Matter)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A skin = 7-10 stations + source->station mapper + per-station scale, spine or true 2D map (positioned stations + canonical connective paths). All engine features (mind mode, vector console, trails, patching) work inside every skin. - planes: Bailey/D.K. Seven Planes & 49 sub-planes, spectrum-ladder spine, Monad / Spiritual Triad / Personality brackets (spec adversarially verified) - tree: Hermetic Qabalah Tree of Life (Golden Dawn attributions, stated), ten sephirot on three pillars, all 22 canonical paths - lodge: Craft Freemasonry from published sources only — officers E/W/S, dark North, Boaz & Jachin at the porch (the player's hands = the candidate) - ladder: Hermetic seven planetary gates; yggdrasil: the nine worlds; lataif: Naqshbandi six subtle centres (wind breathes at Ruh); wuxing: Five Phases, sheng ring + dashed ke star - launch.json: self-contained godstrument-test sandbox entry Co-Authored-By: Claude Fable 5 --- .claude/launch.json | 6 + viz/index.html | 477 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 442 insertions(+), 41 deletions(-) diff --git a/.claude/launch.json b/.claude/launch.json index 836c9d0..581efe8 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -24,6 +24,12 @@ "runtimeExecutable": "python3", "runtimeArgs": ["-m", "http.server", "8899", "--directory", "viz"], "port": 8899 + }, + { + "name": "godstrument-test", + "runtimeExecutable": "sh", + "runtimeArgs": ["-c", "python3 -c \"import json; c=json.load(open('config.json')); c['ws_port']=8790; c['http_port']=8890; json.dump(c, open('/tmp/gs_test_config.json','w'))\" && .venv/bin/python run.py --no-browser --profile minimal --config /tmp/gs_test_config.json"], + "port": 8890 } ] } diff --git a/viz/index.html b/viz/index.html index 4d1a451..dae3201 100644 --- a/viz/index.html +++ b/viz/index.html @@ -751,6 +751,8 @@
  • Press C — chakra view. The sources leave their column and take up their stations on the subtle body: seven wheels on a spine of light, crown to root, each feed orbiting the chakra it belongs to — the sky's machinery at the crown, the omens at the third eye, the species talking at the throat, weather and air at the heart, fire and money at the solar plexus, birth and water at the sacral, quakes and debt and the mortal weight at the root. Each wheel glows with the live average of its members and spins its traditional petals, counter-rotating wheel to wheel. Drag empty space to spin the whole body 360°, drag up/down to tilt, flick and let momentum carry it. Everything still works in there: click a node to mute it, drag it onto a voice to patch, shift-click for its tab. C again and the sources glide home.
  • Right-click anything — the everything menu. Every node carries its full settings under the right button: mute, solo, a level fader (0–2×), a trim offset, every cable it carries with its amount slider and an unpatch ×, and a nested patch to → list for laying new cables without dragging. Right-click the empty sky for the view menu. And the modifiers, for the fast hands: click mutes · ⇧-click opens the node's tab · ⇧-drag rides the level up and down · ⌥-click solos a source · ⌘-click multi-selects for grouping · ⌘⇧-click resets a node to neutral. People play differently; the instrument shouldn't care which hand you reach with.
  • 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. 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. 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.
  • @@ -1867,7 +1869,7 @@ function enterMind(c) { mindChakra = c; - const cs = CHAKRA_SCALES[c.key], steps = SCALES[cs.scale]; + const cs = activeSkin.scaleOf(c.key), steps = SCALES[cs.scale]; mindKeys.notes = []; for (let i = 0; i < 12; i++) mindKeys.notes.push(cs.root + 12 * Math.floor(i / steps.length) + steps[i % steps.length]); @@ -1913,10 +1915,318 @@ return familyOf(key) === "body" ? "root" : "heart"; } + // --------------------------------------------------------------------------- + // Skins — the same 7-station spine read through a different lens. "chakra" is + // the subtle body; "planes" (registered below) is the Bailey / D.K. map of + // consciousness. A skin is only: 7 stations (top->bottom), a source->station + // mapper, a per-station {root,scale}, and optional grouping brackets. The + // spine, orbits, mind mode, the vector console and trails are all shared. + // --------------------------------------------------------------------------- + // ---- the Bailey / D.K. "planes of consciousness" skin ----------------------- + // The Seven Planes & 49 sub-planes of the Cosmic Physical Plane (Alice Bailey / + // Djwhal Khul). Spec cross-checked against the Theosophical corpus. Same spine, + // read as the ladder of consciousness: I Divine (white light) down to VII + // Physical (red). Colours follow the descending spectrum of the user's chart. + const BAILEY = [ // I → VII, most rarefied → densest + { key: "divine", label: "I · divine · adi", petals: 1, col: [238, 240, 250] }, + { key: "monadic", label: "II · monadic · anupadaka", petals: 3, col: [150, 80, 255] }, + { key: "atmic", label: "III · spiritual · atmic", petals: 5, col: [70, 110, 255] }, + { key: "buddhic", label: "IV · intuitional · buddhic", petals: 8, col: [40, 220, 200] }, + { key: "mental", label: "V · mental · egoic lotus", petals: 12, col: [240, 225, 70] }, + { key: "astral", label: "VI · emotional · astral", petals: 10, col: [255, 150, 60] }, + { key: "physical", label: "VII · physical · sthula", petals: 7, col: [255, 85, 85] }, + ]; + const BAILEY_BY_KEY = {}; + for (const c of BAILEY) BAILEY_BY_KEY[c.key] = c; + + const BAILEY_SCALES = { // higher planes airier & higher-pitched + divine: { root: 84, scale: "wholetone" }, + monadic: { root: 77, scale: "lydian" }, + atmic: { root: 72, scale: "pentatonic" }, + buddhic: { root: 65, scale: "major" }, + mental: { root: 60, scale: "mixolydian" }, + astral: { root: 53, scale: "dorian" }, + physical: { root: 45, scale: "minor_pent" }, + }; + + // the three side-bracket groupings from the chart (station indices, 0-based; + // Mental sits in both — its abstract mind is Triad, its concrete mind Personality) + const BAILEY_GROUPS = [ + { name: "MONAD", from: 1, to: 1 }, + { name: "SPIRITUAL TRIAD", from: 2, to: 4 }, + { name: "PERSONALITY", from: 4, to: 6 }, + ]; + + // source → plane by "rarefaction", per the verified mapping + function baileyOf(key) { + const k = (key || "").toLowerCase(), p = k.split(".")[0]; + if (p === "sun" && !/elev/.test(k)) return "divine"; // I · the star's engine + if (p === "iss" || p === "sky" || /_lon/.test(k) || // II · celestial machinery + /^astro\.(saturn|mars|jupiter|venus|mercury)$/.test(k)) return "monadic"; + if (k === "astro.moon" || /zodiac|season/.test(k) || // III · the great cyclic wheels + k === "almanac.dec" || k === "almanac.element") return "atmic"; + if (p === "astro" || /aspect|harmon|tension|retro|fertil|aurora/.test(k)) return "buddhic"; // IV · omen & aspect + if (["wiki","net","news","social","edit","econ","debt"].includes(p) || // V · abstract thought & money-as-idea + /inflation/.test(k) || k === "market.turnover" || k === "crypto.price") return "mental"; + if (/vel$|velocity/.test(k) || // VI · desire, fire, felt sensation + ["fire","crypto","market","light","ambient","audio","mic"].includes(p)) return "astral"; + return "physical"; // VII · the dense body of the world + } + + // --------------------------------------------------------------------------- + // The wider skins — one Source, many Spirits, one Matter. Each tradition is + // rendered from its own published lore, literally and without frills. Spec + // sheets researched + cross-checked against the canonical sources. + // pos = [x,y] in the map frame (maps stand as their true figures, not spines). + // --------------------------------------------------------------------------- + + // THE TREE OF LIFE — Hermetic Qabalah (Golden Dawn attributions, stated as + // such). Three pillars: Mercy right, Severity left, Equilibrium the middle. + // The 22 paths below are the canonical set, all of them, none invented. + const TREE = [ + { key: "keter", label: "keter · crown", petals: 1, col: [255, 255, 255], pos: [0.50, 0.00] }, + { key: "chokmah", label: "chokmah · wisdom", petals: 2, col: [150, 154, 160], pos: [0.84, 0.12] }, + { key: "binah", label: "binah · understanding", petals: 3, col: [70, 66, 92], pos: [0.16, 0.12] }, + { key: "chesed", label: "chesed · mercy", petals: 4, col: [60, 110, 230], pos: [0.84, 0.38] }, + { key: "gevurah", label: "gevurah · severity", petals: 5, col: [210, 40, 40], pos: [0.16, 0.38] }, + { key: "tiferet", label: "tiferet · beauty", petals: 6, col: [255, 210, 60], pos: [0.50, 0.50] }, + { key: "netzach", label: "netzach · victory", petals: 7, col: [40, 190, 100], pos: [0.84, 0.64] }, + { key: "hod", label: "hod · splendour", petals: 8, col: [240, 140, 40], pos: [0.16, 0.64] }, + { key: "yesod", label: "yesod · foundation", petals: 9, col: [140, 70, 220], pos: [0.50, 0.78] }, + { key: "malkut", label: "malkut · kingdom", petals: 10, col: [140, 120, 60], pos: [0.50, 0.98] }, + ]; + const TREE_PATHS = [[0,1],[0,2],[0,5],[1,2],[1,5],[1,3],[2,5],[2,4],[3,4],[3,5],[3,6], + [4,5],[4,7],[5,6],[5,7],[5,8],[6,7],[6,8],[6,9],[7,8],[7,9],[8,9]]; + const TREE_SCALES = { + keter: { root: 72, scale: "wholetone" }, chokmah: { root: 64, scale: "pentatonic" }, + binah: { root: 60, scale: "minor" }, chesed: { root: 57, scale: "lydian" }, + gevurah: { root: 55, scale: "phrygian" }, tiferet: { root: 53, scale: "major" }, + netzach: { root: 48, scale: "dorian" }, hod: { root: 46, scale: "mixolydian" }, + yesod: { root: 41, scale: "phrygian" }, malkut: { root: 36, scale: "minor" }, + }; + function treeOf(key) { + const k = (key || "").toLowerCase(), p = k.split(".")[0]; + if (k === "sun.speed") return "keter"; // the first swirlings + if (/sun_lon|zodiac/.test(k) || p === "iss" || k === "clock.births" || k === "clock.popvel") return "chokmah"; + if (k === "astro.saturn" || p === "debt" || /inflation/.test(k)) return "binah"; // Saturn: time, weight, form + if (k === "world.lifeexp" || k === "world.poverty" || k === "astro.harmony") return "chesed"; + if (k === "astro.mars" || p === "fire" || k === "quake.event" || k === "astro.tension" || k === "world.hunger") return "gevurah"; + if (/^sky\./.test(k)) return "tiferet"; // the solar heart + if (/season|almanac\.(element|fertile)/.test(k)) return "netzach"; // Venus: nature's rhythms + if (["wiki","market","crypto","planes","econ"].includes(p) || /retro/.test(k)) return "hod"; // Mercury: mind & number + if (/^astro\.moon|almanac\.dec|ambient|light\./.test(k)) return "yesod"; // Levanah: the astral machinery + return "malkut"; // the Kingdom: body & world + } + + // THE LODGE — Craft Freemasonry, from published sources only (Mackey, + // Duncan's): the three principal officers, the two pillars of the porch, the + // Lesser Lights' triangle, the Blazing Star on the mosaic pavement, the VSL. + // East at top. The player's own hands stand at the porch, between the pillars. + const LODGE = [ + { key: "east", label: "the east · worshipful master · wisdom", petals: 3, col: [255, 204, 60], pos: [0.50, 0.02] }, + { key: "west", label: "the west · senior warden · strength", petals: 3, col: [205, 100, 30], pos: [0.50, 0.74] }, + { key: "south", label: "the south · junior warden · beauty", petals: 3, col: [255, 238, 150], pos: [0.90, 0.38] }, + { key: "north", label: "the north · place of darkness", petals: 3, col: [55, 62, 95], pos: [0.10, 0.38] }, + { key: "boaz", label: "boaz · strength", petals: 2, col: [176, 141, 87], pos: [0.32, 0.96] }, + { key: "jachin", label: "jachin · establishment", petals: 2, col: [205, 175, 115], pos: [0.68, 0.96] }, + { key: "star", label: "the blazing star", petals: 5, col: [255, 250, 220], pos: [0.50, 0.30] }, + { key: "altar", label: "the altar · volume of the sacred law", petals: 3, col: [240, 240, 245], pos: [0.50, 0.46] }, + { key: "pavement", label: "the mosaic pavement", petals: 4, col: [150, 150, 155], pos: [0.50, 0.60] }, + ]; + const LODGE_PATHS = [[0,2],[2,1],[1,3],[3,0],[4,5],[6,7],[7,8]]; // the square of the lodge, the porch lintel, star->altar->pavement + const LODGE_SCALES = { + east: { root: 60, scale: "major" }, west: { root: 50, scale: "dorian" }, + south: { root: 55, scale: "mixolydian" }, north: { root: 48, scale: "phrygian" }, + boaz: { root: 52, scale: "minor" }, jachin: { root: 53, scale: "minor" }, + star: { root: 72, scale: "lydian" }, altar: { root: 64, scale: "lydian" }, + pavement: { root: 57, scale: "wholetone" }, + }; + function lodgeOf(key) { + const k = (key || "").toLowerCase(), p = k.split(".")[0]; + if (p === "hand") return k.includes("x") || k === "hand.open" ? "boaz" : "jachin"; // the candidate at the porch + if (/^sky\.|sun_lon|_lon|zodiac/.test(k) || k === "astro.saturn" || k === "astro.mars") return "east"; // the heavens, read from the East + if (["crypto","market","econ","debt"].includes(p)) return "west"; // the wages, paid in the West + if (p === "audio" || /season|almanac|lifeexp|births/.test(k)) return "south"; // Beauty holds music & the arts + if (/deaths|hunger|poverty|quake|pm25|aqi/.test(k)) return "north"; // what waits in the dark + if (k === "sun.speed" || k === "astro.harmony" || k === "almanac.fertile") return "star"; // Providence + if (p === "wiki") return "altar"; // humanity's open book + if (/tension|light\.|ambient/.test(k)) return "pavement"; // the chequered floor: light & dark + return "pavement"; + } + + // THE LADDER OF THE SPHERES — Hermetic ascent, Moon lowest to Saturn highest, + // each gate its classical metal. As above, so below. + const LADDER = [ + { key: "saturn", label: "saturn · lead gate · the threshold", petals: 7, col: [95, 90, 110], }, + { key: "jupiter", label: "jupiter · tin gate", petals: 6, col: [70, 110, 200], }, + { key: "mars", label: "mars · iron gate", petals: 5, col: [190, 60, 48], }, + { key: "sun", label: "sol · gold gate · the heart", petals: 4, col: [246, 200, 60], }, + { key: "venus", label: "venus · copper gate", petals: 3, col: [100, 190, 130], }, + { key: "mercury", label: "mercury · quicksilver gate", petals: 2, col: [200, 176, 130], }, + { key: "moon", label: "luna · silver gate", petals: 1, col: [225, 230, 240], }, + ]; + const LADDER_SCALES = { + saturn: { root: 69, scale: "wholetone" }, jupiter: { root: 65, scale: "mixolydian" }, + mars: { root: 62, scale: "phrygian" }, sun: { root: 60, scale: "major" }, + venus: { root: 55, scale: "lydian" }, mercury: { root: 52, scale: "dorian" }, + moon: { root: 48, scale: "minor" }, + }; + function ladderOf(key) { + const k = (key || "").toLowerCase(), p = k.split(".")[0]; + if (k === "astro.saturn" || p === "debt" || /inflation|poverty|hunger|zodiac/.test(k)) return "saturn"; + if (/lifeexp|fertile|harmony|births|popvel/.test(k)) return "jupiter"; + if (k === "astro.mars" || p === "fire" || /tension|quake\.event|deaths/.test(k)) return "mars"; + if (k === "sun.speed" || /^sky\./.test(k)) return "sun"; + if (/season|almanac\.element|centroid/.test(k)) return "venus"; + if (["wiki","market","crypto","planes","iss"].includes(p) || /retro/.test(k)) return "mercury"; + return "moon"; // the sublunary world of flux + } + + // YGGDRASIL — the nine worlds on the world-tree; the classic three-tier + // arrangement (a scholarly reconstruction, as the Eddas never draw one map). + const NINE = [ + { key: "asgard", label: "asgard · the aesir", petals: 8, col: [236, 223, 140] }, + { key: "vanaheim", label: "vanaheim · the vanir", petals: 6, col: [86, 196, 150] }, + { key: "alfheim", label: "alfheim · the light elves", petals: 6, col: [214, 236, 255] }, + { key: "midgard", label: "midgard · humankind", petals: 4, col: [110, 170, 110] }, + { key: "jotunheim", label: "jotunheim · the giants", petals: 5, col: [120, 130, 148] }, + { key: "nidavellir", label: "nidavellir · the dwarves", petals: 4, col: [150, 116, 82] }, + { key: "muspelheim", label: "muspelheim · fire", petals: 7, col: [255, 96, 52] }, + { key: "niflheim", label: "niflheim · ice & mist", petals: 7, col: [150, 196, 230] }, + { key: "helheim", label: "helheim · the dead", petals: 3, col: [110, 90, 125] }, + ]; + const NINE_SCALES = { + asgard: { root: 62, scale: "lydian" }, vanaheim: { root: 57, scale: "mixolydian" }, + alfheim: { root: 55, scale: "lydian" }, midgard: { root: 50, scale: "major" }, + jotunheim:{ root: 47, scale: "phrygian" }, nidavellir: { root: 45, scale: "dorian" }, + muspelheim:{ root: 43, scale: "minor_pent" }, niflheim: { root: 40, scale: "minor" }, + helheim: { root: 36, scale: "phrygian" }, + }; + function nineOf(key) { + const k = (key || "").toLowerCase(), p = k.split(".")[0]; + if (p === "sun" || p === "sky" || p === "iss" || p === "astro" || /zodiac/.test(k)) return "asgard"; + if (/fertile|season|births|almanac/.test(k)) return "vanaheim"; + if (/ambient|light\.|harmony/.test(k)) return "alfheim"; + if (p === "wiki" || p === "hand" || p === "planes" || /lifeexp|popvel/.test(k)) return "midgard"; + if (/wind|pressure|tension|quake\.event/.test(k)) return "jotunheim"; // the storm-giants + if (["crypto","market","debt","econ"].includes(p)) return "nidavellir"; // the dwarves' gold & its curse + if (p === "fire" || k === "weather.temp") return "muspelheim"; + if (/pm25|aqi/.test(k)) return "niflheim"; // the poison-mists + if (/deaths|hunger|poverty|quake\.depth/.test(k)) return "helheim"; + return "midgard"; + } + + // LATA'IF-E-SITTA — the six subtle centres of the Sufi heart (Naqshbandi + // tabulation, colours per Sirhindi's school). Ruh is spirit-as-breath. + const LATAIF = [ + { key: "akhfa", label: "akhfa · the most hidden", petals: 6, col: [70, 175, 90] }, + { key: "khafi", label: "khafi · the hidden", petals: 5, col: [90, 85, 100] }, + { key: "sirr", label: "sirr · the secret", petals: 4, col: [240, 240, 245] }, + { key: "ruh", label: "ruh · the spirit", petals: 3, col: [225, 60, 66] }, + { key: "qalb", label: "qalb · the heart", petals: 2, col: [235, 205, 60] }, + { key: "nafs", label: "nafs · the self", petals: 7, col: [70, 110, 220] }, + ]; + const LATAIF_SCALES = { + akhfa: { root: 72, scale: "lydian" }, khafi: { root: 69, scale: "phrygian" }, + sirr: { root: 67, scale: "wholetone" }, ruh: { root: 64, scale: "major" }, + qalb: { root: 60, scale: "dorian" }, nafs: { root: 55, scale: "minor" }, + }; + function lataifOf(key) { + const k = (key || "").toLowerCase(), p = k.split(".")[0]; + if (k === "sun.speed") return "akhfa"; // the most rarefied + if (p === "astro" || /zodiac|almanac/.test(k)) return "khafi"; // the hidden influences + if (/^sky\.|_lon/.test(k)) return "sirr"; // the secret geometry above + if (/wind|^air\.|^planes|iss/.test(k)) return "ruh"; // ruh IS breath — the moving air + if (p === "wiki" || p === "clock" || /^world\./.test(k)) return "qalb"; // the heart holds humanity + return "nafs"; // appetite, money, fire, the body + } + + // WU XING — the Five Phases; outer pentagon = the generating (sheng) cycle, + // inner pentagram = the controlling (ke) cycle. South (Fire) at top, per the + // Chinese convention. Planets in their classical phase correspondences. + const WUXING = [ + { key: "wood", label: "wood 木 · spring · jupiter", petals: 8, col: [70, 180, 95], pos: [0.06, 0.36] }, + { key: "fire", label: "fire 火 · summer · mars", petals: 7, col: [225, 55, 48], pos: [0.50, 0.02] }, + { key: "earth", label: "earth 土 · centre · saturn", petals: 5, col: [212, 178, 62], pos: [0.94, 0.36] }, + { key: "metal", label: "metal 金 · autumn · venus", petals: 4, col: [225, 226, 232], pos: [0.78, 0.92] }, + { key: "water", label: "water 水 · winter · mercury", petals: 6, col: [65, 90, 160], pos: [0.22, 0.92] }, + ]; + const WUXING_PATHS = [[0,1],[1,2],[2,3],[3,4],[4,0]]; // sheng — the generating ring + const WUXING_PATHS2 = [[0,2],[2,4],[4,1],[1,3],[3,0]]; // ke — the controlling star + const WUXING_SCALES = { + wood: { root: 57, scale: "dorian" }, fire: { root: 62, scale: "mixolydian" }, + earth: { root: 65, scale: "major" }, metal: { root: 69, scale: "minor_pent" }, + water: { root: 74, scale: "pentatonic" }, + }; + function wuxingOf(key) { + const k = (key || "").toLowerCase(), p = k.split(".")[0]; + if (/births|popvel|season|fertile|lifeexp|wind|harmony|almanac\.element/.test(k)) return "wood"; + if (p === "fire" || /temp|mars|tension|ambient|light\.|sky\./.test(k) || k === "sun.speed") return "fire"; + if (/saturn|quake|hunger|poverty|pressure|debt\.total/.test(k)) return "earth"; + if (["market","planes","audio"].includes(p) || /inflation|crypto\.price|debt\.vel/.test(k)) return "metal"; // coin, bells, metal birds + return "water"; // the deep: moon, wisdom, the word + } + + const SKINS = { + chakra: { + key: "chakra", label: "chakra", word: "chakra", + stations: CHAKRAS, stationOf: chakraOf, scaleOf: (k) => CHAKRA_SCALES[k], + }, + planes: { + key: "planes", label: "the planes (bailey)", word: "plane", + stations: BAILEY, stationOf: baileyOf, scaleOf: (k) => BAILEY_SCALES[k], + groups: BAILEY_GROUPS, ribbon: true, + }, + tree: { + key: "tree", label: "tree of life", word: "sephirah", layout: "map", + stations: TREE, stationOf: treeOf, scaleOf: (k) => TREE_SCALES[k], paths: TREE_PATHS, + }, + lodge: { + key: "lodge", label: "the lodge", word: "station", layout: "map", + stations: LODGE, stationOf: lodgeOf, scaleOf: (k) => LODGE_SCALES[k], paths: LODGE_PATHS, + }, + ladder: { + key: "ladder", label: "hermetic ladder", word: "sphere", + stations: LADDER, stationOf: ladderOf, scaleOf: (k) => LADDER_SCALES[k], + }, + yggdrasil: { + key: "yggdrasil", label: "yggdrasil", word: "world", + stations: NINE, stationOf: nineOf, scaleOf: (k) => NINE_SCALES[k], + }, + lataif: { + key: "lataif", label: "lata'if (sufi)", word: "latifa", + stations: LATAIF, stationOf: lataifOf, scaleOf: (k) => LATAIF_SCALES[k], + }, + wuxing: { + key: "wuxing", label: "wu xing", word: "phase", layout: "map", + stations: WUXING, stationOf: wuxingOf, scaleOf: (k) => WUXING_SCALES[k], + paths: WUXING_PATHS, paths2: WUXING_PATHS2, + }, + }; + for (const s in SKINS) { + const byKey = {}; + for (const c of SKINS[s].stations) byKey[c.key] = c; + SKINS[s].byKey = byKey; + } + let activeSkin = SKINS.chakra; + try { const s = localStorage.getItem("gs_skin"); if (s && SKINS[s]) activeSkin = SKINS[s]; } catch (e) {} + function setSkin(k) { + if (!SKINS[k] || activeSkin === SKINS[k]) return; + activeSkin = SKINS[k]; + try { localStorage.setItem("gs_skin", k); } catch (e) {} + mindChakra = null; // leave mind mode across a skin change + for (const n of sources.values()) n.station = null; // re-map sources next frame + clearTrails(); + } + function nextSkinKey() { + const ks = Object.keys(SKINS), i = ks.indexOf(activeSkin.key); + return ks[(i + 1) % ks.length]; + } + function chakraGeom() { const top = HEADER_H + 74; const bot = H - FOOTER_H - 60; - return { cx: W * 0.42, top, bot, gap: (bot - top) / (CHAKRAS.length - 1) }; + return { cx: W * 0.42, top, bot, gap: (bot - top) / (activeSkin.stations.length - 1) }; } const CHAKRA_FOV = 900; @@ -1926,30 +2236,41 @@ chakraSpin.rot += chakraSpin.vel * dt + dt * 0.12; } const g = chakraGeom(); - for (const c of CHAKRAS) { c.members = []; c.energy = 0; } + for (const c of activeSkin.stations) { c.members = []; c.energy = 0; } for (const n of sources.values()) { - if (!n.chakra) n.chakra = chakraOf(n.key); - const c = CHAKRA_BY_KEY[n.chakra]; + if (!n.station) n.station = activeSkin.stationOf(n.key); + const c = activeSkin.byKey[n.station]; + if (!c) continue; c.members.push(n); c.energy += n.normDisp; } - for (const c of CHAKRAS) { + for (const c of activeSkin.stations) { c.energy = c.members.length ? c.energy / c.members.length : 0; c.members.sort((a, b) => a.key < b.key ? -1 : 1); // stable seats } if (mindChakra) { updateMind(t, dt); return; } - CHAKRAS.forEach((c, i) => { - c.cy = g.top + g.gap * i; + const isMap = activeSkin.layout === "map"; + const box = mapBox(g); + activeSkin.stations.forEach((c, i) => { + if (isMap && c.pos) { // 2D sacred figure (Tree, Lodge…) + c.sx = box.x0 + c.pos[0] * (box.x1 - box.x0); + c.sy = box.y0 + c.pos[1] * (box.y1 - box.y0); + } else { // the spine — stacked top to bottom + c.sx = g.cx; c.sy = g.top + g.gap * i; + } + c.cy = c.sy; // legacy alias const m = c.members.length; const dir = i % 2 ? -1 : 1; // adjacent wheels counter-rotate + const baseR = isMap ? Math.min(84, W * 0.09, 40 + m * 7) + : Math.min(300, W * 0.24, 130 + m * 12); for (let j = 0; j < m; j++) { const n = c.members[j]; - let R = Math.min(300, W * 0.24, 130 + m * 12); + let R = baseR; if (fxLevel > 2) R *= 1 + 0.06 * Math.sin(t * 0.7 + j * 1.7); // ultra: orbits breathe -> spirals const ang = chakraSpin.rot * dir + (j / m) * Math.PI * 2; const x3 = Math.cos(ang) * R, z3 = Math.sin(ang) * R; // z>0 is far const s = CHAKRA_FOV / (CHAKRA_FOV + z3); - n.x = g.cx + x3 * s; - n.y = c.cy + z3 * Math.sin(chakraSpin.tilt) * s; + n.x = c.sx + x3 * s; + n.y = c.sy + z3 * Math.sin(chakraSpin.tilt) * s; n.targetY = n.y; // keep the smoother from fighting us n._z = z3; n._s = s; pushTrail(n); @@ -1957,6 +2278,11 @@ }); } + function mapBox(g) { // the frame a 2D skin (map layout) is drawn in + const halfW = Math.min(W * 0.30, 360); + return { x0: g.cx - halfW, x1: g.cx + halfW, y0: g.top + 8, y1: g.bot - 8 }; + } + function pushTrail(n) { // ultra only: comet tails behind every orbit if (fxLevel < 3) { n.trail = null; return; } const tr = n.trail || (n.trail = []); @@ -1975,8 +2301,8 @@ const yaw = chakraSpin.rot; const horizon = H * 0.46 + (chakraSpin.tilt - 0.4) * H * 0.5; for (const n of sources.values()) // everything not of this wheel waits outside - if (n.chakra !== c.key) { n.x = -9999; n.y = -9999; n._z = 0; n._s = 1; n.targetY = n.y; } - const dir = CHAKRAS.indexOf(c) % 2 ? -1 : 1; + if (n.station !== c.key) { n.x = -9999; n.y = -9999; n._z = 0; n._s = 1; n.targetY = n.y; } + const dir = activeSkin.stations.indexOf(c) % 2 ? -1 : 1; const m = c.members.length; for (let j = 0; j < m; j++) { const n = c.members[j]; @@ -2046,7 +2372,7 @@ const list = c.members.filter(n => n.x > -9000).sort((a, b) => (b._z || 0) - (a._z || 0)); for (const n of list) drawChakraNode(n, t); drawMindKeys(t); - const cs = CHAKRA_SCALES[c.key]; + const cs = activeSkin.scaleOf(c.key); ctx.textAlign = "center"; ctx.textBaseline = "alphabetic"; ctx.font = "600 13px 'Helvetica Neue', Arial, sans-serif"; ctx.fillStyle = rgba(rgbMix(c.col, 0.6), 0.9); @@ -2102,46 +2428,91 @@ } function drawChakraSpine(t, g) { - // the sushumna — a column of light, pulsing on the godtime beat ctx.save(); - ctx.strokeStyle = "rgba(215,228,255,0.30)"; - ctx.lineWidth = 3 + godtime.beatFlash * 2.5; - if (fxLevel > 1) { - ctx.shadowColor = "rgba(170,195,255,0.8)"; - ctx.shadowBlur = 18; + const isMap = activeSkin.layout === "map"; + + // map skins (Tree of Life, the Lodge…) draw their canonical connective paths + // between stations instead of a single vertical spine. + if (isMap) drawStationPaths(); + + // planes skin: the spine IS the 49 sub-planes — a descending spectrum ladder, + // each plane's 7 sub-planes shading from atomic (lightest) to densest. + if (activeSkin.ribbon && !isMap) { + const y0 = g.top - 24, y1 = g.bot + 24, n = activeSkin.stations.length * 7; + const segH = (y1 - y0) / n; + for (let j = 0; j < n; j++) { + const st = activeSkin.stations[Math.floor(j / 7)], sub = j % 7; + ctx.fillStyle = rgba(rgbMix(st.col, (6 - sub) / 6 * 0.55), 0.55); + ctx.fillRect(g.cx - 3.5, y0 + j * segH, 7, segH + 0.6); + } + } + + // the central channel — a column of light, pulsing on the godtime beat + if (!isMap) { + ctx.strokeStyle = "rgba(215,228,255,0.30)"; + ctx.lineWidth = (activeSkin.ribbon ? 1.4 : 3) + godtime.beatFlash * 2.5; + if (fxLevel > 1) { + ctx.shadowColor = "rgba(170,195,255,0.8)"; + ctx.shadowBlur = 18; + } + ctx.beginPath(); ctx.moveTo(g.cx, g.top - 46); ctx.lineTo(g.cx, g.bot + 36); ctx.stroke(); + ctx.shadowBlur = 0; + } + + // the three grouping brackets (Monad / Spiritual Triad / Personality) + if (activeSkin.groups) { + const bx = 22; + for (const grp of activeSkin.groups) { + const a = activeSkin.stations[grp.from], b = activeSkin.stations[grp.to]; + if (!a || !b) continue; + const yA = a.cy - 22, yB = b.cy + 22; + ctx.strokeStyle = "rgba(200,210,235,0.30)"; + ctx.lineWidth = 1.4; + ctx.beginPath(); + ctx.moveTo(bx + 7, yA); ctx.lineTo(bx, yA); ctx.lineTo(bx, yB); ctx.lineTo(bx + 7, yB); + ctx.stroke(); + ctx.save(); + ctx.translate(bx - 5, (yA + yB) / 2); ctx.rotate(-Math.PI / 2); + ctx.textAlign = "center"; ctx.textBaseline = "alphabetic"; + ctx.font = "600 10px 'Helvetica Neue', Arial, sans-serif"; + ctx.fillStyle = "rgba(195,208,235,0.6)"; + ctx.fillText(grp.name, 0, 0); + ctx.restore(); + } } - ctx.beginPath(); ctx.moveTo(g.cx, g.top - 46); ctx.lineTo(g.cx, g.bot + 36); ctx.stroke(); - ctx.shadowBlur = 0; // rising prana - for (let k = 0; k < 7; k++) { - const p = (t * 0.11 + k / 7) % 1; - ctx.beginPath(); - ctx.arc(g.cx, lerp(g.bot + 30, g.top - 40, p), 2.2, 0, Math.PI * 2); - ctx.fillStyle = "rgba(230,240,255," + (0.55 * Math.sin(p * Math.PI)).toFixed(3) + ")"; - ctx.fill(); + if (!isMap) { + for (let k = 0; k < 7; k++) { + const p = (t * 0.11 + k / 7) % 1; + ctx.beginPath(); + ctx.arc(g.cx, lerp(g.bot + 30, g.top - 40, p), 2.2, 0, Math.PI * 2); + ctx.fillStyle = "rgba(230,240,255," + (0.55 * Math.sin(p * Math.PI)).toFixed(3) + ")"; + ctx.fill(); + } } - CHAKRAS.forEach((c, i) => { + activeSkin.stations.forEach((c, i) => { const e = c.energy || 0; + const cx2 = c.sx != null ? c.sx : g.cx, cy2 = c.sy != null ? c.sy : c.cy; const r = 15 + e * 10; // the wheel breathes with its members' data - drawHalo(g.cx, c.cy, 66 + e * 60, c.col, 0.18 + e * 0.35); + drawHalo(cx2, cy2, (isMap ? 44 : 66) + e * 60, c.col, 0.18 + e * 0.35); // spinning petals, alternating direction wheel to wheel const dir = i % 2 ? -1 : 1; for (let p = 0; p < c.petals; p++) { const a = chakraSpin.rot * dir * 1.4 + (p / c.petals) * Math.PI * 2; ctx.beginPath(); - ctx.arc(g.cx + Math.cos(a) * (r + 9), c.cy + Math.sin(a) * (r + 9), 1.8, 0, Math.PI * 2); + ctx.arc(cx2 + Math.cos(a) * (r + 9), cy2 + Math.sin(a) * (r + 9), 1.8, 0, Math.PI * 2); ctx.fillStyle = rgba(rgbMix(c.col, 0.35), 0.55 + e * 0.4); ctx.fill(); } - const cg = ctx.createRadialGradient(g.cx - 3, c.cy - 3, 1, g.cx, c.cy, r); + const cg = ctx.createRadialGradient(cx2 - 3, cy2 - 3, 1, cx2, cy2, r); cg.addColorStop(0, rgba(rgbMix(c.col, 0.6), 0.95)); cg.addColorStop(1, rgba(c.col, 0.45)); - ctx.beginPath(); ctx.arc(g.cx, c.cy, r, 0, Math.PI * 2); + ctx.beginPath(); ctx.arc(cx2, cy2, r, 0, Math.PI * 2); ctx.fillStyle = cg; ctx.fill(); ctx.lineWidth = 1.2; ctx.strokeStyle = rgba(rgbMix(c.col, 0.5), 0.8); @@ -2150,11 +2521,31 @@ ctx.textAlign = "center"; ctx.textBaseline = "alphabetic"; ctx.font = "600 10px 'Helvetica Neue', Arial, sans-serif"; ctx.fillStyle = rgba(rgbMix(c.col, 0.55), 0.8); - ctx.fillText(c.label + (c.members.length ? " · " + c.members.length : ""), g.cx, c.cy + r + 16); + ctx.fillText(c.label + (c.members.length ? " · " + c.members.length : ""), cx2, cy2 + r + 16); }); ctx.restore(); } + function drawStationPaths() { // the canonical connective lines of a map skin + const draw = (pairs, dashed) => { + if (!pairs) return; + ctx.setLineDash(dashed ? [4, 6] : []); + for (const pr of pairs) { + const A = activeSkin.stations[pr[0]], B = activeSkin.stations[pr[1]]; + if (!A || !B || A.sx == null || B.sx == null) continue; + const e = Math.max(A.energy || 0, B.energy || 0); // a path glows with its livelier end + ctx.beginPath(); + ctx.moveTo(A.sx, A.sy); ctx.lineTo(B.sx, B.sy); + ctx.lineWidth = 1.2 + godtime.beatFlash * 0.8; + ctx.strokeStyle = "rgba(205,215,240," + (0.14 + e * 0.30).toFixed(3) + ")"; + ctx.stroke(); + } + ctx.setLineDash([]); + }; + draw(activeSkin.paths, false); + draw(activeSkin.paths2, true); // e.g. the wu xing controlling star, dashed + } + // --------------------------------------------------------------------------- // Shockwaves & particles // --------------------------------------------------------------------------- @@ -2539,8 +2930,10 @@ } } else { // click a wheel = step inside it const g = chakraGeom(); - for (const c of CHAKRAS) - if (Math.hypot(mx - g.cx, my - c.cy) < 24) { enterMind(c); return; } + for (const c of activeSkin.stations) + if (Math.hypot(mx - (c.sx != null ? c.sx : g.cx), my - (c.sy != null ? c.sy : c.cy)) < 24) { + enterMind(c); return; + } } chakraSpin.vel = 0; // empty space = grab / look around chakraDrag = { x0: mx, y0: my, rot0: chakraSpin.rot, tilt0: chakraSpin.tilt, lastX: mx }; @@ -2632,9 +3025,9 @@ } if (ev.key === "Escape") { exitMind(); return; } if (ev.key === "ArrowUp" || ev.key === "ArrowDown") { - const i2 = clamp(CHAKRAS.indexOf(mindChakra) + (ev.key === "ArrowUp" ? -1 : 1), - 0, CHAKRAS.length - 1); - enterMind(CHAKRAS[i2]); + const i2 = clamp(activeSkin.stations.indexOf(mindChakra) + (ev.key === "ArrowUp" ? -1 : 1), + 0, activeSkin.stations.length - 1); + enterMind(activeSkin.stations[i2]); return; } } @@ -2703,7 +3096,7 @@ const hd = document.createElement("div"); hd.className = "hd"; const b = document.createElement("b"); b.textContent = node.label || node.key; const sp = document.createElement("span"); - sp.textContent = node.key + (isSrc ? " · " + chakraOf(node.key) + " chakra" : " · voice"); + sp.textContent = node.key + (isSrc ? " · " + activeSkin.stationOf(node.key) + " " + activeSkin.word : " · voice"); hd.appendChild(b); hd.appendChild(sp); ctxEl.appendChild(hd); ctxItem(node.muted ? "unmute" : "mute", () => toggleMuteLocal(node.key), "click"); @@ -2763,6 +3156,8 @@ hd.appendChild(b); hd.appendChild(sp); ctxEl.appendChild(hd); ctxItem(chakraMode ? "leave chakra view" : "chakra view", () => { chakraMode = !chakraMode; if (!chakraMode) layoutDirty = true; }, "C"); + ctxItem("skin: " + activeSkin.label + " ↺", + () => { setSkin(nextSkinKey()); if (!chakraMode) { chakraMode = true; clearTrails(); } }); ctxItem(performMode ? "leave performance mode" : "performance mode", () => window.dispatchEvent(new KeyboardEvent("keydown", { key: "p" })), "P"); if (mindChakra) ctxItem("step out of " + mindChakra.label, () => exitMind(), "esc"); @@ -2843,7 +3238,7 @@ const fam = FAMILIES[node.family] ? FAMILIES[node.family].name : ""; const sub = document.createElement("div"); sub.className = "sub"; sub.textContent = node.key + (fam ? " · " + fam : "") + (isSource ? " · source" : " · voice") - + (isSource ? " · " + chakraOf(node.key) + " chakra" : ""); + + (isSource ? " · " + activeSkin.stationOf(node.key) + " " + activeSkin.word : ""); inspectEl.appendChild(sub); // what it is