viz: skin system — 8 traditions on one engine (Source / Spirit / Matter)

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 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-05 19:35:03 +10:00
parent b997a16697
commit eaba76edca
2 changed files with 442 additions and 41 deletions

View File

@ -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
}
]
}

View File

@ -751,6 +751,8 @@
<li><b>Press <kbd>C</kbd> — chakra view.</b> 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. <b>Drag empty space to spin the whole body 360°</b>, 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. <kbd>C</kbd> again and the sources glide home.</li>
<li><b>Right-click anything — the everything menu.</b> Every node carries its full settings under the right button: mute, <b>solo</b>, a <b>level</b> fader (02×), a <b>trim</b> offset, every cable it carries with its amount slider and an unpatch ×, and a nested <i>patch to →</i> list for laying new cables without dragging. Right-click the empty sky for the view menu. And the modifiers, for the fast hands: <b>click</b> mutes · <b>⇧-click</b> opens the node's tab · <b>⇧-drag</b> rides the level up and down · <b>⌥-click</b> solos a source · <b>⌘-click</b> multi-selects for grouping · <b>⌘⇧-click</b> resets a node to neutral. People play differently; the instrument shouldn't care which hand you reach with.</li>
<li><b>Click a wheel — go inside it (mind mode).</b> In chakra view, click any chakra's core and the camera steps <i>into</i> the wheel: its mandala of petals turns around your whole field of view, and that chakra's feeds circle <i>you</i> — drag to look around the full 360°. A <b>vector console appears under your hands</b>: 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 <kbd>Q</kbd><kbd>]</kbd> row, or by any connected <b>MIDI keyboard</b> — every note runs through the world's own reverb and grit, so you are jamming <i>inside</i> the instrument, in that chakra's mode, while the planet plays around you. <kbd></kbd> <kbd></kbd> travel the spine wheel to wheel; <kbd>esc</kbd> steps back out. And for powerful machines, right-click the sky and set graphics to <b>ultra ✦</b>: every orbiting feed grows a glowing comet trail, and the orbits breathe so the trails weave slow spirals.</li>
<li><b>Skins — the same spine, another cosmology.</b> Right-click the sky and cycle <b>skin</b>. Beyond the seven <i>chakras</i> there is <b>the planes</b>: the Seven Planes &amp; forty-nine sub-planes of the Cosmic Physical Plane from the Alice Bailey / D.K. teachings — <b>I Divine</b> (white light) down through <b>Monadic</b> (violet), <b>Atmic</b>, <b>Buddhic</b>, <b>Mental</b> (where the egoic lotus sits), <b>Astral</b>, to <b>VII Physical</b> (red). The spine becomes the 49-sub-plane spectrum ladder; the side-brackets mark the <b>Monad</b>, the <b>Spiritual Triad</b> and the <b>Personality</b>; 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.</li>
<li><b>One Source, many Spirits, one Matter.</b> That is the whole architecture: the <b>Source</b> is the world's data, the same for everyone; the <b>Spirit</b> is the skin — the belief system through which you read it; the <b>Matter</b> 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 <b>Tree of Life</b> (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); <b>the Lodge</b> (from published Masonic sources only — Worshipful Master in the East, Wardens West and South, the North dark, Boaz and Jachin at the porch where <i>your own hands</i> 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 <b>Hermetic ladder</b> of the seven planetary gates, lead to silver; <b>Yggdrasil</b> and its nine worlds, the dwarves keeping the markets in Nidavellir; the Sufi <b>lata'if</b>, where the wind data literally breathes at Ruh, spirit-as-breath; and the <b>Wu Xing</b>, five phases in their generating ring and controlling star, the metal birds of the air routed to Metal. Right-click the sky → <b>skin</b> 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.</li>
</ul>
<div class="hr"></div>
@ -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