master
AdityaAngajala Publish 0.3.0 6d39e2e 2mo ago
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Circuit Trace Generators</title>
<style>
  :root { color-scheme: dark; }
  * { box-sizing: border-box; }
  /* Never let the readme scroll sideways. The wiki hosts this doc in an iframe
     it auto-grows to our posted height; any horizontal overflow adds a bottom
     scrollbar whose ~15px gutter then forces a spurious vertical scrollbar too.
     overflow-x: clip stops the sideways scroll without making the root a scroll
     container, so the height handshake below still works. min-width:0 keeps grid
     tracks from blowing out; wide tables/pre still scroll inside their own box. */
  *, *::before, *::after { min-width: 0; }
  html, body { max-width: 100%; overflow-x: clip; }
  img, svg, canvas, video, iframe { max-width: 100%; }
  body {
    margin: 0; background: #0d1117; color: #c9d4e3;
    font: 15px/1.65 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  }
  .wrap { max-width: 1120px; margin: 0 auto; padding: 8px 20px 72px; }
  h1 { font-size: 27px; color: #eaf2ff; letter-spacing: -0.01em; margin: 18px 0 6px; }
  h2 { font-size: 20px; color: #eaf2ff; margin: 40px 0 10px; padding-bottom: 6px; border-bottom: 1px solid #1e2633; }
  h3 { font-size: 16px; color: #dbe6f5; margin: 24px 0 6px; }
  p { margin: 0 0 12px; max-width: 78ch; }
  a { color: #00e6dc; text-decoration: none; }
  a:hover { text-decoration: underline; }
  .accent { color: #00e6dc; }
  .lede { color: #a7b3c4; }
  code { color: #64abff; background: #151b25; padding: 1px 5px; border-radius: 4px; font-size: 13px; }
  pre { background: #0a0e14; border: 1px solid #1e2633; border-radius: 8px; padding: 14px 16px; overflow-x: auto; }
  pre code { color: #c9d4e3; background: none; padding: 0; }
  table { border-collapse: collapse; width: 100%; margin: 8px 0 14px; display: block; overflow-x: auto; }
  th, td { border: 1px solid #1e2633; padding: 7px 11px; text-align: left; font-size: 13.5px; vertical-align: top; }
  th { background: #141b26; color: #dbe6f5; }
  ol, ul { max-width: 78ch; padding-left: 22px; }
  li { margin: 3px 0; }
  .demo { margin: 22px 0 30px; }
  .demo .meta { color: #8b97a8; font-size: 13.5px; margin: 0 0 10px; }
  .frame { width: 100%; height: 820px; border: 1px solid #1e2633;
           border-radius: 12px; background: #0d1117; display: block; overflow: hidden; }
  .open { margin-top: 9px; font-size: 13.5px; }
  .pill { display:inline-block; font-size:11px; letter-spacing:.08em; text-transform:uppercase;
          color:#5ff5e8; background:#0b3d33; border:1px solid #0e5548; border-radius:999px; padding:2px 9px; margin-left:8px; vertical-align:middle; }
  footer { margin-top: 52px; color: #5c6675; font-size: 12.5px; border-top: 1px solid #1e2633; padding-top: 16px; }
  .cols { display:grid; gap:22px; }
  @media (min-width: 900px) { .cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }
</style>
</head>
<body>
<div class="wrap">

  <header>
    <h1>Circuit Trace Generators <span class="pill">live</span></h1>
    <p class="lede">Two dependency-free, seeded SVG generators that draw the Adom
    <strong>Nitrogen</strong> hero background, procedural PCB traces, vias, and routed
    light. All three demos are embedded below. Drag the sliders, switch presets, and
    move the pointer over each board.</p>
  </header>

  <!-- ================= LIVE DEMOS ================= -->
  <section class="demo">
    <h2>1 · Tiling router <span class="accent">·</span> repeating torus tile</h2>
    <p class="meta">Occupancy-grid A* router on a torus. Twelve generation knobs, a cursor-fed signal fountain, and five animation presets. Engine: <code>circuit-background.js</code>.</p>
    <iframe class="frame" src="/adom/circuit-trace-generators/render/src/index-classic.html" title="Tiling router, live interactive demo" scrolling="no" loading="lazy"></iframe>
    <p class="open">▶ <a href="/adom/circuit-trace-generators/render/src/index-classic.html" target="_blank" rel="noopener">Open this demo full screen</a></p>
  </section>

  <section class="demo">
    <h2>2 · Tiling reveal <span class="accent">·</span> progressive route growth</h2>
    <p class="meta">The same torus tile grows through Etch, Flood, Sweep, Bloom, or Scatter, then hands off to the exact static SVG. Entry: <code>circuit-background-reveal.js</code>.</p>
    <iframe class="frame" src="/adom/circuit-trace-generators/render/src/index-reveal.html" title="Tiling reveal, live interactive demo" scrolling="no" loading="lazy"></iframe>
    <p class="open">▶ <a href="/adom/circuit-trace-generators/render/src/index-reveal.html" target="_blank" rel="noopener">Open this demo full screen</a></p>
  </section>

  <section class="demo">
    <h2>3 · Field router <span class="accent">·</span> continuous packed field</h2>
    <p class="meta">Version 9 adds obstacle routing, flow or scroll reveal, append-only continuous generation, and five ambient systems. Entry: <code>circuit-background-v9.js</code>.</p>
    <iframe class="frame" src="/adom/circuit-trace-generators/render/src/index.html" title="Field router, live interactive demo" scrolling="no" loading="lazy"></iframe>
    <p class="open">▶ <a href="/adom/circuit-trace-generators/render/src/index.html" target="_blank" rel="noopener">Open this demo full screen</a></p>
  </section>

  <!-- ================= OVERVIEW ================= -->
  <h2>What they are</h2>
  <p>Both recreate the reference artwork's visual grammar rather than copying its
  path data, and both were measured off the same original:</p>

  <table>
    <tr><th>Reference constant</th><th>Value</th></tr>
    <tr><td>Routing pitch</td><td><code>7.895</code> units (<code>150 / 19</code>)</td></tr>
    <tr><td>Trace stroke</td><td><code>1.58</code> units, <code>#151b25</code></td></tr>
    <tr><td>Via annulus</td><td><code>3.34</code> outer / <code>1.76</code> inner</td></tr>
    <tr><td>Board</td><td><code>#0d1117</code></td></tr>
    <tr><td>Routed-light palette</td><td><code>#00e6dc</code> 60%, <code>#64abff</code> 25%, <code>#c5b3ff</code> 15%</td></tr>
  </table>

  <p>They diverge on the thing that matters most: <strong>how the board is filled.</strong>
  One routes a torus and tiles forever; the other routes one continuous field at the
  mount's real size. Pick by whether you want a repeatable texture or a bespoke backdrop.</p>

  <table>
    <tr><th></th><th>Tiling router</th><th>Field router</th></tr>
    <tr><td>Fill strategy</td><td>one <code>900×450</code> tile, repeats in both axes</td><td>one continuous field, no repeat</td></tr>
    <tr><td>Router</td><td>A* nets between placed terminal formations</td><td>flow field and vortex trace packing</td></tr>
    <tr><td>Wrap</td><td>torus occupancy grid (no seam, no crossings)</td><td>reserved grid nodes (no crossings)</td></tr>
    <tr><td>Headless output</td><td><code>generate-tile.mjs</code> writes an SVG tile</td><td><code>generateCircuitField()</code> returns geometry</td></tr>
    <tr><td>Motion</td><td>signal fountain or progressive reveal</td><td>three reveal modes and five ambient systems</td></tr>
    <tr><td>Entry file</td><td><code>circuit-background.js</code> or <code>circuit-background-reveal.js</code></td><td><code>circuit-background-v9.js</code></td></tr>
  </table>

  <p>Both ship <code>createCircuitBackground(mount, options)</code> returning
  <code>{ regenerate, exportSvg, destroy }</code>, both are seeded (the same seed and
  dimensions give identical geometry), and both honour <code>prefers-reduced-motion</code>.</p>

  <div class="cols">
    <div>
      <!-- ============ TILING ============ -->
      <h2>1 · Tiling router</h2>
      <p><code>circuit-background.js</code>. An occupancy-grid router run <strong>on a
      torus</strong>, so the <code>900×450</code> tile it emits repeats in both axes with
      no visible seam and no two traces ever cross. That makes its output usable directly
      as a CSS <code>background-image</code> with <code>background-repeat: repeat</code>.</p>

      <h3>How a tile is built</h3>
      <ol>
        <li><strong><code>placeFormations</code></strong> scatters terminal groups (via ladders and rows) across the grid. These are the pads everything else routes between.</li>
        <li><strong><code>routeNets</code></strong> runs A* between pins, weighted by a <em>turn penalty</em> (higher means longer, calmer runs) and a <em>weave</em> preference for 2-lane neighbour spacing, with a matching <em>cramp</em> aversion to 1-lane spacing outside bundles.</li>
        <li><strong><code>replicateWalk</code></strong> adds translated-copy bundle followers, so neighbouring traces bend together the way a real bus does.</li>
        <li><strong><code>tapCombs</code></strong> and <strong><code>routeStubs</code></strong> add comb and stub motifs as punctuation.</li>
        <li><strong><code>sealEnd</code></strong> enforces the <strong>via mandate</strong>: every trace starts and ends on a via, trimming blocked ends with per-step claim tracking.</li>
      </ol>

      <h3>Tuning</h3>
      <p>Twelve generation knobs, all sliders in the demo and kebab-case flags on the CLI:
      <code>density</code>, <code>formations</code>, <code>busWidth</code>,
      <code>turnPenalty</code>, <code>weave</code>, <code>cramp</code>,
      <code>horizontalBias</code>, <code>netReach</code>, <code>jogBias</code>,
      <code>stubChance</code>, <code>inlineViaChance</code>, <code>combs</code>.</p>

      <h3>Motion: the signal fountain</h3>
      <p>Every signal is mortal. A passive spawn timer (<code>signalInterval</code>, 240 ms)
      is the fountain: each tick, if the census is under <code>maxSignals</code> (12), a new
      signal appears near the cursor, runs to a trace-end via, then rolls its chain's
      survival. Passive and clicked chains decay at separate rates (<code>hopSurvival</code>
      0.4 against <code>burstSurvival</code> 0.72). Presets: <strong>None</strong> (default),
      <strong>Fireflies</strong>, <strong>Swarm</strong>, <strong>Cursor sparks</strong>,
      <strong>Slow streams</strong>.</p>

      <h3>Progressive reveal</h3>
      <p><code>circuit-background-reveal.js</code> grows the same geometry with
      <strong>Etch</strong>, <strong>Flood</strong>, <strong>Sweep</strong>,
      <strong>Bloom</strong>, or <strong>Scatter</strong>. A canvas layer draws only
      moving or cooling routes, then clears after the glow reaches zero and reveals
      the exact static SVG beneath it.</p>

      <h3>Headless CLI</h3>
      <pre><code>node generate-tile.mjs --seed traces --out tile.svg
node generate-tile.mjs --seed foo --turn-penalty 0.8 --combs 0 --transparent &gt; tile.svg</code></pre>
      <p>Seed <code>traces</code> yields 334 traces and 482 vias per tile.</p>
    </div>

    <div>
      <!-- ============ FIELD ============ -->
      <h2>2 · Field router</h2>
      <p><code>circuit-background-v9.js</code> builds one continuous field at the
      mount's real dimensions. Version 9 separates geometry, obstacles, reveal, and
      ambient motion so each system can change without regenerating the others.</p>

      <h3>Routing and obstacles</h3>
      <p><code>fill</code>, <code>routeLength</code>, <code>horizontalFlow</code>,
      <code>flowAngle</code>, <code>diagonalFlow</code>, <code>longFlow</code>,
      <code>swirl</code>, <code>swirlScale</code>, <code>jogLength</code>,
      <code>channelAttraction</code>, <code>edgeDensity</code>, <code>featureScale</code>.
      Obstacles can avoid, outline, or outline and wrap rectangles, ellipses, polygons,
      rings, and wordmarks. <code>obstaclePreset: "adom"</code> uses the built-in mark.</p>

      <h3>Reveal</h3>
      <ul>
        <li><strong>Instant</strong> mounts the finished field.</li>
        <li><strong>Flow sweep</strong> grows each via-to-via route on a timed front.</li>
        <li><strong>Scroll weave</strong> unlocks routes from page progress and can append new seeded world sections without moving existing geometry.</li>
      </ul>

      <h3>Ambient systems</h3>
      <ul>
        <li><strong>None</strong> mounts only the static SVG.</li>
        <li><strong>Adom reflection</strong> sends a restrained brand-color sheen across a precomposed trace mask.</li>
        <li><strong>Original conductor</strong> runs currents, nodes, packets, convoys, waves, and pointer wake.</li>
        <li><strong>Routed field</strong> keeps the legacy spatial sweep and bloom system.</li>
        <li><strong>Adom handshake</strong> caps sparse via-to-via request and acknowledgement transactions.</li>
      </ul>

      <h3>API</h3>
      <pre><code>import { createCircuitBackground } from "./circuit-background-v9.js";

const circuit = createCircuitBackground(mount, {
  seed: "product-launch",
  obstaclePreset: "adom",
  obstacleMode: "outline-wrap",
  revealSystem: "scroll",
  continuousGeneration: true,
  animationSystem: "reflection",
  motion: true,
});
circuit.regenerate({ seed: "another-layout" });
const svg = circuit.exportSvg();  // static, no motion layers</code></pre>
      <p><code>generateCircuitField(options)</code> returns deterministic geometry with no
      DOM. Each trace carries <code>points</code>, <code>length</code>,
    </div>
  </div>

  <!-- ================= RUNNING ================= -->
  <h2>Running the demos</h2>
  <p>The demos above are hosted on this page via the wiki render endpoint, so they need
  no container and do not expire. You can also open each on its own:</p>
  <table>
    <tr><th>Surface</th><th>Live on the wiki</th></tr>
    <tr><td>Tiling router</td><td><a href="/adom/circuit-trace-generators/render/src/index-classic.html">render/src/index-classic.html</a></td></tr>
    <tr><td>Tiling reveal</td><td><a href="/adom/circuit-trace-generators/render/src/index-reveal.html">render/src/index-reveal.html</a></td></tr>
    <tr><td>Field router</td><td><a href="/adom/circuit-trace-generators/render/src/index.html">render/src/index.html</a></td></tr>
  </table>
  <p>Locally the generators live in adjacent source directories and run on separate ports:</p>
  <pre><code>cd ~/project/circuit-generator
node serve-classic.mjs   # tiling router, http://localhost:8930

cd ~/project/circuit-generator-codex
./serve.sh               # field router, http://localhost:8124</code></pre>

  <!-- ================= FILE MAP ================= -->
  <h2>File map</h2>
  <p>The two local source directories are combined under <code>src/</code> in this page's repo:</p>
  <table>
    <tr><th>File</th><th>Belongs to</th></tr>
    <tr><td><code>circuit-background.js</code></td><td>tiling router (engine)</td></tr>
    <tr><td><code>circuit-background-reveal.js</code></td><td>tiling router (progressive reveal)</td></tr>
    <tr><td><code>index-classic.html</code>, <code>index-reveal.html</code></td><td>tiling demos</td></tr>
    <tr><td><code>generate-tile.mjs</code></td><td>tiling router (CLI)</td></tr>
    <tr><td><code>circuit-background-v9.js</code></td><td>field router entry</td></tr>
    <tr><td><code>circuit-geometry-v9.js</code>, <code>circuit-obstacles-v9.js</code></td><td>field geometry and obstacles</td></tr>
    <tr><td><code>circuit-reveal-*.js</code>, <code>circuit-animation-*.js</code></td><td>field reveal and ambient motion</td></tr>
    <tr><td><code>index.html</code>, <code>server.js</code>, <code>serve.sh</code></td><td>field router (demo, port 8124)</td></tr>
    <tr><td><code>TILING-REVEAL.md</code>, <code>FIELD-ROUTER.md</code></td><td>full implementation notes</td></tr>
  </table>

  <footer>
    This is the page's interactive readme (<code>readme.html</code>). The plain-text
    version lives in <code>README.md</code> on the Files tab, alongside all source under
    <code>src/</code>.
  </footer>
</div>

<script>
// Tell the wiki's readme-frame how tall we are, so it grows to fit and there is
// no inner scrollbar. The parent accepts { type: "readme-height", height: N }
// and clamps it (120..16000). It checks identity by source, so targetOrigin "*"
// is fine from this sandboxed (origin "null") document.
(function () {
  var last = 0;
  function post() {
    var h = Math.ceil(document.documentElement.scrollHeight);
    if (h === last) return;
    last = h;
    try { parent.postMessage({ type: "readme-height", height: h }, "*"); } catch (e) {}
  }
  window.addEventListener("load", post);
  window.addEventListener("resize", post);
  document.addEventListener("DOMContentLoaded", post);
  if (window.ResizeObserver) { new ResizeObserver(post).observe(document.documentElement); }
  // catch late layout (fonts, iframe chrome) without a live clock
  [100, 400, 1200].forEach(function (t) { setTimeout(post, t); });
})();
</script>
</body>
</html>