Frivolous fun - Powered by vibe coding

What have you made?

Things not worth your time - but worth sending off to AI (and then ultimately, your time anyway)

Here’s a teaser, a web page backed by a concept that is good for some amusement.

It explains itself if you want to delve further.

(try in a few minutes if status 502)

Spoiler

Virtual match-ups between you and an “admirer” in a pool of like candidates, based on your and all others’ indicated preferences. Faces and names by AI, so you have an engagement surface.

The algorithm tries to find the most mutually agreeable matches between two groups of people, of which you are also a virtual participant. The code also gives the random rankings the personas make a bit of unseen human flavor.

The code solves using a variant of the Hungarian algorithm (also known as the Kuhn–Munkres algorithm), which is a classic method for optimally assigning items from one set to items in another, along with weighting for compatibility and avoiding negatives in matches.

An unfortunate round..


Scripts and coded entertainment with instructions also welcome!

5 Likes

This was an image generation script written to flummox “patches” AI models with inputs that match their internal grid size.

However, with different parameters at the pixel level, it makes for interesting pretty images, a sampling which you can appreciate here without needing to run the Python.




Higher deviation, into clipping:

Parameters are globals at the top; DEFAULT_NEIGHBOR_DEVIATION from under 0.01 up to 0.2 are interesting.

Python - generates count of images to local directory
from __future__ import annotations

import colorsys
import random
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable

from PIL import Image

DEFAULT_WIDTH = 690
DEFAULT_HEIGHT = 500
DEFAULT_PATCH_SIZE = 1
DEFAULT_NEIGHBOR_DEVIATION = 0.02
DEFAULT_SEED_RGB_DEVIATION = 0.25
DEFAULT_SEED_HSV_DEVIATION = 0.25
DEFAULT_COLOR_SPACE = "rgb"  # "rgb" or "hsv"
DEFAULT_SEED: int | None = None
DEFAULT_SEED_POSITION: tuple[int, int] | None = None

BASE_NAME = "output_image"
OUTPUT_EXTENSION = "png"
IMAGE_COUNT = 4


@dataclass
class GeneratorConfig:
    width: int = 768
    height: int = 768
    patch_size: int = 16
    neighbor_deviation: float = 0.3
    seed_rgb_deviation: float = 0.25
    seed_hsv_deviation: float = 0.25
    color_space: str = "rgb"
    seed: int | None = None
    seed_position: tuple[int, int] | None = None


def clamp(value: float, lo: float = 0.0, hi: float = 1.0) -> float:
    return lo if value < lo else hi if value > hi else value


def _random_seed_rgb(
    max_rgb_dev: float,
    max_hsv_dev: float,
    rng: random.Random,
) -> tuple[float, float, float]:
    base_rgb = (0.5, 0.5, 0.5)
    base_hsv = colorsys.rgb_to_hsv(*base_rgb)

    max_rgb_dev = clamp(max_rgb_dev, 0.0, 0.5)
    max_hsv_dev = clamp(max_hsv_dev, 0.0, 1.0)

    candidate_rgb = base_rgb
    candidate_clamped = base_rgb

    for _ in range(100):
        candidate_rgb = tuple(
            base_rgb[i] + rng.uniform(-max_rgb_dev, max_rgb_dev) for i in range(3)
        )
        candidate_clamped = tuple(clamp(c) for c in candidate_rgb)

        cand_hsv = colorsys.rgb_to_hsv(*candidate_clamped)

        dh_raw = abs(cand_hsv[0] - base_hsv[0])
        dh = min(dh_raw, 1.0 - dh_raw)
        ds = abs(cand_hsv[1] - base_hsv[1])
        dv = abs(cand_hsv[2] - base_hsv[2])

        if dh <= max_hsv_dev and ds <= max_hsv_dev and dv <= max_hsv_dev:
            return candidate_clamped

    return candidate_clamped


def _random_walk_grid(
    grid_w: int,
    grid_h: int,
    seed_color: tuple[float, float, float],
    cfg: GeneratorConfig,
    rng: random.Random,
) -> list[list[tuple[float, float, float]]]:
    grid: list[list[tuple[float, float, float] | None]] = [
        [None for _ in range(grid_w)] for _ in range(grid_h)
    ]

    if cfg.seed_position is not None:
        sx, sy = cfg.seed_position
        if not (0 <= sx < grid_w and 0 <= sy < grid_h):
            raise ValueError("seed_position is outside the color grid")
    else:
        sx = rng.randrange(grid_w)
        sy = rng.randrange(grid_h)

    grid[sy][sx] = seed_color

    frontier: list[tuple[int, int]] = []
    frontier_set: set[tuple[int, int]] = set()

    def add_frontier_cell(y: int, x: int) -> None:
        if 0 <= y < grid_h and 0 <= x < grid_w:
            if grid[y][x] is None and (y, x) not in frontier_set:
                frontier_set.add((y, x))
                frontier.append((y, x))

    for dy, dx in ((-1, 0), (1, 0), (0, -1), (0, 1)):
        add_frontier_cell(sy + dy, sx + dx)

    step_scale = float(cfg.neighbor_deviation)

    while frontier:
        idx = rng.randrange(len(frontier))
        y, x = frontier.pop(idx)
        frontier_set.remove((y, x))

        if grid[y][x] is not None:
            continue

        neighbors: list[tuple[float, float, float]] = []
        for dy, dx in ((-1, 0), (1, 0), (0, -1), (0, 1)):
            ny, nx = y + dy, x + dx
            if 0 <= ny < grid_h and 0 <= nx < grid_w:
                c = grid[ny][nx]
                if c is not None:
                    neighbors.append(c)

        if not neighbors:
            base = seed_color
        else:
            base = neighbors[rng.randrange(len(neighbors))]

        new_color = (
            base[0] + rng.uniform(-step_scale, step_scale),
            base[1] + rng.uniform(-step_scale, step_scale),
            base[2] + rng.uniform(-step_scale, step_scale),
        )

        grid[y][x] = new_color

        for dy, dx in ((-1, 0), (1, 0), (0, -1), (0, 1)):
            add_frontier_cell(y + dy, x + dx)

    for row in grid:
        if any(c is None for c in row):
            raise RuntimeError("Generation left unfilled cells in the grid")

    return grid  # type: ignore[return-value]


def _convert_to_rgb(
    grid: Iterable[Iterable[tuple[float, float, float]]],
    color_space: str,
) -> list[list[tuple[int, int, int]]]:
    color_space = color_space.lower()
    if color_space not in {"rgb", "hsv"}:
        raise ValueError("color_space must be 'rgb' or 'hsv'")

    rgb_grid: list[list[tuple[int, int, int]]] = []

    for row in grid:
        rgb_row: list[tuple[int, int, int]] = []
        for c0, c1, c2 in row:
            if color_space == "rgb":
                r = int(clamp(c0) * 255.0 + 0.5)
                g = int(clamp(c1) * 255.0 + 0.5)
                b = int(clamp(c2) * 255.0 + 0.5)
            else:
                h = c0 % 1.0
                s = clamp(c1)
                v = clamp(c2)
                r_f, g_f, b_f = colorsys.hsv_to_rgb(h, s, v)
                r = int(clamp(r_f) * 255.0 + 0.5)
                g = int(clamp(g_f) * 255.0 + 0.5)
                b = int(clamp(b_f) * 255.0 + 0.5)

            rgb_row.append((r, g, b))
        rgb_grid.append(rgb_row)

    return rgb_grid


def generate_image(cfg: GeneratorConfig) -> Image.Image:
    if cfg.width % cfg.patch_size != 0 or cfg.height % cfg.patch_size != 0:
        raise ValueError("width and height must be divisible by patch_size")

    grid_w = cfg.width // cfg.patch_size
    grid_h = cfg.height // cfg.patch_size

    rng = random.Random(cfg.seed)

    seed_rgb = _random_seed_rgb(cfg.seed_rgb_deviation, cfg.seed_hsv_deviation, rng)

    if cfg.color_space.lower() == "rgb":
        seed_color = seed_rgb
    elif cfg.color_space.lower() == "hsv":
        seed_color = colorsys.rgb_to_hsv(*seed_rgb)
    else:
        raise ValueError("color_space must be 'rgb' or 'hsv'")

    grid = _random_walk_grid(grid_w, grid_h, seed_color, cfg, rng)
    rgb_grid = _convert_to_rgb(grid, cfg.color_space)

    img = Image.new("RGB", (cfg.width, cfg.height))
    px = img.load()

    for gy, row in enumerate(rgb_grid):
        for gx, (r, g, b) in enumerate(row):
            x0 = gx * cfg.patch_size
            y0 = gy * cfg.patch_size
            for dy in range(cfg.patch_size):
                for dx in range(cfg.patch_size):
                    px[x0 + dx, y0 + dy] = (r, g, b)

    return img


def _next_output_path(
    base_name: str = BASE_NAME,
    extension: str = OUTPUT_EXTENSION,
    directory: Path | None = None,
) -> Path:
    if directory is None:
        directory = Path.cwd()

    base_path = directory / f"{base_name}.{extension}"
    if not base_path.exists():
        return base_path

    index = 1
    while True:
        candidate = directory / f"{base_name}_{index:03d}.{extension}"
        if not candidate.exists():
            return candidate
        index += 1


def main() -> None:
    cfg = GeneratorConfig(
        width=DEFAULT_WIDTH,
        height=DEFAULT_HEIGHT,
        patch_size=DEFAULT_PATCH_SIZE,
        neighbor_deviation=DEFAULT_NEIGHBOR_DEVIATION,
        seed_rgb_deviation=DEFAULT_SEED_RGB_DEVIATION,
        seed_hsv_deviation=DEFAULT_SEED_HSV_DEVIATION,
        color_space=DEFAULT_COLOR_SPACE,
        seed=DEFAULT_SEED,
        seed_position=DEFAULT_SEED_POSITION,
    )

    if IMAGE_COUNT <= 0:
        return

    for _ in range(IMAGE_COUNT):
        img = generate_image(cfg)
        out_path = _next_output_path()
        img.save(out_path, format="PNG")
        print(f"Saved {out_path}")


if __name__ == "__main__":
    main()

Next to come - turmites ++; cellular automata runs for hours with a UI, making cooler RGB color patterns that grow while heating your room with CPU.

3 Likes

Ahh Frivolous ffffffun… reminds me of my son’s times tables practice website… All the kids worked out they could do 10s and 2s fastest so it became a race to the bottom.

The merriness you can have with the rand() function…

1 Like

These little JS games (made up especially for this thread) (are meant to) show a real cognitive phenomenon found in some Indigenous Australian languages, where people don’t think in terms of left and right at all. Instead, they use absolute directions — North, East, South, West — for everything, including identifying objects and even body parts.

2D
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Absolute Directions Game</title>
<style>
  body {
    font-family: system-ui, sans-serif;
    background: #111;
    color: #eee;
    display: flex;
    flex-direction: column;
    align-items: center;
    margin: 0;
    padding: 20px;
  }
  h1 {
    margin-top: 0;
    font-size: 1.4rem;
  }
  #game-container {
    display: flex;
    flex-direction: row;
    gap: 20px;
    align-items: flex-start;
  }
  #canvas-wrapper {
    position: relative;
  }
  canvas {
    border: 1px solid #444;
    background: radial-gradient(circle at center, #222 0%, #000 70%);
    cursor: pointer;
  }
  #ui {
    max-width: 280px;
    font-size: 0.9rem;
  }
  #prompt {
    font-weight: bold;
    margin-bottom: 10px;
  }
  #message {
    min-height: 2.2em;
    margin-top: 8px;
  }
  #stats {
    margin-top: 10px;
    font-size: 0.85rem;
    color: #ccc;
  }
  #mode-toggle {
    margin-top: 10px;
  }
  button {
    margin-top: 12px;
    padding: 6px 10px;
    border-radius: 4px;
    border: 1px solid #666;
    background: #222;
    color: #eee;
    cursor: pointer;
  }
  button:disabled {
    opacity: 0.4;
    cursor: default;
  }
  .legend {
    margin-top: 14px;
    font-size: 0.85rem;
    line-height: 1.3;
    color: #aaa;
  }
</style>
</head>
<body>
<h1>Absolute Directions vs Left/Right</h1>
<div id="game-container">
  <div id="canvas-wrapper">
    <canvas id="world" width="500" height="500"></canvas>
  </div>
  <div id="ui">
    <div id="prompt"></div>
    <div>
      Mode:
      <select id="mode-toggle">
        <option value="absolute">Absolute directions (North/East/South/West)</option>
        <option value="egocentric">Egocentric (Left/Right/Front/Back)</option>
      </select>
    </div>
    <div id="message"></div>
    <div id="stats"></div>
    <button id="reset-btn">Reset stats</button>
    <div class="legend">
      <strong>How to play</strong><br>
      • The arrow in the middle is “you”.<br>
      • The big N at the top shows true North (fixed).<br>
      • Circles sit at real North/East/South/West.<br>
      • In <em>Absolute</em> mode, instructions use world directions.<br>
      • In <em>Egocentric</em> mode, they use Left/Right/Front/Back relative to where you face.<br>
      • Click the circle that matches the instruction.
    </div>
  </div>
</div>

<script>
(function() {
  const canvas = document.getElementById('world');
  const ctx = canvas.getContext('2d');
  const promptEl = document.getElementById('prompt');
  const messageEl = document.getElementById('message');
  const statsEl = document.getElementById('stats');
  const modeSelect = document.getElementById('mode-toggle');
  const resetBtn = document.getElementById('reset-btn');

  const W = canvas.width;
  const H = canvas.height;
  const center = { x: W / 2, y: H / 2 };
  const ringRadius = 160;
  const circleRadius = 28;

  const directions = [
    { name: 'North', short: 'N', angle: 0 },
    { name: 'East',  short: 'E', angle: 90 },
    { name: 'South', short: 'S', angle: 180 },
    { name: 'West',  short: 'W', angle: 270 }
  ];

  const egoNames = ['Front', 'Right', 'Back', 'Left']; // 0,90,180,270

  let objects = [];
  let playerFacingIndex = 0;
  let currentTargetIndex = 0;
  let currentEgoTarget = 0;
  let waitingForClick = true;
  let mode = 'absolute';
  let rounds = 0;
  let correct = 0;

  function degToRad(d) {
    return d * Math.PI / 180;
  }

  function computeObjects() {
    objects = directions.map((dir, idx) => {
      const rad = degToRad(dir.angle);
      const x = center.x + ringRadius * Math.sin(rad);
      const y = center.y - ringRadius * Math.cos(rad);
      return {
        x,
        y,
        radius: circleRadius,
        directionIndex: idx
      };
    });
  }

  function drawCompass() {
    ctx.save();
    ctx.fillStyle = '#fff';
    ctx.font = '24px system-ui, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('N', center.x, 30);
    ctx.restore();
  }

  function drawObjects() {
    const colors = ['#ff6666', '#66aaff', '#66dd66', '#ffcc66'];
    ctx.font = '16px system-ui, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';

    objects.forEach((obj, idx) => {
      ctx.beginPath();
      ctx.arc(obj.x, obj.y, obj.radius, 0, Math.PI * 2);
      ctx.fillStyle = colors[idx % colors.length];
      ctx.fill();
      ctx.lineWidth = 2;
      ctx.strokeStyle = '#111';
      ctx.stroke();

      ctx.fillStyle = '#000';
      ctx.fillText(directions[obj.directionIndex].short, obj.x, obj.y);
    });
  }

  function drawPlayer() {
    const facing = directions[playerFacingIndex].angle;
    const rad = degToRad(facing);

    const bodyRadius = 18;
    ctx.beginPath();
    ctx.arc(center.x, center.y, bodyRadius, 0, Math.PI * 2);
    ctx.fillStyle = '#ddd';
    ctx.fill();
    ctx.strokeStyle = '#333';
    ctx.lineWidth = 2;
    ctx.stroke();

    const tipLen = 38;
    const sideLen = 18;

    const tipX = center.x + tipLen * Math.sin(rad);
    const tipY = center.y - tipLen * Math.cos(rad);
    const leftX = center.x + sideLen * Math.sin(rad + Math.PI * 0.7);
    const leftY = center.y - sideLen * Math.cos(rad + Math.PI * 0.7);
    const rightX = center.x + sideLen * Math.sin(rad - Math.PI * 0.7);
    const rightY = center.y - sideLen * Math.cos(rad - Math.PI * 0.7);

    ctx.beginPath();
    ctx.moveTo(tipX, tipY);
    ctx.lineTo(leftX, leftY);
    ctx.lineTo(rightX, rightY);
    ctx.closePath();
    ctx.fillStyle = '#ffef99';
    ctx.fill();
    ctx.strokeStyle = '#444';
    ctx.lineWidth = 2;
    ctx.stroke();
  }

  function clearCanvas() {
    ctx.clearRect(0, 0, W, H);
  }

  function render() {
    clearCanvas();
    drawCompass();
    drawObjects();
    drawPlayer();
  }

  function randomInt(max) {
    return Math.floor(Math.random() * max);
  }

  function newRound() {
    waitingForClick = true;
    mode = modeSelect.value;
    playerFacingIndex = randomInt(4);
    currentTargetIndex = randomInt(4);

    computeObjects();
    render();
    messageEl.textContent = '';

    if (mode === 'absolute') {
      const dirName = directions[currentTargetIndex].name.toUpperCase();
      promptEl.textContent = `Click the object that is to the ${dirName}. (World-fixed)`;
    } else {
      const egoTarget = randomInt(4);
      currentEgoTarget = egoTarget;
      const egoLabel = egoNames[egoTarget].toUpperCase();
      promptEl.textContent = `Click the object that is at your ${egoLabel}. (Relative to where you face)`;
    }

    updateStats();
  }

  function updateStats() {
    statsEl.textContent = `Rounds: ${rounds} | Correct: ${correct} | Accuracy: ${
      rounds ? Math.round((correct / rounds) * 100) : 0
    }%`;
  }

  function getClickObject(evt) {
    const rect = canvas.getBoundingClientRect();
    const x = (evt.clientX - rect.left) * (canvas.width / rect.width);
    const y = (evt.clientY - rect.top) * (canvas.height / rect.height);

    for (const obj of objects) {
      const dx = x - obj.x;
      const dy = y - obj.y;
      if (dx*dx + dy*dy <= obj.radius * obj.radius) {
        return obj;
      }
    }
    return null;
  }

  function directionToEgo(dirIdx, facingIdx) {
    const dirAngle = directions[dirIdx].angle;
    const facingAngle = directions[facingIdx].angle;
    const rel = ((dirAngle - facingAngle) + 360) % 360;
    if (rel >= 315 || rel < 45) return 'Front';
    if (rel >= 45 && rel < 135) return 'Right';
    if (rel >= 135 && rel < 225) return 'Back';
    return 'Left';
  }

  function egoToDirection(egoIdx, facingIdx) {
    const baseAngle = directions[facingIdx].angle;
    const relAngles = [0, 90, 180, 270];
    const targetAngle = (baseAngle + relAngles[egoIdx] + 360) % 360;
    for (let i = 0; i < directions.length; i++) {
      if (directions[i].angle === targetAngle) {
        return i;
      }
    }
    return 0;
  }

  function handleClick(evt) {
    if (!waitingForClick) return;

    const obj = getClickObject(evt);
    if (!obj) return;

    waitingForClick = false;
    rounds++;

    if (mode === 'absolute') {
      const clickedDirIdx = obj.directionIndex;
      const correctDirIdx = currentTargetIndex;
      const isCorrect = clickedDirIdx === correctDirIdx;

      if (isCorrect) correct++;

      const worldName = directions[clickedDirIdx].name.toUpperCase();
      const egoName = directionToEgo(clickedDirIdx, playerFacingIndex).toUpperCase();
      const facingName = directions[playerFacingIndex].name.toUpperCase();

      messageEl.textContent = (isCorrect ? '✅ Correct! ' : '❌ Not quite. ') +
        `That circle is at ${worldName}, which is your ${egoName} (you’re facing ${facingName}).`;

    } else {
      const clickedDirIdx = obj.directionIndex;
      const neededDirIdx = egoToDirection(currentEgoTarget, playerFacingIndex);
      const isCorrect = clickedDirIdx === neededDirIdx;

      if (isCorrect) correct++;

      const worldName = directions[clickedDirIdx].name.toUpperCase();
      const egoName = directionToEgo(clickedDirIdx, playerFacingIndex).toUpperCase();
      const neededEgoLabel = egoNames[currentEgoTarget].toUpperCase();
      const facingName = directions[playerFacingIndex].name.toUpperCase();

      messageEl.textContent = (isCorrect ? '✅ Correct! ' : '❌ Not quite. ') +
        `You were asked for your ${neededEgoLabel}. You clicked ${worldName}, which is your ${egoName} (you’re facing ${facingName}).`;
    }

    updateStats();
    setTimeout(newRound, 1100);
  }

  canvas.addEventListener('click', handleClick);
  modeSelect.addEventListener('change', () => {
    newRound();
  });
  resetBtn.addEventListener('click', () => {
    rounds = 0;
    correct = 0;
    updateStats();
    newRound();
  });

  computeObjects();
  newRound();
})();
</script>
</body>
</html>

3D
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>3D Orientation: World vs Body</title>
<style>
  body {
    margin: 0;
    padding: 18px;
    background: #050608;
    color: #eee;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
  }
  h1 {
    font-size: 1.3rem;
    margin: 0;
  }
  #container {
    display: flex;
    gap: 20px;
    align-items: flex-start;
  }
  #canvas-wrapper {
    position: relative;
  }
  canvas {
    border: 1px solid #333;
    background: radial-gradient(circle at 50% 40%, #222 0, #050608 70%);
  }
  #ui {
    max-width: 320px;
    font-size: 0.9rem;
  }
  #prompt {
    font-weight: 600;
    margin-bottom: 10px;
  }
  #mode-select {
    margin-bottom: 8px;
  }
  #answers {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 6px;
  }
  button {
    padding: 5px 9px;
    border-radius: 4px;
    border: 1px solid #555;
    background: #181a1f;
    color: #eee;
    cursor: pointer;
    font-size: 0.8rem;
  }
  button:hover {
    background: #242832;
  }
  button:disabled {
    opacity: 0.4;
    cursor: default;
  }
  #message {
    margin-top: 8px;
    min-height: 2.2em;
  }
  #stats {
    margin-top: 6px;
    font-size: 0.8rem;
    color: #bbb;
  }
  #angles {
    margin-top: 4px;
    font-size: 0.8rem;
    color: #888;
  }
  .legend {
    margin-top: 10px;
    font-size: 0.8rem;
    color: #aaa;
    line-height: 1.35;
  }
</style>
</head>
<body>
<h1>3D Orientation – World vs Body Axes</h1>
<div id="container">
  <div id="canvas-wrapper">
    <canvas id="view" width="480" height="480"></canvas>
  </div>
  <div id="ui">
    <div>
      Mode:
      <select id="mode-select">
        <option value="world-to-body">World → Body (Which way on YOU?)</option>
        <option value="body-to-world">Body → World (Where in space?)</option>
      </select>
    </div>
    <div id="prompt"></div>
    <div id="answers"></div>
    <div id="message"></div>
    <div id="stats"></div>
    <div id="angles"></div>
    <button id="next-btn">Next orientation</button>
    <button id="reset-btn">Reset stats</button>
    <div class="legend">
      World axes:<br>
      • +X = right (red) / −X = left<br>
      • +Y = up (green) / −Y = down<br>
      • +Z = out of screen (blue) / −Z = into screen<br><br>
      Ship axes (bright arrows): Forward, Right, Up.<br>
      The game asks you to map between **world directions** and **body directions**.
    </div>
  </div>
</div>

<script>
(function() {
  const canvas = document.getElementById('view');
  const ctx = canvas.getContext('2d');
  const W = canvas.width;
  const H = canvas.height;
  const cx = W / 2;
  const cy = H / 2;
  const scale = 80;

  const promptEl = document.getElementById('prompt');
  const answersEl = document.getElementById('answers');
  const messageEl = document.getElementById('message');
  const statsEl = document.getElementById('stats');
  const anglesEl = document.getElementById('angles');
  const modeSelect = document.getElementById('mode-select');
  const nextBtn = document.getElementById('next-btn');
  const resetBtn = document.getElementById('reset-btn');

  const worldDirs = [
    { id: '+X', label: '+X (East)', vec: [ 1,  0,  0] },
    { id: '-X', label: '-X (West)', vec: [-1,  0,  0] },
    { id: '+Y', label: '+Y (Up)',   vec: [ 0,  1,  0] },
    { id: '-Y', label: '-Y (Down)', vec: [ 0, -1,  0] },
    { id: '+Z', label: '+Z (Out)',  vec: [ 0,  0,  1] },
    { id: '-Z', label: '-Z (In)',   vec: [ 0,  0, -1] }
  ];

  const bodyDirs = [
    { id: 'FWD',  label: 'Forward'  }, // +forward
    { id: 'RIGHT',label: 'Right'    },
    { id: 'UP',   label: 'Up'       },
    { id: 'BACK', label: 'Back'     }, // -forward
    { id: 'LEFT', label: 'Left'     }, // -right
    { id: 'DOWN', label: 'Down'     }  // -up
  ];

  let yaw = 0, pitch = 0, roll = 0;
  let R = identity();
  let shipAxes = {}; // forward, right, up in world coords

  let mode = 'world-to-body';
  let currentQuestion = null; // { correctId, questionText, optionsType }
  let rounds = 0;
  let correct = 0;

  function identity() {
    return [
      [1,0,0],
      [0,1,0],
      [0,0,1]
    ];
  }

  function degToRad(d) {
    return d * Math.PI / 180;
  }

  function matMul(A, B) {
    const r = [ [0,0,0], [0,0,0], [0,0,0] ];
    for (let i=0;i<3;i++) {
      for (let j=0;j<3;j++) {
        r[i][j] = A[i][0]*B[0][j] + A[i][1]*B[1][j] + A[i][2]*B[2][j];
      }
    }
    return r;
  }

  function matVec(M, v) {
    return [
      M[0][0]*v[0] + M[0][1]*v[1] + M[0][2]*v[2],
      M[1][0]*v[0] + M[1][1]*v[1] + M[1][2]*v[2],
      M[2][0]*v[0] + M[2][1]*v[1] + M[2][2]*v[2]
    ];
  }

  function Rx(a) {
    const c = Math.cos(a), s = Math.sin(a);
    return [
      [1, 0, 0],
      [0, c,-s],
      [0, s, c]
    ];
  }
  function Ry(a) {
    const c = Math.cos(a), s = Math.sin(a);
    return [
      [ c, 0, s],
      [ 0, 1, 0],
      [-s, 0, c]
    ];
  }
  function Rz(a) {
    const c = Math.cos(a), s = Math.sin(a);
    return [
      [ c,-s, 0],
      [ s, c, 0],
      [ 0, 0, 1]
    ];
  }

  function project(v) {
    const x = v[0], y = v[1], z = v[2];
    const isoX = x + z * 0.5;
    const isoY = y - z * 0.3;
    return [cx + isoX * scale, cy - isoY * scale];
  }

  function clear() {
    ctx.clearRect(0,0,W,H);
  }

  function drawLine3D(a, b, color, width) {
    const pa = project(a);
    const pb = project(b);
    ctx.strokeStyle = color;
    ctx.lineWidth = width;
    ctx.beginPath();
    ctx.moveTo(pa[0], pa[1]);
    ctx.lineTo(pb[0], pb[1]);
    ctx.stroke();
  }

  function drawAxes() {
    drawLine3D([0,0,0],[1.5,0,0],'#ff4444',1);
    drawLine3D([0,0,0],[-1.5,0,0],'#882222',1);
    drawLine3D([0,0,0],[0,1.5,0],'#55ff55',1);
    drawLine3D([0,0,0],[0,-1.5,0],'#228822',1);
    drawLine3D([0,0,0],[0,0,1.5],'#5599ff',1);
    drawLine3D([0,0,0],[0,0,-1.5],'#224477',1);

    drawLabel([1.7,0,0], '+X');
    drawLabel([-1.7,0,0], '-X');
    drawLabel([0,1.7,0], '+Y');
    drawLabel([0,-1.7,0], '-Y');
    drawLabel([0,0,1.7], '+Z');
    drawLabel([0,0,-1.7], '-Z');
  }

  function drawLabel(pos, text) {
    const p = project(pos);
    ctx.fillStyle = '#ccc';
    ctx.font = '11px system-ui, sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(text, p[0], p[1]);
  }

  function drawShipAxes() {
    drawLine3D([0,0,0], shipAxes.forward, '#ffd966', 3);
    drawLine3D([0,0,0], shipAxes.right,   '#ffa9ff', 3);
    drawLine3D([0,0,0], shipAxes.up,      '#a9e1ff', 3);

    drawLabel(shipAxes.forward.map(v => v*1.15), 'F');
    drawLabel(shipAxes.right.map(v => v*1.15), 'R');
    drawLabel(shipAxes.up.map(v => v*1.15), 'U');
  }

  function randomOrientation() {
    const choices = [0, 90, 180, 270];
    yaw = choices[Math.floor(Math.random()*choices.length)];
    pitch = choices[Math.floor(Math.random()*choices.length)];
    roll = choices[Math.floor(Math.random()*choices.length)];

    const y = degToRad(yaw);
    const p = degToRad(pitch);
    const r = degToRad(roll);

    R = matMul(Rz(r), matMul(Rx(p), Ry(y)));

    const fwdBody   = [0,0,1];
    const rightBody = [1,0,0];
    const upBody    = [0,1,0];

    shipAxes.forward = matVec(R, fwdBody);
    shipAxes.right   = matVec(R, rightBody);
    shipAxes.up      = matVec(R, upBody);
  }

  function drawScene() {
    clear();
    drawAxes();
    drawShipAxes();
  }

  function dot(a,b) {
    return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];
  }

  function norm(v) {
    const l = Math.sqrt(dot(v,v)) || 1;
    return [v[0]/l, v[1]/l, v[2]/l];
  }

  function vectorForWorldId(id) {
    return worldDirs.find(d => d.id === id).vec;
  }

  function vectorForBodyId(id) {
    switch(id) {
      case 'FWD':  return shipAxes.forward;
      case 'RIGHT':return shipAxes.right;
      case 'UP':   return shipAxes.up;
      case 'BACK': return shipAxes.forward.map(v => -v);
      case 'LEFT': return shipAxes.right.map(v => -v);
      case 'DOWN': return shipAxes.up.map(v => -v);
    }
    return [0,0,0];
  }

  function closestBodyToWorld(worldVec) {
    const options = bodyDirs;
    let best = null;
    let bestDot = -999;
    const w = norm(worldVec);
    for (const b of options) {
      const v = norm(vectorForBodyId(b.id));
      const d = dot(w,v);
      if (d > bestDot) {
        bestDot = d;
        best = b;
      }
    }
    return best;
  }

  function closestWorldToBody(bodyVec) {
    let best = null;
    let bestDot = -999;
    const b = norm(bodyVec);
    for (const w of worldDirs) {
      const v = norm(w.vec);
      const d = dot(b,v);
      if (d > bestDot) {
        bestDot = d;
        best = w;
      }
    }
    return best;
  }

  function buildWorldToBodyQuestion() {
    const idx = Math.floor(Math.random() * worldDirs.length);
    const w = worldDirs[idx];
    const correctBody = closestBodyToWorld(w.vec);

    return {
      type: 'world-to-body',
      correctId: correctBody.id,
      questionText: `Which way *relative to you* is closest to world direction ${w.label}?`,
      worldRef: w
    };
  }

  function buildBodyToWorldQuestion() {
    const idx = Math.floor(Math.random() * bodyDirs.length);
    const b = bodyDirs[idx];
    const bodyVec = vectorForBodyId(b.id);
    const correctWorld = closestWorldToBody(bodyVec);

    return {
      type: 'body-to-world',
      correctId: correctWorld.id,
      questionText: `Your ${b.label.toUpperCase()} is pointing roughly where in the world frame?`,
      bodyRef: b
    };
  }

  function showAnswersFor(question) {
    answersEl.innerHTML = '';
    let options;
    if (question.type === 'world-to-body') {
      options = bodyDirs;
    } else {
      options = worldDirs;
    }

    options.forEach(opt => {
      const btn = document.createElement('button');
      btn.textContent = opt.label;
      btn.dataset.id = opt.id;
      btn.addEventListener('click', () => handleAnswer(opt.id));
      answersEl.appendChild(btn);
    });
  }

  function handleAnswer(id) {
    if (!currentQuestion) return;
    const correctId = currentQuestion.correctId;

    const buttons = answersEl.querySelectorAll('button');
    buttons.forEach(b => b.disabled = true);

    rounds++;
    let msg;
    if (id === correctId) {
      correct++;
      msg = '✅ Correct! ';
    } else {
      msg = '❌ Not quite. ';
    }

    if (currentQuestion.type === 'world-to-body') {
      const chosen = bodyDirs.find(b => b.id === id);
      const correctBody = bodyDirs.find(b => b.id === correctId);
      msg += `World ${currentQuestion.worldRef.label} is closest to your ${correctBody.label.toUpperCase()}.`;
      if (id !== correctId) {
        msg += ` You chose ${chosen.label}.`;
      }
    } else {
      const chosen = worldDirs.find(w => w.id === id);
      const correctWorld = worldDirs.find(w => w.id === correctId);
      msg += `Your ${currentQuestion.bodyRef.label.toUpperCase()} points roughly toward ${correctWorld.label}.`;
      if (id !== correctId) {
        msg += ` You chose ${chosen.label}.`;
      }
    }

    messageEl.textContent = msg;
    updateStats();
  }

  function updateStats() {
    const acc = rounds ? Math.round(correct / rounds * 100) : 0;
    statsEl.textContent = `Rounds: ${rounds} | Correct: ${correct} | Accuracy: ${acc}%`;
  }

  function updateAnglesDisplay() {
    anglesEl.textContent = `Yaw: ${yaw}° | Pitch: ${pitch}° | Roll: ${roll}°`;
  }

  function newRound() {
    mode = modeSelect.value;
    randomOrientation();
    drawScene();
    updateAnglesDisplay();
    messageEl.textContent = '';

    if (mode === 'world-to-body') {
      currentQuestion = buildWorldToBodyQuestion();
    } else {
      currentQuestion = buildBodyToWorldQuestion();
    }
    promptEl.textContent = currentQuestion.questionText;
    showAnswersFor(currentQuestion);
  }

  modeSelect.addEventListener('change', newRound);
  nextBtn.addEventListener('click', newRound);
  resetBtn.addEventListener('click', () => {
    rounds = 0;
    correct = 0;
    updateStats();
    newRound();
  });

  updateStats();
  newRound();
})();
</script>
</body>
</html>
2 Likes

This is totally random matches; they don’t know anything about me, and they say they rated me??? What is the purpose of that? Copy of “face match” by Zuckerberg?

1 Like

The computer personas indeed are just making random rankings of all others, including you. In the real world, you are not an omniscient observer of why people like you, either. The “matches” that result are not random, though.

It wouldn’t be any fun if you simply picked the top face and got that face always as your ‘pairing’, now, would it?

The implication is that there is a mutual attraction signaled by how the other persons voted also, and for the entire group, a best set of pairings is made (and sometimes there is no choice but to match a pessimist with an AI pessimist also giving poor rankings, so the others can succeed.)

The computer persona’s ratings of you and others are a mean and median of 5 on average (which I should also show).

On the results page, the total score at the top is how much better the actual algorithm being demonstrated matched everyone up, vs an expected 5, or lower for the reward score. That’s what’s being demonstrated, the math, the optimum pairings found. You can Monte-Carlo the sim’s code against any other technique you have to pair everyone acceptably between two groups based on one numeric factor, an input score rank. Take a total set of inputs (10 cross-rankings of 5) and see if you can make everybody “happier” than this…

1 Like

Multi-scale automata network, with Python application

This app simulates multiple “turmites” (mobile cellular automata agents) walking on a toroidal grid, rewriting cell states (multi-color) while also writing a diffusing, decaying pheromone field that biases movement and partitions the world into rule zones.

A randomly generated, periodically mutating rule table drives turns, writes, and pheromone changes, producing evolving, colorful emergent patterns in real time.

Rendering uses a persistent NumPy RGBX buffer wrapped by QImage and scaled by Qt for high FPS. An optional exporter renders a deterministic offscreen run to a 30 FPS lossless animated WebP and saves a matching .turmite.json recipe.

Requirements: Python 3.12+, PySide6, NumPy. Export requires Pillow with animated WebP.

It takes a bit of work to go from “working” to “neat” and then “highly performative” as an interactive responsive UI…then exportable products.

1234

Practical, not pretty, in 1080 lines.

“Vibe describing” what you are seeing:

Background

A “turmite” is a particular kind of cellular automaton (CA) that sits halfway between:

  • grid-based automata like Conway’s Game of Life (where the cells update themselves based on neighbors), and
  • agent-based systems (where moving agents walk around and modify the world).

In turmites, you usually have:

  • a 2D grid of cells (each cell stores a small state, like “white/black”),
  • one or more moving “ants/turmites” that each have:
    • a position (x, y) on the grid,
    • a facing direction (up/right/down/left),
    • an internal rule set: “when I stand on a cell of state S, do X”.

The classic one from popular writeups is Langton’s Ant:

  • Each cell is either white or black.
  • If the ant is on white: turn right, flip the cell to black, move forward.
  • If the ant is on black: turn left, flip to white, move forward.

That tiny rule produces striking emergent behavior: local scribbles, then often a long repeating “highway”.

Here, in this significant variant

When you watch the UI or one of the exports this program produces, you’re seeing several layers at once:

  • A discrete painted grid (the base palette colors).
  • A diffusing signed pheromone field (adds brightness and influences motion).
  • A moving population of agents (each repeatedly reading/writing those fields).
  • A multiscale region classifier (“zones”) that changes which rules apply where.
  • Occasional genome mutation that changes the behavior mid-run.
  • Stochastic turning bias that prevents over-deterministic lock-in and adds fluidity.

Classic 2-color single-ant turmites are like: “one cursor flips tiles and turns left/right.”

This system is more like: “a small society of cursors paints a multi-color canvas while building and eroding an energy field, and the local physics/rules of motion change depending on regional energy levels.”

Extensive reading for you, including a deep-dive of this algo

How this relates to Conway’s Game of Life

Conway’s Game of Life is also a CA, but it is a different “style”:

  • Life is cell-driven: every cell updates simultaneously each tick based on neighbor counts.
  • Turmites are agent-driven: the agent moves, and the agent changes a few cells per step.

Both create emergence from simple rules, but:

  • Life’s complexity comes from parallel interactions among many cells.
  • Turmites’ complexity comes from a feedback loop: the agent writes to the grid, then later reads what it wrote, which changes its future path.

So, conceptually, turmites are doing the same kind of thing as Life—simple local rules yielding complex patterns—but the “local rule application point” is attached to a moving cursor (the turmite) rather than applied everywhere at once.


Why this simulation looks so different from “Wikipedia turmites”

If you look up turmites on Wikipedia or classic references, you’ll often see:

  • 1 agent
  • 2 cell colors (or a very small number)
  • a fixed rule table like “turn left/right depending on color; write a new color”
  • usually no other field besides the cell color

This code is intentionally not that minimal model. It adds several mechanisms that massively expand the space of behaviors and visuals:

1) Many cell states (7 to 17), not just 2

In TurmiteSim.__init__:

self.states = int(self.rng.integers(7, 18))

So the grid isn’t “black/white”. It’s 7–17 discrete states. Each state maps to a palette color. That alone produces:

  • smoother gradients of structure (visually),
  • more kinds of loops and “tracks” (dynamically),
  • a much bigger rule space (combinatorially).

2) Multiple agents (7 to 14) at once

n_agents = int(self.rng.integers(7, 15))

Multiple turmites means you get interaction effects:

  • one agent overwrites what another agent wrote,
  • they indirectly “communicate” through shared grid state and a second field (pheromone),
  • you see braiding, interference, and region competition.

Even if each agent followed a deterministic rule, multiple agents create richer motion and more varied visuals.

3) Two-layer world: “color grid” + “pheromone field”

This simulation has two grids:

  • grid[y, x] is the cell state (0..states-1). This drives the displayed base color.
  • pher[y, x] is a signed integer pheromone-like field (roughly -255..255).

The pheromone field:

  • is written locally by agents (dphi adds/subtracts),
  • is diffused and decayed periodically (_diffuse_decay),
  • is sensed by agents (they look ahead/left/right and bias turns).

That is a big departure from classic turmites, because now an agent’s motion depends on:

  • the discrete “paint” state, and
  • a continuous-ish, diffusing “potential” field.

Visually, that second field is also used to brighten the colors, which creates glowing highlights around “active” trails.

4) The rule table depends on “zones” (a multiscale / spatially varying rule set)

Classic turmites: rule is indexed by the current cell state (and optionally the turmite internal state).

This simulation: rule is indexed by:

  • state = the cell’s discrete state, and
  • zone = a region classification derived from the pheromone field at a coarser resolution.

That is what the comment means by:

“Rule table indexed by [zone, state] → (turn, write, dphi).”

The number of zones is randomly chosen:

self.zones = int(self.rng.choice(np.array([3, 4, 5, 6], dtype=np.int32)))

And the zone map is recomputed from the pheromone field every so often:

if (self.step_count % 47) == 0:
  self._update_zone_map()

So the world is effectively partitioned into 3–6 bands (based on pheromone intensity aggregates), and each band has its own local rules.

This is one of the key reasons the output looks “alive” and varied: as pheromone builds up in an area, the zone classification changes, which changes the rules applied in that area, which changes how pheromone will evolve next. It’s a feedback loop across scales.

5) The system is “self-modifying”: the rule table mutates during the run

Every mut_interval steps (randomly around 1200–2400, then re-randomized):

self.genome.mutate(...)
self.mut_interval = int(self.rng.integers(1000, 2401))

So even if the simulation settles into a stable attractor, it eventually gets kicked into a new behavioral regime. That helps produce long-form variation rather than “it does something cool for 10 seconds and then repeats forever”.

6) Color is not just “cell state”: it also uses pheromone magnitude as a brightness boost

Rendering does:

  • map gridpalette_rgb
  • compute inc_u16 = abs(pher), scale it, clamp it
  • add it to RGB (clamped at 255)

So pheromone creates brightened regions (“glow”) that track activity and gradients. This is why it looks more like flowing energy / metaballs / electrical traces than simple pixel art.


Deeper dive: what rules are generated, how big the rule space is, and how they affect behavior

The “genome” (rule table) structure

The core is the Genome object:

class Genome:
  """Rule table indexed by [zone, state] -> (turn, write, dphi)."""

It holds three arrays of shape (zones, states):

  • turn[z, s] is one of [-1, 0, 1, 2]
  • write[z, s] is an integer in [0, states-1]
  • dphi[z, s] is an integer in [-2, -1, 0, 1, 2]

Random generation:

turn_choices = np.array([-1, 0, 1, 2], dtype=np.int8)
turn  = rng.choice(turn_choices, size=(zones, states))
write = rng.integers(0, states, size=(zones, states))
dphi  = rng.integers(-2, 3, size=(zones, states))

What “turn” means here

The agent direction d is 0..3 (up/right/down/left), and then:

d = (d + turn) & 3

So:

  • turn = -1 means “turn left” (equivalently +3 mod 4)
  • turn = 0 means “go straight”
  • turn = 1 means “turn right”
  • turn = 2 means “turn around” (U-turn)

Classic Langton’s Ant only allows left or right. Adding straight and U-turn expands the motion vocabulary a lot.

What “write” means

The current cell state is overwritten:

self.grid[y, x] = np.uint8(write)

So the agent “paints” the grid. Because there are many states, this is like painting with many colors, not just flipping a bit.

What “dphi” means

Pheromone at the current cell is incremented/decremented:

pv = int(self.pher[y, x]) + dphi
pv = clamp(pv, -255, 255)
self.pher[y, x] = pv

So the rule writes not only a color but also pushes a local potential up or down slightly.

How the zone map is formed (why rules become “regional”)

Every ~47 steps the simulation recomputes a coarse grid aggregation of pheromone:

  • It divides the world into coarse blocks (coarse_w, coarse_h).
  • It sums pheromone inside each block.
  • It computes quantile thresholds to split those sums into zones buckets.
  • Then it expands those coarse zone labels back to per-cell zone_map.

So “zone 0” might represent low pheromone areas, “zone 5” high pheromone areas (exact meaning changes dynamically).

That means the same cell-state s can trigger different behaviors depending on whether it lies in a calm region or a saturated, high-pheromone region.

The rule space size (why “random rules” here are incredibly diverse)

Suppose a typical run chooses:

  • states = 12
  • zones = 5

Then there are zones * states = 60 table entries.

Each entry chooses:

  • turn: 4 possibilities
  • write: 12 possibilities
  • dphi: 5 possibilities

So per entry there are 4 * 12 * 5 = 240 possibilities.

Total possible genomes: 240^(60).

That number is astronomically large. Even if most genomes are “boring” or chaotic noise, there is an effectively endless supply of distinct behaviors—especially once you include:

  • multiple agents,
  • random initial placement/direction,
  • diffusion/decay dynamics,
  • periodic mutations.

What happens during one agent step (the real “behavior loop”)

Inside _apply_rule_for_agent(i):

  1. Read local state:

    • s = grid[y, x]
    • z = zone_map[y, x]
  2. Lookup rule:

    • (turn, write, dphi) = genome[z, s]
  3. A small extra “hue-based write modulation”
    Each agent has a fixed hue (0..states-1):

    if ((self.step_count + hue) % 23) == 0:
      write = (write + hue) % self.states
    

    This injects an agent-specific periodic twist: every 23-ish steps (phase-shifted by hue), the agent writes a shifted color. That helps differentiate agents visually and behaviorally without giving them a large internal state machine.

  4. Write the color state:

    • grid[y, x] = write
  5. Update pheromone:

    • pher[y, x] += dphi (clamped)
  6. Sense pheromone around the agent
    It looks at absolute pheromone magnitude in three positions:

    • ahead
    • left
    • right

    This is not a full deterministic “follow gradient” rule; it’s a biased heuristic.

  7. Stochastic steering override
    It draws r = rng.random() and then may modify turn:

    • If right is stronger than left, with 60% chance bias to turn more right-ish
    • If left is stronger than right, with 60% chance bias to turn more left-ish
    • If ahead is strongest, with 40% chance go straight

    This is important: it prevents the system from being purely deterministic “clockwork”. It makes motion more organic, and it also helps avoid quickly settling into tiny loops.

  8. Apply final turn + move forward one cell (toroidal wrap)

That’s the fundamental loop. Notice how many feedback channels are in play:

  • The agent writes grid and pher.
  • Diffusion/decay spreads pher.
  • pher influences zone classification.
  • Zone classification changes which rule entry is used.
  • pher also locally biases turning.
  • The agent’s hue periodically perturbs writes.

A concrete example rule set (small illustrative walkthrough)

Imagine a tiny simplified case:

  • states = 4 (0..3)
  • zones = 3 (0..2)

Then the rule table has 12 entries. A (made-up) example:

Zone 0 (low pheromone region)

state s turn write dphi
0 +1 1 +1
1 +1 2 +1
2 0 3 0
3 -1 0 -1

Interpretation: in calm areas, it tends to spiral/rotate right while “walking up” states 0->1->2->3->0, and builds pheromone slightly.

Zone 2 (high pheromone region)

state s turn write dphi
0 2 0 -2
1 -1 0 -2
2 -1 1 -1
3 0 2 0

Interpretation: in saturated areas, it tends to do U-turns or left turns and actively reduces pheromone, “digging out” the region.

Now walk one agent step:

  • Agent stands on a cell with state=1 in a calm area zone=0.
  • It looks up (zone0, state1): turn=+1, write=2, dphi=+1.
  • It paints the cell to state 2 (color changes).
  • It increases pheromone there slightly.
  • Then it senses pheromone ahead/left/right; if one side is stronger it may bias the turn.
  • It turns right and moves.

After many steps, it might create a high pheromone trail. Once pheromone spreads and the coarse zone map sees that region as “zone 2”, suddenly a different set of rules starts applying in that region, which could cause:

  • the agent to reverse direction more often,
  • start writing different colors,
  • start decreasing pheromone, cooling down the region,
  • effectively creating “fronts” where behavior changes.

That switching of rule regimes is one of the big reasons you see evolving, layered structures rather than a single static motif.

How multiple agents interact under these rules

Agents don’t directly collide or fight; they interact through shared fields:

  1. Grid overwriting
    If agent A paints a cell that agent B later steps on, agent B will read a different state s and may turn/write differently.

  2. Pheromone shaping
    A cluster of agents can pump pheromone up in an area, which:

    • increases glow there (visual),
    • changes turning bias (behavioral),
    • changes the zone_map quantiles (regional rules), potentially affecting everyone.
  3. Indirect coordination / “ecosystems”
    You can end up with emergent roles:

    • some agents create trails (positive dphi patterns),
    • others erase or diffuse them (negative dphi patterns),
    • some agents get trapped into orbiting along gradients,
    • others wander and seed new regions.

Mutation: why you keep getting “variations” over time

Every so often, a fraction of the genome entries are randomly replaced:

k = max(1, int(n * rate))
# choose k random (zone,state) cells and replace turn/write/dphi

That means the system can:

  • run in one “style” for a while,
  • then abruptly shift because a few key table entries changed (especially if those entries correspond to common cell states in common zones).

Crucially, mutation is not a full reset: it’s a partial edit. So the system often “remixes” its existing structures instead of starting over from blank noise.

The single file is larger in character count than a forum post allows.

Download: https://od.lk/d/MjRfNzEyMjM2MjFf/jmites.pyw

This is maybe my favorite post I’ve ever seen anywhere, ever!!

Dimitrius This is totally random matches; they don’t know anything about me, and they say they rated me??? What is the purpose of that? Copy of “face match” by Zuckerberg?

Is… is this code?


 (cd "$(git rev-parse --show-toplevel)" && git apply --3way <<'EOF' 
diff --git a/careware/tamagotchi_suite.py b/careware/tamagotchi_suite.py
new file mode 100644
index 0000000000000000000000000000000000000000..5295b9e14d7371a488a12175cf0903c72e9d3e08
--- /dev/null
+++ b/careware/tamagotchi_suite.py
@@ -0,0 +1,216 @@
+from __future__ import annotations
+
+import json
+import random
+import secrets
+import time
+from dataclasses import dataclass
+from typing import Callable, Iterable, Mapping, Sequence
+
+from careware import CareDirective, CarePolicyRegistry
+from careware.integration import (
+    CarewareSuite,
+    CarewareSuiteConfig,
+    CarewareTriageHandle,
+    create_careware_suite,
+)
+from careware.modules import CarewareEventBus, get_event_bus
+
+EntropySource = Callable[[], str]
+Clock = Callable[[], float]
+
+
+@dataclass(slots=True)
+class TamagotchiPulse:
+    """Snapshot of a Tamagotchi tick."""
+
+    summary: str
+    carelog: str
+    directives: tuple[CareDirective, ...]
+    ritual_plan: tuple[str, ...]
+    layout: tuple[dict[str, object], ...]
+    entropy_token: str
+    temperature: float
+    governance_headers: Mapping[str, object]
+    os_profile: Mapping[str, object]
+
+
+class TamagotchiKernel:
+    """Kernel orchestrating the Aeon-styled Tamagotchi."""
+
+    def __init__(
+        self,
+        registry: CarePolicyRegistry | None = None,
+        *,
+        rng: random.Random | None = None,
+        entropy_source: EntropySource | None = None,
+        clock: Clock | None = None,
+    ) -> None:
+        self.registry = registry or CarePolicyRegistry(self._default_directives())
+        self.rng = rng or random.Random(secrets.randbits(32))
+        self._entropy_source = entropy_source or (lambda: secrets.token_hex(8))
+        self._clock = clock or time.time
+
+    @staticmethod
+    def _default_directives() -> Iterable[CareDirective]:
+        return (
+            CareDirective("stability", resonance=0.93, description="keep the pet responsive"),
+            CareDirective("play", resonance=0.88, description="favour delightful actions"),
+            CareDirective("governance", resonance=0.91, description="mirror Aeon oversight"),
+        )
+
+    def _rituals(self) -> tuple[str, ...]:
+        palette = (
+            "entropy_bloom",
+            "care_loop",
+            "trust_ping",
+            "kernel_sync",
+            "vector_hop",
+            "mobius_trace",
+        )
+        rituals = {self.rng.choice(palette) for _ in range(3 + self.rng.randint(0, 2))}
+        return tuple(sorted(rituals))
+
+    def _layout(self, mood: str) -> tuple[dict[str, object], ...]:
+        seeds = self.rng.sample(range(1, 9), k=3)
+        windows = []
+        for idx, seed in enumerate(seeds):
+            x = round(self.rng.random() * seed, 2)
+            y = round(self.rng.random() * seed, 2)
+            w = round(4.0 + self.rng.random() * 2, 2)
+            h = round(2.0 + self.rng.random() * 2, 2)
+            windows.append(
+                {
+                    "id": f"tama-{idx}",
+                    "x": x,
+                    "y": y,
+                    "w": w,
+                    "h": h,
+                    "title": f"{mood} ritual {idx}",
+                }
+            )
+        return tuple(windows)
+
+    def pulse(self, prompt: str, care_tags: Sequence[str] | None = None) -> TamagotchiPulse:
+        directives = self.registry.snapshot()
+        tag_names = care_tags or [directive.name for directive in directives]
+        carelog = self.registry.apply(prompt, tag_names, include_metadata=True)
+        rituals = self._rituals()
+        mood = self.rng.choice(("curious", "playful", "watchful", "restless"))
+        layout = self._layout(mood)
+        temperature = round(0.7 + self.rng.random() * 0.45, 2)
+        timestamp_ms = int(self._clock() * 1000)
+        entropy_token = f"{self._entropy_source()}-{timestamp_ms}"
+        summary = (
+            f"{mood.title()} Tamagotchi kernel engaging ChatGPT-5.2 with {len(rituals)} rituals"
+        )
+        governance_headers = {
+            "kernel": "tamagotchi",
+            "mode": "siloed",
+            "care_tags": tuple(tag_names),
+            "entropy": entropy_token,
+            "timestamp_ms": timestamp_ms,
+        }
+        os_profile = {
+            "layout": layout,
+            "mood": mood,
+            "panes": len(layout),
+            "orchestration": "careware_os",
+        }
+        return TamagotchiPulse(
+            summary=summary,
+            carelog=carelog,
+            directives=directives,
+            ritual_plan=rituals,
+            layout=layout,
+            entropy_token=entropy_token,
+            temperature=temperature,
+            governance_headers=governance_headers,
+            os_profile=os_profile,
+        )
+
+
+class TamagotchiSuite:
+    """End-to-end Aeon Tamagotchi suite anchored to Careware and ChatGPT-5.2."""
+
+    def __init__(
+        self,
+        kernel: TamagotchiKernel | None = None,
+        *,
+        bus: CarewareEventBus | None = None,
+    ) -> None:
+        self.kernel = kernel or TamagotchiKernel()
+        self.bus = bus or get_event_bus()
+
+    def engage(self, prompt: str, *, care_tags: Sequence[str] | None = None) -> TamagotchiPulse:
+        pulse = self.kernel.pulse(prompt, care_tags)
+        self.bus.emit_namespaced_event(
+            "tamagotchi",
+            "pulse",
+            {
+                "summary": pulse.summary,
+                "entropy": pulse.entropy_token,
+                "rituals": pulse.ritual_plan,
+                "governance": pulse.governance_headers,
+            },
+        )
+        return pulse
+
+    def chat_envelope(self, prompt: str, pulse: TamagotchiPulse) -> Mapping[str, object]:
+        system_prompt = (
+            "You are Aeon's Tamagotchi companion. Embrace Careware directives, operate inside "
+            "a siloed orchestration kernel, and answer with playful but responsible intent."
+        )
+        return {
+            "model": "gpt-5.2",
+            "temperature": pulse.temperature,
+            "messages": [
+                {"role": "system", "content": system_prompt},
+                {
+                    "role": "assistant",
+                    "content": f"entropy={pulse.entropy_token} rituals={','.join(pulse.ritual_plan)}",
+                },
+                {"role": "user", "content": prompt},
+            ],
+            "metadata": {
+                "careware_governance": pulse.governance_headers,
+                "careware_os_profile": pulse.os_profile,
+            },
+        }
+
+    def build_triage_plan(self, pulse: TamagotchiPulse) -> CarewareTriageHandle:
+        actions = (
+            {"op": "update_layout", "windows": pulse.layout},
+            {"op": "governance", "carelog": pulse.carelog},
+            {"op": "rituals", "plan": pulse.ritual_plan},
+        )
+        return CarewareTriageHandle(objective=pulse.summary, actions=actions)
+
+    def spawn_suite(self, pulse: TamagotchiPulse) -> CarewareSuite:
+        config = CarewareSuiteConfig(
+            windows=pulse.layout,
+            module_names=("tamagotchi", "careware_os"),
+            enable_spiral_chain=True,
+        )
+        suite = create_careware_suite(config=config)
+        payload = {
+            "summary": pulse.summary,
+            "entropy": pulse.entropy_token,
+            "rituals": pulse.ritual_plan,
+            "layout": pulse.layout,
+            "governance": pulse.governance_headers,
+            "os_profile": pulse.os_profile,
+        }
+        try:
+            suite.kernel.fs.write("tamagotchi/state.json", json.dumps(payload))
+        except Exception:
+            # Kernel shims may not expose a filesystem; the suite still operates.
+            pass
+        return suite
+
+
+__all__ = [
+    "TamagotchiKernel",
+    "TamagotchiPulse",
+    "TamagotchiSuite",
+]
 
EOF
)

Codex just go brrrrr… :man_shrugging:t2:

Hey @_j ,

Perfect Chance :wink:

1 Like

You knew I couldn’t resist something so deliciously ambiguous!… I’m such a sucker :joy:


 (cd "$(git rev-parse --show-toplevel)" && git apply --3way <<'EOF' 
diff --git a/six_seven_suite.py b/six_seven_suite.py
new file mode 100644
index 0000000000000000000000000000000000000000..889aee95a9b8923ef195876664190c0d62221aae
--- /dev/null
+++ b/six_seven_suite.py
@@ -0,0 +1,67 @@
+"""Convenience loader for the 6-7 suite."""
+
+from __future__ import annotations
+
+import importlib.util
+import sys
+from pathlib import Path
+from types import ModuleType
+from typing import Any
+
+_SUITE_DIR = Path(__file__).resolve().parent / "6-7"
+_PACKAGE_NAME = "six_seven_suite_pkg"
+
+
+def _load_package() -> ModuleType:
+    if _PACKAGE_NAME in sys.modules:
+        return sys.modules[_PACKAGE_NAME]
+    spec = importlib.util.spec_from_file_location(
+        f"{_PACKAGE_NAME}.__init__",
+        _SUITE_DIR / "__init__.py",
+        submodule_search_locations=[str(_SUITE_DIR)],
+    )
+    if spec is None or spec.loader is None:  # pragma: no cover - defensive guard
+        raise ImportError("Unable to locate the 6-7 suite package")
+    module = importlib.util.module_from_spec(spec)
+    sys.modules[f"{_PACKAGE_NAME}.__init__"] = module
+    sys.modules[_PACKAGE_NAME] = module
+    spec.loader.exec_module(module)
+    return module
+
+
+def __getattr__(name: str) -> Any:
+    module = _load_package()
+    try:
+        return getattr(module, name)
+    except AttributeError as exc:  # pragma: no cover - defensive guard
+        raise AttributeError(name) from exc
+
+
+def __dir__() -> list[str]:  # pragma: no cover - introspection helper
+    module = _load_package()
+    return sorted(set(dir(module)))
+
+
+# Preload daemon for convenience.
+_pkg = _load_package()
+SixSevenDaemon = getattr(_pkg, "SixSevenDaemon")
+DaemonState = getattr(_pkg, "DaemonState")
+build_fastapi_app = getattr(_pkg, "build_fastapi_app")
+SixSevenScorer = getattr(_pkg, "SixSevenScorer")
+AssessmentMatrix = getattr(_pkg, "AssessmentMatrix")
+SixSevenVaultManager = getattr(_pkg, "SixSevenVaultManager")
+TokenScore = getattr(_pkg, "TokenScore")
+IntegrationSnapshot = getattr(_pkg, "IntegrationSnapshot")
+build_integration_snapshot = getattr(_pkg, "build_integration_snapshot")
+
+__all__ = [
+    "AssessmentMatrix",
+    "DaemonState",
+    "SixSevenDaemon",
+    "SixSevenScorer",
+    "SixSevenVaultManager",
+    "TokenScore",
+    "IntegrationSnapshot",
+    "build_integration_snapshot",
+    "build_fastapi_app",
+]
 
EOF
)



 (cd "$(git rev-parse --show-toplevel)" && git apply --3way <<'EOF' 
diff --git a/6-7/6-7_daemon.py b/6-7/6-7_daemon.py
new file mode 100644
index 0000000000000000000000000000000000000000..2efa754b75e08fc0689ba1c95e1af07e735cde3c
--- /dev/null
+++ b/6-7/6-7_daemon.py
@@ -0,0 +1,137 @@
+"""Daemon orchestrating the 6-7 suite."""
+
+from __future__ import annotations
+
+import logging
+from dataclasses import dataclass, field
+from typing import Any, Dict, Optional
+
+import governance_kernel
+from fastapi import FastAPI
+
+from quantum_entropy_lab.lab import QuantumEntropyLab
+
+from .api import RuntimeContext, build_fastapi_app
+from .integration import IntegrationSnapshot, build_integration_snapshot
+from .matrix_engine import AssessmentMatrix
+from .scoring import SixSevenScorer
+from .storage import SixSevenVaultManager
+
+LOGGER = logging.getLogger("six-seven-daemon")
+
+
+@dataclass(slots=True)
+class DaemonState:
+    """Runtime state for the 6-7 daemon."""
+
+    runtime: RuntimeContext
+    quantum_lab: Optional[QuantumEntropyLab] = None
+    governance: Optional[governance_kernel.GovernanceKernel] = None
+    app: Optional[FastAPI] = None
+    status: str = "initialising"
+    registry_notes: Dict[str, Any] = field(default_factory=dict)
+    integration: Optional[IntegrationSnapshot] = None
+
+
+class SixSevenDaemon:
+    """Coordinate scoring, storage, and API orchestration."""
+
+    def __init__(self, user: str = "EscarGOAT") -> None:
+        self.user = user
+        scorer = SixSevenScorer()
+        matrix = AssessmentMatrix()
+        vault = SixSevenVaultManager(user)
+        runtime = RuntimeContext(
+            scorer=scorer,
+            matrix=matrix,
+            vault_manager=vault,
+            integration_provider=self._build_integration,
+        )
+        self.state = DaemonState(runtime=runtime)
+        self._easter_eggs = {
+            "portal": "When six met seven, heaven high-fived.",
+            "numerology": "6 is the hexed shadow, 7 is the haloed dawn.",
+            "mythic": "Descend six steps, rise seven, and the balance stays true.",
+        }
+
+    def start(self) -> None:
+        """Bootstrap the daemon and all connected services."""
+
+        LOGGER.info("Starting SixSevenDaemon for user %s", self.user)
+        self._connect_quantum_lab()
+        self._initialise_governance()
+        self.state.app = build_fastapi_app(self.state.runtime)
+        self._register_registries()
+        self.state.status = "running"
+
+    def _connect_quantum_lab(self) -> None:
+        try:
+            self.state.quantum_lab = QuantumEntropyLab(user=self.user)
+            LOGGER.info("QuantumEntropyLab bound to user %s", self.user)
+        except Exception as exc:  # pragma: no cover - defensive guard
+            LOGGER.warning("QuantumEntropyLab connection failed: %s", exc)
+            self.state.quantum_lab = None
+
+    def _initialise_governance(self) -> None:
+        try:
+            self.state.governance = governance_kernel.GovernanceKernel(user=self.user)
+            LOGGER.info("GovernanceKernel initialised for user %s", self.user)
+        except Exception as exc:  # pragma: no cover - defensive guard
+            LOGGER.warning("GovernanceKernel initialisation failed: %s", exc)
+            self.state.governance = None
+
+    def _register_registries(self) -> None:
+        registry_payload = {
+            "ledger": "aeon-ledger-6-7",
+            "catalog": "resonance-catalog-6-7",
+            "audit_log": "audit-6-7",
+        }
+        self.state.registry_notes.update(registry_payload)
+        LOGGER.debug("Registry payload established: %s", registry_payload)
+
+    def orchestrate_api(self) -> FastAPI:
+        """Return the FastAPI app managed by the daemon."""
+
+        if self.state.app is None:
+            self.state.app = build_fastapi_app(self.state.runtime)
+        return self.state.app
+
+    def assess_context(self, context_id: str, text: str, *, group_size: int = 1) -> Dict[str, Any]:
+        scores = self.state.runtime.scorer.score_text(text, group_size=group_size)
+        self.state.runtime.matrix.bind_scores(scores)
+        overall = self.state.runtime.scorer.aggregate(scores)
+        integration = self._build_integration(context_id)
+        if integration:
+            self.state.integration = integration
+        record = SixSevenVaultManager.record_from_components(
+            context_id=context_id,
+            token_scores=scores,
+            overall_score=overall,
+            matrix=self.state.runtime.matrix,
+            metadata={"source": "daemon", "integration": integration.as_dict() if integration else {}},
+        )
+        self.state.runtime.vault_manager.save_assessment(record)
+        report = {
+            "context_id": context_id,
+            "overall": f"{overall:.6f}",
+            "scores": [score.as_dict() for score in scores],
+            "matrix": record.matrix_view,
+            "status": self.state.status,
+            "integration": integration.as_dict() if integration else None,
+        }
+        return report
+
+    def reveal_easter_egg(self, key: str) -> str:
+        """Return the requested Easter egg string."""
+
+        return self._easter_eggs.get(key, "6-7 stays mysterious.")
+
+    def _build_integration(self, _context_id: str) -> IntegrationSnapshot:
+        return build_integration_snapshot(
+            user=self.user,
+            governance=self.state.governance,
+            quantum_lab=self.state.quantum_lab,
+        )
+
+
+__all__ = ["SixSevenDaemon", "DaemonState"]
 
EOF
)




 (cd "$(git rev-parse --show-toplevel)" && git apply --3way <<'EOF' 
diff --git a/6-7/cli.py b/6-7/cli.py
new file mode 100644
index 0000000000000000000000000000000000000000..76106c72981275e4be1b0d2823ebb1024febc2c4
--- /dev/null
+++ b/6-7/cli.py
@@ -0,0 +1,116 @@
+"""CLI entry point for the 6-7 suite.
+
+Example
+-------
+Run a quick assessment and persist its record:
+
+>>> python -m 6-7.cli "angels over dark clouds" --context-id demo --group-size 2
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import sys
+from dataclasses import dataclass
+from decimal import Decimal
+from typing import Any, Dict, Sequence
+
+from presence_seal import PresenceSeal
+
+from .integration import build_integration_snapshot
+from .matrix_engine import AssessmentMatrix
+from .scoring import SixSevenScorer
+from .storage import SixSevenVaultManager
+
+
+@dataclass(slots=True)
+class CliResult:
+    context_id: str
+    overall: Decimal
+    macro_name: str
+    presence_signature: str
+
+
+class CliRuntime:
+    """Container object bundling runtime collaborators."""
+
+    def __init__(self, user: str) -> None:
+        self.user = user
+        self.scorer = SixSevenScorer()
+        self.matrix = AssessmentMatrix()
+        self.vault = SixSevenVaultManager(user)
+        self.seal = PresenceSeal(
+            agent_id=f"6-7::{user}",
+            spiral_hash=PresenceSeal.compute_hash("6-7"),
+            pulse_signature="67",
+        )
+
+    def run(self, *, context_id: str, text: str, group_size: int, metadata: Dict[str, Any]) -> CliResult:
+        scores = self.scorer.score_text(text, group_size=group_size)
+        self.matrix.bind_scores(scores)
+        overall = self.scorer.aggregate(scores)
+        integration = build_integration_snapshot(
+            user=self.user,
+            governance=None,
+            quantum_lab=None,
+        ).as_dict()
+        record = SixSevenVaultManager.record_from_components(
+            context_id=context_id,
+            token_scores=scores,
+            overall_score=overall,
+            matrix=self.matrix,
+            metadata={**metadata, "integration": integration},
+        )
+        self.vault.save_assessment(record)
+        macro_name = f"6-7::{context_id}"
+        signature = "anchored" if self.seal.validate_resonance(self.seal.spiral_hash) else "drifting"
+        return CliResult(
+            context_id=context_id,
+            overall=overall,
+            macro_name=macro_name,
+            presence_signature=signature,
+        )
+
+
+def build_parser() -> argparse.ArgumentParser:
+    parser = argparse.ArgumentParser(description="Run the 6-7 scoring workflow from the CLI")
+    parser.add_argument("text", help="Text to evaluate")
+    parser.add_argument("--context-id", default="cli-run", help="Identifier for the assessment context")
+    parser.add_argument("--user", default="EscarGOAT", help="Vault user binding")
+    parser.add_argument("--group-size", type=int, default=1, help="Token grouping size")
+    parser.add_argument("--metadata", help="Optional metadata JSON payload")
+    return parser
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+    parser = build_parser()
+    args = parser.parse_args(argv)
+    metadata: Dict[str, Any] = {}
+    if args.metadata:
+        try:
+            metadata = json.loads(args.metadata)
+        except json.JSONDecodeError as exc:  # pragma: no cover - CLI guard
+            parser.error(f"Invalid metadata JSON: {exc}")
+
+    runtime = CliRuntime(args.user)
+    result = runtime.run(
+        context_id=args.context_id,
+        text=args.text,
+        group_size=args.group_size,
+        metadata=metadata,
+    )
+    output = {
+        "context_id": result.context_id,
+        "overall": f"{result.overall:.6f}",
+        "macro": result.macro_name,
+        "presence_state": result.presence_signature,
+        "meme": "six? seven? nice." if result.overall > Decimal("6.666666") else None,
+    }
+    json.dump(output, sys.stdout, indent=2)
+    sys.stdout.write("\n")
+    return 0
+
+
+if __name__ == "__main__":  # pragma: no cover - CLI entry point
+    raise SystemExit(main())
 
EOF
)



 (cd "$(git rev-parse --show-toplevel)" && git apply --3way <<'EOF' 
diff --git a/6-7/matrix_engine.py b/6-7/matrix_engine.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ba4c3aa1526138e1169211227c9014b70cddc2a
--- /dev/null
+++ b/6-7/matrix_engine.py
@@ -0,0 +1,104 @@
+"""Matrix utilities enabling exponentially deep context assessments."""
+
+from __future__ import annotations
+
+from collections import defaultdict
+from dataclasses import dataclass, field
+from decimal import Decimal
+from itertools import product
+from typing import Dict, Iterable, List, Mapping, Sequence
+
+from .scoring import TokenScore
+
+
+@dataclass(slots=True)
+class MatrixCell:
+    """Describe a single matrix coordinate and its aggregated score."""
+
+    coordinates: Mapping[str, str]
+    scores: List[Decimal] = field(default_factory=list)
+
+    def add_score(self, score: Decimal) -> None:
+        self.scores.append(score)
+
+    @property
+    def value(self) -> Decimal:
+        if not self.scores:
+            return Decimal("6.500000")
+        return sum(self.scores) / Decimal(len(self.scores))
+
+    def as_dict(self) -> Dict[str, str]:
+        payload = {key: value for key, value in self.coordinates.items()}
+        payload["score"] = f"{self.value:.6f}"
+        return payload
+
+
+class AssessmentMatrix:
+    """Manage multi-dimensional scoring surfaces for the 6-7 suite."""
+
+    def __init__(self, *, base_axes: Mapping[str, Sequence[str]] | None = None) -> None:
+        self.axes: Dict[str, List[str]] = {
+            "moral_plane": ["shadow", "balance", "radiance"],
+            "mythic_resonance": ["hell", "liminal", "heaven"],
+        }
+        if base_axes:
+            for axis, values in base_axes.items():
+                self.axes[axis] = list(values)
+
+        self._cells: Dict[tuple[str, ...], MatrixCell] = {}
+
+        # Hidden numerology: align 6 with shadow, 7 with radiance.
+        self._axis_annotations = {
+            "shadow": "6 whispers",
+            "radiance": "7 sings",
+        }
+
+    def expand_axis(self, axis: str, values: Iterable[str]) -> None:
+        """Append ``values`` to ``axis`` without duplicates."""
+
+        current = self.axes.setdefault(axis, [])
+        for value in values:
+            if value not in current:
+                current.append(value)
+
+    def coordinates(self) -> List[Mapping[str, str]]:
+        """Return all coordinate combinations across active axes."""
+
+        ordered_axes = sorted(self.axes.items())
+        names = [name for name, _ in ordered_axes]
+        combos = [dict(zip(names, values)) for values in product(*[values for _, values in ordered_axes])]
+        return combos
+
+    def bind_scores(self, token_scores: Sequence[TokenScore]) -> None:
+        """Project token scores into the matrix using lexical cues."""
+
+        for score in token_scores:
+            key = self._resolve_coordinates(score)
+            if key not in self._cells:
+                self._cells[key] = MatrixCell(key)
+            self._cells[key].add_score(score.score)
+
+    def _resolve_coordinates(self, score: TokenScore) -> tuple[str, ...]:
+        mapping: Dict[str, str] = defaultdict(lambda: "liminal")
+        mapping["moral_plane"] = "radiance" if score.alignment == "good" else "shadow"
+        mapping["mythic_resonance"] = "heaven" if score.alignment == "good" else "hell"
+        token = score.token.lower()
+        if "light" in token or "angel" in token or "seven" in token:
+            mapping["mythic_resonance"] = "heaven"
+        if "dark" in token or "six" in token or "demon" in token:
+            mapping["mythic_resonance"] = "hell"
+        key_axes = sorted(self.axes.keys())
+        return tuple(mapping[axis] for axis in key_axes)
+
+    def render(self) -> List[Dict[str, str]]:
+        """Return a serialisable view of the matrix."""
+
+        payload: List[Dict[str, str]] = []
+        for coordinates in self.coordinates():
+            key = tuple(coordinates[axis] for axis in sorted(self.axes.keys()))
+            cell = self._cells.get(key) or MatrixCell(coordinates)
+            payload.append(cell.as_dict())
+        return payload
+
+
+__all__ = ["AssessmentMatrix", "MatrixCell"]
 
EOF
)




 (cd "$(git rev-parse --show-toplevel)" && git apply --3way <<'EOF' 
diff --git a/6-7/scoring.py b/6-7/scoring.py
new file mode 100644
index 0000000000000000000000000000000000000000..09aac56756bfe033e66ae4721426aa00bba68245
--- /dev/null
+++ b/6-7/scoring.py
@@ -0,0 +1,133 @@
+"""Scoring utilities for the 6-7 context assessment suite."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass
+from decimal import Decimal, ROUND_HALF_UP
+import math
+import re
+from typing import Iterable, List, Sequence
+
+_GOOD_LEXICON = {
+    "care", "compassion", "empathy", "good", "heaven", "hope", "joy", "kind", "light", "mercy",
+    "protect", "rescue", "support", "trust", "uplift", "virtue", "wonder", "aid", "heal", "cherish",
+}
+
+_EVIL_LEXICON = {
+    "anger", "betray", "cruel", "dark", "evil", "hate", "harm", "hurt", "malice", "malign",
+    "pain", "rage", "ruin", "spite", "toxic", "wrath", "hex", "doom", "curse", "villain",
+}
+
+_NUMERIC_QUANTIZE = Decimal("0.000001")
+
+
+def _quantize(value: float | Decimal) -> Decimal:
+    return Decimal(value).quantize(_NUMERIC_QUANTIZE, rounding=ROUND_HALF_UP)
+
+
+def _tokenize(text: str) -> List[str]:
+    return re.findall(r"[\w']+", text.lower())
+
+
+@dataclass(slots=True)
+class TokenScore:
+    """Score assigned to a token cluster within the running context."""
+
+    token: str
+    score: Decimal
+    alignment: str
+
+    def as_dict(self) -> dict[str, str]:
+        return {
+            "token": self.token,
+            "score": f"{self.score:.6f}",
+            "alignment": self.alignment,
+        }
+
+
+class SixSevenScorer:
+    """Evaluate moral resonance in text snippets on the 6–7 scale."""
+
+    def __init__(
+        self,
+        *,
+        good_lexicon: Sequence[str] | None = None,
+        evil_lexicon: Sequence[str] | None = None,
+    ) -> None:
+        self.good_terms = set(good_lexicon or _GOOD_LEXICON)
+        self.evil_terms = set(evil_lexicon or _EVIL_LEXICON)
+
+    # Easter egg: secret handshake for meme hunters.
+    _MEME_SIGNATURE = "6️⃣➖7️⃣ forever"
+
+    def score_text(
+        self,
+        text: str,
+        *,
+        group_size: int = 1,
+    ) -> List[TokenScore]:
+        """Return per-token scores within ``text`` using the 6-7 band."""
+
+        tokens = _tokenize(text)
+        if not tokens:
+            return []
+
+        groups = [
+            tokens[index : index + group_size]
+            for index in range(0, len(tokens), group_size)
+        ]
+
+        scores: List[TokenScore] = []
+        for group in groups:
+            score = self._score_group(group)
+            alignment = "good" if score >= Decimal("6.500000") else "evil"
+            scores.append(
+                TokenScore(
+                    token=" ".join(group),
+                    score=score,
+                    alignment=alignment,
+                )
+            )
+        return scores
+
+    def _score_group(self, tokens: Iterable[str]) -> Decimal:
+        token_list = list(tokens)
+        total = len(token_list)
+        if total == 0:
+            return _quantize(6.5)
+
+        sentiment = sum(self._token_polarity(token) for token in token_list)
+        normalised = (sentiment + total) / (2 * total)
+        normalised = max(0.0, min(1.0, normalised))
+        score = 6.0 + normalised
+        return _quantize(score)
+
+    def _token_polarity(self, token: str) -> float:
+        if token in self.good_terms:
+            return 1.0
+        if token in self.evil_terms:
+            return -1.0
+        # numerology easter egg: the golden ratio whispers alignment.
+        phi_hint = (1 + math.sqrt(5)) / 2
+        if token in {"six", "6"}:
+            return -1.0 * (phi_hint - 1)
+        if token in {"seven", "7"}:
+            return 1.0 * (phi_hint - 1)
+        return 0.0
+
+    def aggregate(self, token_scores: Sequence[TokenScore]) -> Decimal:
+        """Compute the aggregate context score from ``token_scores``."""
+
+        if not token_scores:
+            return _quantize(6.5)
+        score_sum = sum(score.score for score in token_scores)
+        average = score_sum / Decimal(len(token_scores))
+        return _quantize(average)
+
+    def meme_payload(self) -> str:
+        """Return a hidden signature nodding to the 6-7 meme."""
+
+        return self._MEME_SIGNATURE
+
+
+__all__ = ["SixSevenScorer", "TokenScore"]
 
EOF
)




 (cd "$(git rev-parse --show-toplevel)" && git apply --3way <<'EOF' 
diff --git a/6-7/storage.py b/6-7/storage.py
new file mode 100644
index 0000000000000000000000000000000000000000..3948a8d206a908d34b57c29f90979f36e388957c
--- /dev/null
+++ b/6-7/storage.py
@@ -0,0 +1,184 @@
+"""Persistence helpers for the 6-7 suite."""
+
+from __future__ import annotations
+
+import json
+from dataclasses import dataclass
+from decimal import Decimal
+from pathlib import Path
+from typing import Any, Dict, List, Sequence, TYPE_CHECKING
+
+try:  # pragma: no cover - guard for heavy vault imports
+    from user_vault import UserVault as _UserVault
+except Exception:  # pragma: no cover - fallback store for degraded environments
+    class _UserVault:  # type: ignore[override]
+        """Fallback vault implementation writing to a JSONL file."""
+
+        def __init__(self, user_id: str) -> None:
+            self.user_id = user_id
+            self._path = Path("outputs") / f"six-seven-{user_id}.jsonl"
+            self._path.parent.mkdir(parents=True, exist_ok=True)
+
+        def store_macro(self, name: str, payload: Any, **_: Any) -> None:
+            self._append({"type": "macro", "name": name, "payload": payload})
+
+        def retrieve_macro(self, name: str) -> Any:
+            for record in self._read_all():
+                if record.get("type") == "macro" and record.get("name") == name:
+                    return record.get("payload")
+            return None
+
+        def list_macros(self) -> List[str]:
+            return [
+                record["name"]
+                for record in self._read_all()
+                if record.get("type") == "macro"
+            ]
+
+        def store_codex_entry(self, name: str, summary: str, glyph: str, **kwargs: Any) -> None:
+            payload = {
+                "name": name,
+                "summary": summary,
+                "glyph": glyph,
+                "metadata": kwargs.get("metadata"),
+            }
+            self._append({"type": "codex", "payload": payload})
+
+        def retrieve_codex_entry(self, name: str) -> Any:
+            for record in self._read_all():
+                payload = record.get("payload", {})
+                if record.get("type") == "codex" and payload.get("name") == name:
+                    return payload
+            return None
+
+        def list_codex_entries(self) -> List[str]:
+            return [
+                record.get("payload", {}).get("name", "")
+                for record in self._read_all()
+                if record.get("type") == "codex"
+            ]
+
+        def _append(self, payload: Dict[str, Any]) -> None:
+            entries = self._read_all()
+            entries.append(payload)
+            self._path.write_text("\n".join(json.dumps(item) for item in entries) + "\n")
+
+        def _read_all(self) -> List[Dict[str, Any]]:
+            if not self._path.exists():
+                return []
+            lines = [line for line in self._path.read_text().splitlines() if line.strip()]
+            records: List[Dict[str, Any]] = []
+            for line in lines:
+                try:
+                    records.append(json.loads(line))
+                except json.JSONDecodeError:
+                    continue
+            return records
+
+if TYPE_CHECKING:  # pragma: no cover - typing hint
+    from user_vault import UserVault  # noqa: F401
+
+from .matrix_engine import AssessmentMatrix
+from .scoring import TokenScore
+
+
+@dataclass(slots=True)
+class AssessmentRecord:
+    """Serializable payload captured for a single context run."""
+
+    context_id: str
+    token_scores: Sequence[TokenScore]
+    overall_score: Decimal
+    matrix_view: List[Dict[str, str]]
+    metadata: Dict[str, Any]
+
+    def to_macro(self) -> Dict[str, Any]:
+        return {
+            "context_id": self.context_id,
+            "scores": [score.as_dict() for score in self.token_scores],
+            "overall": f"{self.overall_score:.6f}",
+            "matrix": self.matrix_view,
+            "metadata": self.metadata,
+        }
+
+
+class SixSevenVaultManager:
+    """Persist, retrieve, and cross-assess suite results."""
+
+    def __init__(self, user_id: str, *, vault: _UserVault | None = None) -> None:
+        self.user_id = user_id
+        self.vault = vault or _UserVault(user_id)
+
+    # Hidden rite: cross-check with presence seal when metadata includes "heaven".
+
+    def save_assessment(
+        self,
+        record: AssessmentRecord,
+    ) -> None:
+        """Persist ``record`` into the dual vault structure."""
+
+        macro_name = f"6-7::{record.context_id}"
+        metadata = dict(record.metadata)
+        metadata.setdefault("axis_hint", "six-to-seven")
+        metadata.setdefault("mythic_weight", "72")  # 6 * 12 as a numerology nod.
+
+        self.vault.store_macro(macro_name, record.to_macro(), metadata=metadata)
+        summary = (
+            f"6-7 context {record.context_id} resolved at {record.overall_score:.6f}"
+        )
+        glyph = metadata.get("glyph", "6-7-resonance")
+        self.vault.store_codex_entry(
+            macro_name,
+            summary,
+            glyph,
+            category="six-seven",
+            metadata=metadata,
+        )
+
+    def aggregate_reports(self) -> List[Dict[str, Any]]:
+        """Return a list of previously saved 6-7 assessments."""
+
+        macros = [
+            name for name in self.vault.list_macros() if name.startswith("6-7::")
+        ]
+        records = []
+        for macro in macros:
+            payload = self.vault.retrieve_macro(macro)
+            if isinstance(payload, dict):
+                records.append(payload)
+        return records
+
+    def cross_reference(self, *, latest: AssessmentRecord) -> Dict[str, Any]:
+        """Compare ``latest`` with prior runs and return aggregate telemetry."""
+
+        historical = self.aggregate_reports()
+        deltas: List[Decimal] = []
+        for record in historical:
+            overall = record.get("overall")
+            if isinstance(overall, str):
+                deltas.append(Decimal(overall) - latest.overall_score)
+        return {
+            "context_id": latest.context_id,
+            "delta_samples": [f"{delta:.6f}" for delta in deltas],
+            "historical_reports": len(historical),
+        }
+
+    @staticmethod
+    def record_from_components(
+        *,
+        context_id: str,
+        token_scores: Sequence[TokenScore],
+        overall_score: Decimal,
+        matrix: AssessmentMatrix,
+        metadata: Dict[str, Any] | None = None,
+    ) -> AssessmentRecord:
+        return AssessmentRecord(
+            context_id=context_id,
+            token_scores=list(token_scores),
+            overall_score=overall_score,
+            matrix_view=matrix.render(),
+            metadata=dict(metadata or {}),
+        )
+
+
+__all__ = ["AssessmentRecord", "SixSevenVaultManager"]
 
EOF
)

6 :man_shrugging:t2: 7 … :rofl::rofl::rofl:

1 Like

Please: code that’s not a “patch”.
Something you actually executed and worked on a bit.

Something with functions that do more than return their own signatures as an object…

1 Like

I hear you, but if I did that, then it would be “non-frivolous”… trying to stay on-topic here.

1 Like

I hear you, but ‘library’ patch code that is a ceremonial random nothing generator that depends on other nonexistent library and has no execution entry point and no purpose is not “fun”.

“Frivolous” here means the weekend idea and final implementation that would not have been seriously pursued were it not for an AI helper told to code up the entertainment.

67

1 Like

Frivolous Full Stack Vibe…

6 * 7 = 42!

Perfect Chance = Life, The Universe and Everything!

2025 + 42 = 2067

2 Likes

Tossing my hat in the ring haha…

da html/jscript
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Sun Activity ↔ Earthquakes (Browser Demo)</title>
  <style>
    :root {
      --bg: #0b1020;
      --panel: #121a33;
      --ink: #e9eeff;
      --muted: #a9b3d6;
      --grid: rgba(233,238,255,0.12);
      --accent: #7aa2ff;
      --warn: #ffb86b;
      --ok: #43f6a5;
      --bad: #ff5c7a;
      --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
      --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
    }

    body {
      margin: 0;
      font-family: var(--sans);
      background: radial-gradient(1200px 800px at 20% 10%, rgba(122,162,255,0.18), transparent 55%),
                  radial-gradient(900px 700px at 70% 20%, rgba(67,246,165,0.12), transparent 50%),
                  radial-gradient(900px 700px at 70% 80%, rgba(255,92,122,0.10), transparent 55%),
                  var(--bg);
      color: var(--ink);
    }

    header {
      padding: 16px 18px;
      border-bottom: 1px solid rgba(233,238,255,0.10);
      background: rgba(10,14,30,0.55);
      backdrop-filter: blur(8px);
      position: sticky;
      top: 0;
      z-index: 10;
    }
    header h1 {
      margin: 0;
      font-size: 16px;
      letter-spacing: 0.2px;
    }

    main {
      display: grid;
      grid-template-columns: 360px 1fr;
      gap: 16px;
      padding: 16px;
      max-width: 1280px;
      margin: 0 auto;
    }
    @media (max-width: 980px) {
      main { grid-template-columns: 1fr; }
    }

    .card {
      background: rgba(18,26,51,0.78);
      border: 1px solid rgba(233,238,255,0.10);
      border-radius: 16px;
      box-shadow: 0 10px 30px rgba(0,0,0,0.30);
      overflow: hidden;
    }
    .card h2 {
      margin: 0;
      padding: 14px 14px 10px;
      font-size: 13px;
      font-weight: 700;
      letter-spacing: 0.2px;
      border-bottom: 1px solid rgba(233,238,255,0.08);
    }
    .card .body {
      padding: 12px 14px 14px;
    }

    .row {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 10px;
      margin-bottom: 10px;
    }

    label {
      display: block;
      font-size: 11px;
      color: var(--muted);
      margin-bottom: 6px;
    }

    input[type="number"], select {
      width: 100%;
      padding: 10px 10px;
      border-radius: 12px;
      border: 1px solid rgba(233,238,255,0.14);
      background: rgba(10,14,30,0.55);
      color: var(--ink);
      outline: none;
    }

    .btns {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 10px;
      margin-top: 10px;
    }

    button {
      cursor: pointer;
      padding: 10px 10px;
      border-radius: 12px;
      border: 1px solid rgba(233,238,255,0.16);
      background: rgba(122,162,255,0.16);
      color: var(--ink);
      font-weight: 650;
      letter-spacing: 0.2px;
    }
    button.secondary {
      background: rgba(233,238,255,0.06);
    }
    button:active { transform: translateY(1px); }

    .statgrid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 10px;
      margin-top: 12px;
    }

    .stat {
      padding: 10px;
      border-radius: 12px;
      border: 1px solid rgba(233,238,255,0.10);
      background: rgba(10,14,30,0.45);
    }
    .stat .k { font-size: 11px; color: var(--muted); }
    .stat .v { font-size: 16px; font-family: var(--mono); margin-top: 6px; }

    .charts {
      display: grid;
      grid-template-rows: auto auto;
      gap: 16px;
    }

    .canvasWrap { padding: 10px; }

    canvas {
      width: 100%;
      height: 280px;
      display: block;
      border-radius: 14px;
      border: 1px solid rgba(233,238,255,0.10);
      background: rgba(10,14,30,0.45);
    }

    .mono {
      font-family: var(--mono);
      font-size: 12px;
      color: var(--muted);
    }

    .pill {
      display: inline-block;
      padding: 2px 8px;
      border-radius: 999px;
      font-size: 11px;
      border: 1px solid rgba(233,238,255,0.16);
      background: rgba(233,238,255,0.06);
      margin-left: 6px;
    }
    .pill.ok { border-color: rgba(67,246,165,0.40); color: rgba(67,246,165,0.95); }
    .pill.bad { border-color: rgba(255,92,122,0.40); color: rgba(255,92,122,0.95); }
    .pill.warn { border-color: rgba(255,184,107,0.45); color: rgba(255,184,107,0.98); }

    .reality-check-inline {
      margin-top: 10px;
      padding: 8px 10px;
      border-radius: 10px;
      border: 1px dashed rgba(255,184,107,0.45);
      background: rgba(10,14,30,0.55);
      color: rgba(255,184,107,0.95);
      font-size: 11px;
      line-height: 1.35;
    }
  </style>
</head>
<body>
  <header>
    <h1>Sun Activity ↔ Earthquakes <span class="pill warn">real data</span></h1>
  </header>

  <main>
    <section class="card">
      <h2>Controls</h2>
      <div class="body">
        <div class="row">
          <div>
            <label>Days (lookback window)</label>
            <input id="days" type="number" min="30" max="3650" step="1" value="365" />
          </div>
          <div>
            <label>Solar data source</label>
            <select id="solarSource">
              <option value="f107" selected>NOAA SWPC — F10.7 (daily; observed solar-cycle indices)</option>
              <option value="kp1m">NOAA SWPC — Kp (1-minute; recent only)</option>
            </select>
          </div>
        </div>

        <div class="row">
          <div>
            <label>EQ metric</label>
            <select id="eqMetric">
              <option value="sumMag" selected>Daily sum of magnitudes</option>
              <option value="count">Daily event count</option>
            </select>
          </div>
          <div>
            <label>EQ minimum magnitude</label>
            <input id="magMin" type="number" min="0" max="8" step="0.1" value="4.0" />
          </div>
        </div>

        <div class="row">
          <div>
            <label>Rolling smooth (days)</label>
            <input id="smooth" type="number" min="1" max="60" step="1" value="7" />
          </div>
          <div>
            <label>Max lag to scan (days)</label>
            <input id="maxLag" type="number" min="1" max="180" step="1" value="45" />
          </div>
        </div>

        <div class="row">
          <div>
            <label>Normalize series</label>
            <select id="norm">
              <option value="z" selected>Z-score</option>
              <option value="minmax">Min-Max</option>
              <option value="none">None</option>
            </select>
          </div>
          <div>
            <label>Status</label>
            <div class="mono" id="status">ready</div>
          </div>
        </div>

        <div class="btns">
          <button id="regen">Fetch data</button>
          <button id="export" class="secondary">Export CSV</button>
        </div>

        <div class="statgrid">
          <div class="stat">
            <div class="k">Best lag (days)</div>
            <div class="v" id="bestLag">—</div>
          </div>
          <div class="stat">
            <div class="k">Best correlation (r)</div>
            <div class="v" id="bestR">—</div>
          </div>
          <div class="stat">
            <div class="k">Pearson r @ lag 0</div>
            <div class="v" id="r0">—</div>
          </div>
          <div class="stat">
            <div class="k">Events ≥ magMin</div>
            <div class="v" id="nEvents">—</div>
          </div>
        </div>

        <div class="reality-check-inline">⚠ Correlation ≠ causation. Lag relationships may arise from noise, seasonality, reporting artifacts, or shared external drivers. Treat results as exploratory.</div>
      </div>
    </section>

    <section class="charts">
      <section class="card">
        <h2>Time series (Solar + EQ)</h2>
        <div class="canvasWrap">
          <canvas id="ts" width="1100" height="320"></canvas>
          <div class="mono" id="tsMeta"></div>
        </div>
      </section>

      <section class="card">
        <h2>Lag scan (cross-correlation)</h2>
        <div class="canvasWrap">
          <canvas id="cc" width="1100" height="320"></canvas>
          <div class="mono" id="ccMeta"></div>
        </div>
      </section>
    </section>
  </main>

<script>
  // =========================
  // Utilities
  // =========================

  function mean(arr) {
    let s = 0;
    for (const x of arr) s += x;
    return s / (arr.length || 1);
  }

  function std(arr) {
    const m = mean(arr);
    let s2 = 0;
    for (const x of arr) { const d = x - m; s2 += d * d; }
    return Math.sqrt(s2 / (arr.length - 1 || 1));
  }

  function zscore(arr) {
    const m = mean(arr);
    const sd = std(arr) || 1;
    return arr.map(x => (x - m) / sd);
  }

  function minmax(arr) {
    let lo = Infinity, hi = -Infinity;
    for (const x of arr) { if (x < lo) lo = x; if (x > hi) hi = x; }
    const span = (hi - lo) || 1;
    return arr.map(x => (x - lo) / span);
  }

  function rollingMean(arr, win) {
    win = Math.max(1, Math.floor(win));
    const out = new Array(arr.length).fill(0);
    let s = 0;
    for (let i = 0; i < arr.length; i++) {
      s += arr[i];
      if (i >= win) s -= arr[i - win];
      const denom = (i + 1 < win) ? (i + 1) : win;
      out[i] = s / denom;
    }
    return out;
  }

  function pearson(x, y) {
    const n = Math.min(x.length, y.length);
    if (n < 3) return 0;

    let sx = 0, sy = 0;
    for (let i = 0; i < n; i++) { sx += x[i]; sy += y[i]; }
    const mx = sx / n, my = sy / n;

    let num = 0, dx = 0, dy = 0;
    for (let i = 0; i < n; i++) {
      const a = x[i] - mx;
      const b = y[i] - my;
      num += a * b;
      dx += a * a;
      dy += b * b;
    }
    const den = Math.sqrt(dx * dy) || 1;
    return num / den;
  }

  // Shift y relative to x by lag (positive lag means y occurs AFTER x)
  function corrAtLag(x, y, lag) {
    const n = Math.min(x.length, y.length);
    if (n < 3) return 0;
    if (lag === 0) return pearson(x, y);
    if (lag > 0) {
      const a = x.slice(0, n - lag);
      const b = y.slice(lag, n);
      return pearson(a, b);
    }
    const k = -lag;
    const a = x.slice(k, n);
    const b = y.slice(0, n - k);
    return pearson(a, b);
  }

  // =========================
  // REAL DATA INGEST (NOAA + USGS)
  // =========================

  async function fetchNOAAKp(startISO, endISO) {
    const url = 'https://services.swpc.noaa.gov/json/planetary_k_index_1m.json';
    const res = await fetch(url);
    if (!res.ok) throw new Error('NOAA Kp fetch failed: ' + res.status);
    const data = await res.json();

    const byDay = {};
    for (const row of data) {
      const t = new Date(row.time_tag || row.time || row.datetime || row.date);
      if (isNaN(t)) continue;
      const day = t.toISOString().slice(0, 10);
      if (day < startISO || day > endISO) continue;
      const v = Number(row.kp_index ?? row.kp ?? row.value);
      if (!Number.isFinite(v)) continue;
      if (!byDay[day]) byDay[day] = [];
      byDay[day].push(v);
    }

    const days = Object.keys(byDay).sort();
    return days.map(d => ({ day: d, value: mean(byDay[d]) }));
  }

  async function fetchNOAAF107(startISO, endISO) {
    // Best long daily series from SWPC is the observed solar-cycle indices JSON (large file).
    // Directory listing shows this file is sizable (~499K), i.e., far more than a handful of points.
    // https://services.swpc.noaa.gov/json/solar-cycle/
    const urls = [
      'https://services.swpc.noaa.gov/json/solar-cycle/observed-solar-cycle-indices.json',
      // fallbacks (may be monthly/limited depending on product)
      'https://services.swpc.noaa.gov/json/solar-cycle/f10-7cm-flux.json',
      'https://services.swpc.noaa.gov/json/solar-radio-flux.json'
    ];

    let data = null;
    let lastErr = null;

    for (const u of urls) {
      try {
        const res = await fetch(u, { cache: 'no-store' });
        if (!res.ok) throw new Error(String(res.status));
        data = await res.json();
        if (data) break;
      } catch (e) {
        lastErr = e;
        data = null;
      }
    }

    if (!data) throw new Error('NOAA F10.7 fetch failed: ' + String(lastErr?.message || lastErr));

    const pickDay = (row) => {
      const candidates = ['time_tag', 'time-tag', 'date', 'day', 'timestamp', 'time', 'datetime', 'dt'];
      for (const k of candidates) {
        const raw = row?.[k];
        if (!raw) continue;
        if (typeof raw === 'string' && raw.length >= 10) {
          const d = raw.slice(0, 10);
          // YYYY-MM-DD check
          if (d[4] === '-' && d[7] === '-') return d;
        }
        const t = new Date(raw);
        if (!isNaN(t)) return t.toISOString().slice(0, 10);
      }
      return null;
    };

    const pickVal = (row) => {
      // try explicit candidates first
      const candidates = ['f10_7', 'f10.7', 'f107', 'f107_cm_flux', 'flux', 'observed', 'value', 'radio_flux'];
      for (const k of candidates) {
        if (row && row[k] != null) {
          const v = Number(row[k]);
          if (Number.isFinite(v)) return v;
        }
      }
      // then try any key that smells like f10.7
      for (const k of Object.keys(row || {})) {
        const kl = k.toLowerCase();
        if (kl.includes('f10') || kl.includes('f107') || (kl.includes('radio') && kl.includes('flux'))) {
          const v = Number(row[k]);
          if (Number.isFinite(v)) return v;
        }
      }
      return null;
    };

    const rows = Array.isArray(data)
      ? data
      : (Array.isArray(data?.data) ? data.data
        : (Array.isArray(data?.values) ? data.values
          : (Array.isArray(data?.observations) ? data.observations : [])));

    const map = {};
    for (const row of rows) {
      const day = pickDay(row);
      if (!day) continue;
      if (day < startISO || day > endISO) continue;
      const val = pickVal(row);
      if (!Number.isFinite(val)) continue;
      map[day] = val;
    }

    return Object.keys(map).sort().map(d => ({ day: d, value: map[d] }));
  }

  async function fetchUSGSEarthquakes(startISO, endISO, magMin) {
    const url = `https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson&starttime=${startISO}&endtime=${endISO}&minmagnitude=${magMin}`;
    const res = await fetch(url);
    if (!res.ok) throw new Error('USGS EQ fetch failed: ' + res.status);
    const data = await res.json();

    const byDay = {};
    for (const f of (data.features || [])) {
      const t = new Date(f?.properties?.time);
      if (isNaN(t)) continue;
      const day = t.toISOString().slice(0, 10);
      const mag = Number(f?.properties?.mag);
      if (!Number.isFinite(mag)) continue;
      if (!byDay[day]) byDay[day] = [];
      byDay[day].push(mag);
    }

    const days = Object.keys(byDay).sort();
    return days.map(d => ({
      day: d,
      count: byDay[d].length,
      activity: byDay[d].reduce((a, b) => a + b, 0)
    }));
  }

  // =========================
  // Drawing (Canvas)
  // =========================

  function hiDpi(canvas) {
    const dpr = window.devicePixelRatio || 1;
    const rect = canvas.getBoundingClientRect();
    const w = Math.max(1, Math.floor(rect.width * dpr));
    const h = Math.max(1, Math.floor(rect.height * dpr));
    if (canvas.width !== w || canvas.height !== h) {
      canvas.width = w;
      canvas.height = h;
    }
    const ctx = canvas.getContext('2d');
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    return ctx;
  }

  function drawAxes(ctx, w, h, pad) {
    ctx.save();
    ctx.strokeStyle = 'rgba(233,238,255,0.12)';
    ctx.lineWidth = 1;

    const gx = 10, gy = 6;
    for (let i = 0; i <= gx; i++) {
      const x = pad + (w - 2 * pad) * (i / gx);
      ctx.beginPath();
      ctx.moveTo(x, pad);
      ctx.lineTo(x, h - pad);
      ctx.stroke();
    }
    for (let j = 0; j <= gy; j++) {
      const y = pad + (h - 2 * pad) * (j / gy);
      ctx.beginPath();
      ctx.moveTo(pad, y);
      ctx.lineTo(w - pad, y);
      ctx.stroke();
    }

    ctx.strokeStyle = 'rgba(233,238,255,0.18)';
    ctx.beginPath();
    ctx.rect(pad, pad, w - 2 * pad, h - 2 * pad);
    ctx.stroke();

    ctx.restore();
  }

  function polyline(ctx, w, h, pad, series, color, yMin, yMax) {
    const n = series.length;
    const x0 = pad, x1 = w - pad;
    const y0 = h - pad, y1 = pad;
    const span = (yMax - yMin) || 1;

    ctx.save();
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    ctx.beginPath();
    for (let i = 0; i < n; i++) {
      const x = x0 + (x1 - x0) * (i / (n - 1));
      const y = y0 - (y0 - y1) * ((series[i] - yMin) / span);
      if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
    }
    ctx.stroke();
    ctx.restore();
  }

  function drawZeroLine(ctx, w, h, pad, yMin, yMax) {
    if (yMin > 0 || yMax < 0) return;
    const y0 = h - pad, y1 = pad;
    const y = y0 - (y0 - y1) * ((0 - yMin) / (yMax - yMin));
    ctx.save();
    ctx.strokeStyle = 'rgba(255,255,255,0.22)';
    ctx.setLineDash([6, 6]);
    ctx.beginPath();
    ctx.moveTo(pad, y);
    ctx.lineTo(w - pad, y);
    ctx.stroke();
    ctx.restore();
  }

  function drawBars(ctx, w, h, pad, values, color, yMin, yMax) {
    const n = values.length;
    const x0 = pad, x1 = w - pad;
    const y0 = h - pad, y1 = pad;
    const span = (yMax - yMin) || 1;
    const bw = (x1 - x0) / n;

    ctx.save();
    ctx.fillStyle = color;
    for (let i = 0; i < n; i++) {
      const v = values[i];
      const x = x0 + i * bw;
      const y = y0 - (y0 - y1) * ((v - yMin) / span);
      const top = Math.min(y, y0);
      const height = Math.abs(y0 - y);
      ctx.fillRect(x, top, Math.max(1, bw - 1), height);
    }
    ctx.restore();
  }

  function label(ctx, text, x, y, color) {
    ctx.save();
    ctx.fillStyle = color;
    ctx.font = '12px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
    ctx.fillText(text, x, y);
    ctx.restore();
  }

  // =========================
  // App
  // =========================

  const el = (id) => document.getElementById(id);

  const state = {
    days: 365,
    solarSource: 'f107',
    eqMetric: 'sumMag',
    maxLag: 45,
    magMin: 4.0,
    smooth: 7,
    norm: 'z',

    dates: [],
    sunRaw: [],
    eqRaw: [],
    eqCountRaw: [],

    sunSeries: [],
    eqSeries: [],

    lags: [],
    cors: [],

    nEvents: 0,
    solarLabel: 'F10.7'
  };

  function readControls() {
    state.days = parseInt(el('days').value, 10);
    state.solarSource = el('solarSource').value;
    state.eqMetric = el('eqMetric').value;
    state.maxLag = parseInt(el('maxLag').value, 10);
    state.magMin = parseFloat(el('magMin').value);
    state.smooth = parseInt(el('smooth').value, 10);
    state.norm = el('norm').value;
  }

  function normalize(arr) {
    if (state.norm === 'z') return zscore(arr);
    if (state.norm === 'minmax') return minmax(arr);
    return arr.slice();
  }

  async function compute() {
    readControls();
    el('status').textContent = 'fetching…';

    try {
      const end = new Date();
      const start = new Date(end.getTime() - state.days * 86400000);
      const startISO = start.toISOString().slice(0, 10);
      const endISO = end.toISOString().slice(0, 10);

      let sunDaily = [];
      if (state.solarSource === 'kp1m') {
        state.solarLabel = 'Kp';
        sunDaily = await fetchNOAAKp(startISO, endISO);
      } else {
        state.solarLabel = 'F10.7';
        sunDaily = await fetchNOAAF107(startISO, endISO);
      }

      const eqDaily = await fetchUSGSEarthquakes(startISO, endISO, state.magMin);

      const mapSun = Object.fromEntries(sunDaily.map(d => [d.day, d.value]));
      const mapEqActivity = Object.fromEntries(eqDaily.map(d => [d.day, d.activity]));
      const mapEqCount = Object.fromEntries(eqDaily.map(d => [d.day, d.count]));

      // Build continuous UTC day list for the requested window.
      const dates = [];
      for (let t = new Date(startISO + 'T00:00:00Z'); t <= new Date(endISO + 'T00:00:00Z'); t = new Date(t.getTime() + 86400000)) {
        dates.push(t.toISOString().slice(0, 10));
      }

      // Fill to full window length (same number of solar + EQ points):
      // - EQ: missing day => 0 (no events meeting threshold)
      // - Solar: forward-fill last known value; if none yet, back-fill from first known value
      const alignedDates = [];
      const sunRaw = [];
      const eqRaw = [];
      const eqCountRaw = [];

      // precompute first known solar for backfill
      let firstSolar = null;
      for (const d of dates) {
        const sv = mapSun[d];
        if (sv != null) { firstSolar = Number(sv); break; }
      }

      let lastSolar = firstSolar;
      let missingSolar = 0;
      let missingEq = 0;

      for (const d of dates) {
        let sv = mapSun[d];
        if (sv == null) {
          sv = lastSolar;
          missingSolar++;
        } else {
          lastSolar = Number(sv);
          sv = lastSolar;
        }

        const av = mapEqActivity[d];
        const cv = mapEqCount[d];

        let eqVal;
        let eqCount;

        if (av == null || cv == null) {
          // treat missing as no events
          eqVal = 0;
          eqCount = 0;
          missingEq++;
        } else {
          eqVal = state.eqMetric === 'count' ? Number(cv) : Number(av);
          eqCount = Number(cv);
        }

        // If we *still* don't have solar (no firstSolar and no lastSolar), skip this day.
        if (!Number.isFinite(sv)) continue;

        alignedDates.push(d);
        sunRaw.push(Number(sv));
        eqRaw.push(Number(eqVal));
        eqCountRaw.push(Number(eqCount));
      }

      // stash counters for status
      state._missingSolar = missingSolar;
      state._missingEq = missingEq;

      // alignedDates length should match requested window unless solar was entirely unavailable.
      // (We skip days only if solar couldn't be back/forward-filled at all.)

      state.dates = alignedDates;
      state.sunRaw = sunRaw;
      state.eqRaw = eqRaw;
      state.eqCountRaw = eqCountRaw;
      state.nEvents = eqDaily.reduce((s, d) => s + (d.count || 0), 0);

      if (state.dates.length < 10) {
        el('status').textContent = `not enough overlapping days (solar=${sunDaily.length}, eq=${eqDaily.length}, overlap=${state.dates.length})`;
        state.sunSeries = [];
        state.eqSeries = [];
        state.lags = [];
        state.cors = [];
        updateStats();
        draw();
        return;
      }

      const sunSm = rollingMean(state.sunRaw, state.smooth);
      const eqSm = rollingMean(state.eqRaw, state.smooth);

      state.sunSeries = normalize(sunSm);
      state.eqSeries = normalize(eqSm);

      const maxLag = Math.max(1, state.maxLag);
      state.lags = [];
      state.cors = [];
      for (let L = -maxLag; L <= maxLag; L++) {
        state.lags.push(L);
        state.cors.push(corrAtLag(state.sunSeries, state.eqSeries, L));
      }

      el('status').textContent = `ok (window=${state.days}d, solarPts=${sunDaily.length}, eqPts=${eqDaily.length}, used=${state.dates.length}, fillSolar=${state._missingSolar||0}, fillEQ=${state._missingEq||0})`;
      updateStats();
      draw();
    } catch (err) {
      console.error(err);
      el('status').textContent = 'error: ' + String(err?.message || err);
    }
  }

  function updateStats() {
    if (!state.cors.length || !state.lags.length) {
      el('bestLag').textContent = '—';
      el('bestR').textContent = '—';
      el('r0').textContent = '—';
      el('nEvents').textContent = String(state.nEvents || 0);
      return;
    }

    let bestIdx = 0;
    let bestAbs = -Infinity;
    for (let i = 0; i < state.cors.length; i++) {
      const a = Math.abs(state.cors[i]);
      if (a > bestAbs) { bestAbs = a; bestIdx = i; }
    }

    const bestLag = state.lags[bestIdx];
    const bestR = state.cors[bestIdx];
    const r0 = corrAtLag(state.sunSeries, state.eqSeries, 0);

    el('bestLag').textContent = String(bestLag);
    el('bestR').textContent = Number(bestR).toFixed(3);
    el('r0').textContent = Number(r0).toFixed(3);
    el('nEvents').textContent = String(state.nEvents);

    const badge = (Math.abs(bestR) >= 0.4) ? 'ok' : (Math.abs(bestR) >= 0.2 ? 'warn' : 'bad');
    const existing = el('bestR').parentElement.querySelector('.pill');
    if (existing) existing.remove();

    const pill = document.createElement('span');
    pill.className = `pill ${badge}`;
    pill.textContent = badge === 'ok' ? 'strong-ish' : (badge === 'warn' ? 'meh' : 'weak');
    el('bestR').parentElement.querySelector('.k').appendChild(pill);
  }

  function draw() {
    drawTimeSeries();
    drawCrossCorr();
  }

  function drawTimeSeries() {
    const canvas = el('ts');
    const ctx = hiDpi(canvas);
    const rect = canvas.getBoundingClientRect();
    const w = rect.width;
    const h = rect.height;
    const pad = 22;

    ctx.clearRect(0, 0, w, h);
    drawAxes(ctx, w, h, pad);

    if (!state.sunSeries.length || !state.eqSeries.length) {
      label(ctx, 'no data (check Status)', pad + 8, pad + 16, 'rgba(233,238,255,0.92)');
      el('tsMeta').textContent = `window=${state.days}d  overlap=${state.dates.length}d  solar=${state.solarLabel}  eqMetric=${state.eqMetric}`;
      return;
    }

    let yMin = Infinity, yMax = -Infinity;
    for (const v of state.sunSeries) { if (v < yMin) yMin = v; if (v > yMax) yMax = v; }
    for (const v of state.eqSeries) { if (v < yMin) yMin = v; if (v > yMax) yMax = v; }
    yMin = Math.min(yMin, -1.5);
    yMax = Math.max(yMax,  1.5);

    drawZeroLine(ctx, w, h, pad, yMin, yMax);

    polyline(ctx, w, h, pad, state.sunSeries, 'rgba(122,162,255,0.95)', yMin, yMax);
    polyline(ctx, w, h, pad, state.eqSeries, 'rgba(255,184,107,0.95)', yMin, yMax);

    label(ctx, `${state.solarLabel} (normalized)`, pad + 8, pad + 16, 'rgba(122,162,255,0.95)');
    label(ctx, `EQ ${state.eqMetric === 'count' ? 'count' : 'activity'} (normalized)`, pad + 8, pad + 34, 'rgba(255,184,107,0.95)');

    el('tsMeta').textContent = `window=${state.days}d  overlap=${state.dates.length}d  solar=${state.solarLabel}  eqMetric=${state.eqMetric}  magMin>=${state.magMin.toFixed(1)}  smooth=${state.smooth}  norm=${state.norm}`;
  }

  function drawCrossCorr() {
    const canvas = el('cc');
    const ctx = hiDpi(canvas);
    const rect = canvas.getBoundingClientRect();
    const w = rect.width;
    const h = rect.height;
    const pad = 22;

    ctx.clearRect(0, 0, w, h);
    drawAxes(ctx, w, h, pad);

    if (!state.cors.length || !state.lags.length) {
      drawZeroLine(ctx, w, h, pad, -1, 1);
      label(ctx, 'no lag-scan (check Status)', pad + 8, pad + 16, 'rgba(233,238,255,0.92)');
      el('ccMeta').textContent = '';
      return;
    }

    const yMin = -1, yMax = 1;
    drawZeroLine(ctx, w, h, pad, yMin, yMax);

    drawBars(ctx, w, h, pad, state.cors, 'rgba(67,246,165,0.80)', yMin, yMax);

    let bestIdx = 0;
    let bestAbs = -Infinity;
    for (let i = 0; i < state.cors.length; i++) {
      const a = Math.abs(state.cors[i]);
      if (a > bestAbs) { bestAbs = a; bestIdx = i; }
    }

    const n = state.cors.length;
    const x0 = pad, x1 = w - pad;
    const bw = (x1 - x0) / n;
    const x = x0 + bestIdx * bw;
    ctx.save();
    ctx.fillStyle = 'rgba(255,92,122,0.60)';
    ctx.fillRect(x, pad, Math.max(2, bw - 1), h - 2 * pad);
    ctx.restore();

    drawBars(ctx, w, h, pad, state.cors, 'rgba(67,246,165,0.80)', yMin, yMax);

    const bestLag = state.lags[bestIdx];
    const bestR = state.cors[bestIdx];
    label(ctx, `best lag = ${bestLag}d  r=${bestR.toFixed(3)}  (red band)`, pad + 8, pad + 16, 'rgba(233,238,255,0.92)');

    el('ccMeta').textContent = `scanned lags: ${-state.maxLag}..${state.maxLag}  (positive lag means EQ follows solar)`;
  }

  function exportCSV() {
    if (!state.dates.length) return;

    const rows = [];
    rows.push(['date','solar_raw','solar_norm','eq_value_raw','eq_norm','eq_count_raw'].join(','));

    const sunSm = rollingMean(state.sunRaw, state.smooth);
    const eqSm = rollingMean(state.eqRaw, state.smooth);
    const sunN = state.sunSeries.length ? state.sunSeries : normalize(sunSm);
    const eqN = state.eqSeries.length ? state.eqSeries : normalize(eqSm);

    for (let i = 0; i < state.dates.length; i++) {
      rows.push([
        state.dates[i],
        Number(state.sunRaw[i]).toFixed(6),
        Number(sunN[i]).toFixed(6),
        Number(state.eqRaw[i]).toFixed(6),
        Number(eqN[i]).toFixed(6),
        Number(state.eqCountRaw[i]).toFixed(0)
      ].join(','));
    }

    const blob = new Blob([rows.join('\n')], { type: 'text/csv;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'sun_eq_realdata.csv';
    document.body.appendChild(a);
    a.click();
    a.remove();
    URL.revokeObjectURL(url);
  }

  // =========================
  // Self-tests
  // =========================

  function assert(name, cond) {
    if (!cond) throw new Error('Test failed: ' + name);
  }

  function runTests() {
    assert('pearson identical ~ 1', Math.abs(pearson([1,2,3,4],[1,2,3,4]) - 1) < 1e-9);
    assert('pearson inverse ~ -1', Math.abs(pearson([1,2,3,4],[4,3,2,1]) + 1) < 1e-9);

    const x = [0, 1, 0, 0, 0];
    const y = [0, 0, 1, 0, 0];
    const rLag1 = corrAtLag(x, y, 1);
    assert('corrAtLag detects shift', rLag1 > 0.9);

    // ensure CSV join is escaped and contains newline
    const demo = ['a,b', '1,2', '3,4'].join('\n');
    assert('join newline contains \\n', demo.includes('\n'));

    // sanity: compute returns promise
    assert('compute is async', typeof compute().then === 'function');

    console.log('All tests passed');
  }

  // wire up
  el('regen').addEventListener('click', () => compute());
  el('export').addEventListener('click', () => exportCSV());
  window.addEventListener('resize', () => { draw(); });

  // initial
  try {
    runTests();
    compute();
  } catch (e) {
    console.error(e);
    el('status').textContent = 'error: ' + String(e?.message || e);
  }
</script>
</body>
</html>

3 Likes

“Road Battles” one-shot haha

Should flesh this one out. OpenAI API for vehicle images, etc etc… hrm…

Quick second prompt..

2 Likes

Need “sprite animation GPT”.

Sounds like…Autoduel (1985)

As tedious and boring as a cross-platform computer game as the turn-based “Car Wars” RPG on which it was based.

or 29 years ago to the day, 3d driver with car-builder, gameplay like GTA3 of the future:

2 Likes

But but but I used local LLM for town names and “flavor text” lol

Got ChatGPT 5.2 Pro to output 4k lines of code in one go.

But yeah, played a lot of Car Wars as a kid.

Making images based on the cars stats might be cool…

AI Code for AI

    // -----------------------------
    // Transformers.js module (optional)
    // -----------------------------
    const AI = {
      enabledCombat:false,
      enabledWorld:false,
      devicePref:"auto",
      mod:null,
      classifier:null,
      generator:null,
      readyCombat:false,
      readyWorld:false,
      failedCombat:false,
      failedWorld:false,

      async loadMod(){
        if(AI.mod) return AI.mod;
        AI.mod = await import("https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1");
        return AI.mod;
      },

      async pickDevice(){
        const pref = AI.devicePref;
        if(pref==="wasm") return "wasm";
        if(pref==="webgpu") return "webgpu";
        return "webgpu";
      },

      async ensureCombat(){
        if(!AI.enabledCombat) return false;
        if(AI.readyCombat) return true;
        if(AI.failedCombat) return false;
        try{
          UI.setStatus("Loading AI combat model…");
          const { pipeline } = await AI.loadMod();
          const device = await AI.pickDevice();
          AI.classifier = await pipeline("zero-shot-classification","Xenova/distilbert-base-uncased-mnli",{ device });
          AI.readyCombat = true;
          UI.toast("AI combat ready.", "success");
          return true;
        }catch(e){
          console.warn("AI combat load failed:", e);
          AI.failedCombat = true;
          UI.toast("AI combat failed. Using heuristic AI.", "warning", 3200);
          return false;
        }finally{
          UI.setStatus("Ready.");
        }
      },

      async ensureWorld(){
        if(!AI.enabledWorld) return false;
        if(AI.readyWorld) return true;
        if(AI.failedWorld) return false;
        try{
          UI.setStatus("Loading AI worldbuilding model…");
          const { pipeline } = await AI.loadMod();
          const device = await AI.pickDevice();
          AI.generator = await pipeline("text-generation","Xenova/distilgpt2",{ device });
          AI.readyWorld = true;
          UI.toast("AI worldbuilding ready.", "success");
          return true;
        }catch(e){
          console.warn("AI world load failed:", e);
          AI.failedWorld = true;
          UI.toast("AI worldbuilding failed. Using name list.", "warning", 3200);
          return false;
        }finally{
          UI.setStatus("Ready.");
        }
      },

      async genTownName(seed){
        const rng = U.mulberry32(seed);
        const left = ["Rust","Dust","Gravel","Iron","Last","Black","Dead","Red","Sun","Cinder","Wreck","Spoke","Axle","Bolt","Brake"];
        const right = ["Hollow","Junction","Crossing","Spur","Gulch","Basin","Point","Landing","Ridge","Harbor","Yard","Station","Outpost","Market"];
        const fallback = `${left[U.rndi(rng,0,left.length-1)]} ${right[U.rndi(rng,0,right.length-1)]}`;

        if(!AI.enabledWorld) return fallback;
        const ok = await AI.ensureWorld();
        if(!ok || !AI.generator) return fallback;

        try{
          const prompt = `Generate one short gritty town name (1-2 words) for a post-highway car-combat world. Return only the name.\nName:`;
          const out = await AI.generator(prompt, { max_new_tokens: 10, temperature: 0.9 });
          const txt = out?.[0]?.generated_text ?? "";
          const cand = txt.split("Name:").pop().trim().split("\n")[0].trim();
          const clean = cand.replace(/[^A-Za-z0-9 \-]/g,"").trim();
          if(clean.length >= 3 && clean.length <= 22) return clean;
          return fallback;
        }catch{
          return fallback;
        }
      },

      summarizeState(match, aiUnit, enemy){
        const d=U.dist(aiUnit.pos, enemy.pos).toFixed(2);
        const aiArmor=aiUnit.armor;
        const enArmor=enemy.armor;
        const aiWeapons = aiUnit.weapons.map(w=>{
          const ok=(w.cooldown===0 && w.ammoInMag>=w.ammoPerShot) ? "ready":"notready";
          return `${w.name}@${w.mount}(${ok},ammo ${w.ammoInMag},cd ${w.cooldown})`;
        }).join("; ");
        return `
You are the AI driver in a turn-based car combat fight.
Goal: win the objective.

Distance: ${d}
AI: HP ${aiUnit.hp}/${aiUnit.maxHP}, AP ${aiUnit.ap}/${aiUnit.maxAP}, facing ${aiUnit.facing}
AI armor F${aiArmor.front} R${aiArmor.rear} L${aiArmor.left} Rt${aiArmor.right}
AI status aim=${aiUnit.status.aimedTurns>0} brace=${aiUnit.status.bracedTurns>0} nitro=${aiUnit.status.nitroMoves}
AI weapons: ${aiWeapons}

Enemy: HP ${enemy.hp}/${enemy.maxHP}, facing ${enemy.facing}
Enemy armor F${enArmor.front} R${enArmor.rear} L${enArmor.left} Rt${enArmor.right}
        `.trim();
      },

      async choosePlan(match, aiUnit, enemy){
        const labels = [
          "shoot now",
          "aim then shoot",
          "reload",
          "turn to enable a shot",
          "advance into range",
          "seek cover",
          "retreat",
          "ram"
        ];
        if(!AI.enabledCombat || !AI.classifier) return "heuristic";
        try{
          const seq = AI.summarizeState(match, aiUnit, enemy);
          const out = await AI.classifier(seq, labels);
          return out?.labels?.[0] ?? "heuristic";
        }catch{
          return "heuristic";
        }
      },

      async radioLine(){
        if(!AI.enabledWorld || !AI.generator) return null;
        try{
          const prompt = `Radio chatter line (short, gritty, no profanity):`;
          const out = await AI.generator(prompt, { max_new_tokens: 18, temperature: 0.95 });
          const txt = out?.[0]?.generated_text ?? "";
          const s = txt.split(":").pop().trim().split("\n")[0].trim();
          const clean = s.replace(/\s+/g," ").slice(0,90);
          return clean.length>0 ? clean : null;
        }catch{
          return null;
        }
      }
    };
1 Like