<?php
// Die yield & cost simulator — customer-facing page.
// Heavy lifting runs on the ChipFoundryServices compute pool: this page POSTs
// the die geometry, wafer size and defect parameters to /edge/yield (same-origin),
// NGINX routes it to the compute node, the node steps the die grid to count
// gross-die-per-wafer, applies four defect-limited yield models (Poisson, Murphy,
// negative-binomial clustering, Seeds) and computes good-die-per-wafer and
// cost-per-good-die, and returns JSON, and we render it here. Results are
// annotated with explanatory text pulled live from the CFS knowledge base
// (/api.php?q=).
header('Content-Type: text/html; charset=utf-8');
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Die Yield &amp; Cost-per-Good-Die Simulator | ChipFoundryServices</title>
<link rel="stylesheet" href="/cfs_theme.css?v=2">
<meta name="description" content="Run a wafer die-yield and cost simulation in your browser — computed on the ChipFoundryServices compute pool. Explore gross-die-per-wafer packing, the Poisson / Murphy / negative-binomial / Seeds defect-yield models, good-die-per-wafer, packing efficiency and cost-per-good-die for AI-accelerator dies.">
<style>
  :root{ --green:#07C160; --ink:#16181d; --muted:#6b7280; --line:#e6e8eb; --bg:#f5f6f8; --si:#3a4a63; --film:#4f9cf9; --cav:#eef2f7; }
  *{ box-sizing:border-box; }
  body{ margin:0; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; background:var(--bg); color:var(--ink); }
  .wrap{ max-width:1040px; margin:0 auto; padding:24px 16px 56px; }
  h1{ font-size:24px; margin:0 0 4px; }
  .lede{ color:var(--muted); font-size:14px; margin:0 0 20px; line-height:1.5; }
  .lede a{ color:var(--green); text-decoration:none; }
  .grid{ display:grid; grid-template-columns:320px 1fr; gap:20px; align-items:start; }
  @media(max-width:760px){ .grid{ grid-template-columns:1fr; } }
  .card{ background:#fff; border:1px solid var(--line); border-radius:12px; padding:18px; box-shadow:0 1px 3px rgba(0,0,0,.05); }
  label{ display:block; font-size:13px; font-weight:600; margin:14px 0 5px; }
  label:first-of-type{ margin-top:0; }
  .val{ float:right; color:var(--green); font-weight:700; font-variant-numeric:tabular-nums; }
  input[type=range]{ width:100%; accent-color:var(--green); }
  .presets{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:14px; }
  .preset{ font-size:12px; padding:6px 10px; border:1px solid var(--line); border-radius:999px; background:#fafbfc; cursor:pointer; }
  .preset:hover{ border-color:var(--green); color:var(--green); }
  button.run{ width:100%; margin-top:18px; background:var(--green); color:#fff; border:0; border-radius:8px; padding:12px; font-size:15px; font-weight:700; cursor:pointer; }
  button.run:disabled{ opacity:.6; cursor:default; }
  .viz{ display:flex; justify-content:center; background:linear-gradient(#fbfcfd,#f3f5f7); border-radius:10px; padding:10px; margin-bottom:10px; }
  .vizcap{ font-size:12px; color:var(--muted); text-align:center; margin:-4px 0 12px; }
  svg{ max-width:100%; height:auto; }
  table{ width:100%; border-collapse:collapse; margin-top:14px; font-size:14px; }
  td,th{ text-align:left; padding:8px 10px; border-bottom:1px solid var(--line); }
  th{ color:var(--muted); font-weight:600; font-size:12px; text-transform:uppercase; letter-spacing:.03em; }
  td.num{ text-align:right; font-variant-numeric:tabular-nums; font-family:ui-monospace,Menlo,monospace; }
  .verdict{ margin-top:12px; padding:10px 12px; border-radius:8px; font-size:14px; font-weight:600; }
  .node{ margin-top:12px; font-size:13px; color:var(--muted); }
  .node b{ color:var(--green); }
  .kb{ margin-top:20px; }
  .kb h2{ font-size:15px; margin:0 0 4px; }
  .kb .src{ font-size:12px; color:var(--muted); margin:0 0 12px; }
  .kb .src a{ color:var(--green); text-decoration:none; }
  .kb-body{ font-size:14px; line-height:1.6; color:#e2e8f0; }
  .kb-body p{ margin:0 0 10px; }
  .kb-body ul{ margin:0 0 10px; padding-left:20px; }
  .kb-body li{ margin:2px 0; }
  .kb-block{ margin-bottom:16px; }
  .kb-body figure.kb-figure{ margin:18px 0; text-align:center; overflow-x:auto; -webkit-overflow-scrolling:touch; }
  .kb-body figure.kb-figure svg{ max-width:100%; height:auto; }
        /* Professional flowchart (Claude.ai-style grouped stage diagram) */
        .kb-body .cfs-flow {
            display: flex;
            flex-direction: column;
            align-items: stretch;
            gap: 0;
            margin: 22px 0 26px;
            /* Anthropic/Claude.ai UI font — Styrene, NOT the serif answer body */
            font-family: 'Styrene B', 'Styrene A', ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
            -webkit-font-smoothing: antialiased;
        }
        .kb-body .cfs-flow-row {
            display: flex;
            justify-content: center;
            align-items: stretch;
            gap: 18px;
            flex-wrap: wrap;
        }
        .kb-body .cfs-node {
            flex: 1 1 220px;
            max-width: 340px;
            min-height: 62px;
            border-radius: 14px;
            padding: 14px 18px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            gap: 3px;
            border: 1px solid;
            box-sizing: border-box;
        }
        .kb-body .cfs-node-title { font-weight: 600; font-size: 16px; line-height: 1.25; }
        .kb-body .cfs-node-sub { font-size: 13.5px; opacity: .82; line-height: 1.3; }
        /* tones */
        .kb-body .cfs-node.neutral { background: #2b2a27; border-color: #45433d; color: #efeee8; }
        .kb-body .cfs-node.green   { background: #14312a; border-color: #2f6d55; color: #d6f0e2; }
        .kb-body .cfs-node.green .cfs-node-title { color: #8fe3bd; }
        .kb-body .cfs-node.blue { background: #173f68; border-color: #2f74b8; color: #d7e6f7; }
        .kb-body .cfs-node.blue .cfs-node-title { color: #c4ddf7; }
        .kb-body .cfs-node.orange  { background: #4a2317; border-color: #8a3c22; color: #f3d8ca; }
        .kb-body .cfs-node.orange .cfs-node-title { color: #f0b79a; }
        /* connecting arrow */
        .kb-body .cfs-arrow { display: flex; justify-content: center; align-items: center; height: 34px; color: #8a8880; }
        .kb-body .cfs-arrow svg { width: 18px; height: 30px; }
        /* parallel arrows — one under each node column, matching the node row layout */
        .kb-body .cfs-arrow-row { display: flex; justify-content: center; gap: 18px; height: 34px; }
        .kb-body .cfs-arrow-col { flex: 1 1 220px; max-width: 340px; display: flex; justify-content: center; align-items: center; color: #8a8880; }
        .kb-body .cfs-arrow-col svg { width: 18px; height: 30px; }
        /* tiered landscape row: labeled dashed band of peer cards, not a flow step */
        .kb-body .cfs-tier {
            border: 1px dashed #3d3d3a;
            border-radius: 16px;
            padding: 18px 20px 20px;
            margin: 0 0 16px;
        }
        .kb-body .cfs-tier:last-child { margin-bottom: 0; }
        .kb-body .cfs-tier-h {
            font-size: 17px;
            font-weight: 600;
            color: #f2f1ec;
            margin-bottom: 14px;
            font-family: 'Styrene B', 'Styrene A', ui-sans-serif, -apple-system, sans-serif;
        }
        .kb-body .cfs-tier-row {
            display: flex;
            gap: 14px;
            flex-wrap: wrap;
        }
        .kb-body .cfs-tier-row .cfs-node {
            flex: 1 1 150px;
            text-align: center;
        }
        /* chip floorplan row: fixed-function blocks with memory feeder */
        .kb-body .cfs-die {
            border: 1px solid #3d3d3a; border-radius: 18px;
            background: rgba(255,255,255,0.03);
            padding: 20px 22px 22px; margin: 0;
        }
        .kb-body .cfs-die-h {
            text-align: center; font-size: 18px; font-weight: 600; color: #f2f1ec;
            font-family: 'Styrene B','Styrene A', ui-sans-serif, -apple-system, sans-serif;
        }
        .kb-body .cfs-die-note {
            text-align: center; font-size: 14px; color: #b8b7b1; margin: 2px 0 18px;
            font-family: 'Styrene B','Styrene A', ui-sans-serif, -apple-system, sans-serif;
        }
        .kb-body .cfs-die-top { display: flex; align-items: stretch; justify-content: center; gap: 0; }
        .kb-body .cfs-die-top .cfs-node { flex: 1 1 0; text-align: center; margin: 0; }
        .kb-body .cfs-die-in {
            display: flex; align-items: center; gap: 6px; white-space: nowrap;
            color: #b8b7b1; font-size: 14px; padding-right: 10px;
            font-family: 'Styrene B','Styrene A', ui-sans-serif, sans-serif;
        }
        .kb-body .cfs-die-ar, .kb-body .cfs-die-inar { display: flex; align-items: center; padding: 0 10px; color: #8a8880; }
        .kb-body .cfs-die-ar svg, .kb-body .cfs-die-inar svg { width: 34px; height: 16px; flex: 0 0 34px; }
        .kb-body .cfs-die-downs { display: flex; justify-content: space-around; padding: 6px 0 2px; }
        .kb-body .cfs-die-dar { display: flex; color: #8a8880; }
        .kb-body .cfs-die-dar svg { width: 16px; height: 28px; }
        .kb-body .cfs-die-base { margin-top: 2px; }
        .kb-body .cfs-die-base .cfs-node { width: 100%; max-width: 100%; text-align: center; margin: 0; }
        .kb-body .cfs-die-uar { display: flex; justify-content: center; padding: 4px 0; color: #8a8880; }
        .kb-body .cfs-die-uar svg { width: 16px; height: 28px; }
        .kb-body .cfs-die-feed {
            margin: 10px auto 0; max-width: 62%; text-align: center;
            padding: 12px 18px; background: #0f0f0e; border: 1px solid #3d3d3a;
            border-radius: 12px; color: #f2f1ec; font-weight: 600;
            font-family: 'Styrene B','Styrene A', ui-sans-serif, sans-serif;
        }
        /* grouped stage (fab loop) */
        .kb-body .cfs-group {
            background: #3b357a;
            border: 1px solid #4f4894;
            border-radius: 18px;
            padding: 16px 18px 18px;
            margin: 0 auto;
            width: 100%;
            max-width: 720px;
            box-sizing: border-box;
        }
        .kb-body .cfs-group-head { text-align: center; margin-bottom: 14px; }
        .kb-body .cfs-group-title { font-weight: 600; font-size: 17px; color: #eceaff; }
        .kb-body .cfs-group-note { font-size: 13.5px; color: #c3bdf0; margin-top: 2px; }
        .kb-body .cfs-group-grid {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 14px 16px;
        }
        /* loop caption sits as a centered footer BELOW the grid, not wedged between rows */
        .kb-body .cfs-group-loop {
            text-align: center;
            font-size: 13.5px;
            color: #c3bdf0;
            margin-top: 14px;
        }
        /* fab-loop CYCLE: 4 stages in a clockwise ring with internal arrows */
        .kb-body .cfs-cycle {
            display: grid;
            grid-template-columns: 1fr auto 1fr;
            grid-template-rows: auto auto auto;
            grid-template-areas:
                "tl ar tr"
                "lp x  dn"
                "bl al br";
            column-gap: 14px;
            row-gap: 10px;
            align-items: center;
        }
        .kb-body .cfs-cyc { display: flex; }
        .kb-body .cfs-cyc .cfs-node { width: 100%; max-width: 100%; }
        .kb-body .cfs-cyc.cyc-tl { grid-area: tl; }
        .kb-body .cfs-cyc.cyc-tr { grid-area: tr; }
        .kb-body .cfs-cyc.cyc-bl { grid-area: bl; }
        .kb-body .cfs-cyc.cyc-br { grid-area: br; }
        .kb-body .cfs-cyc-ar { display: flex; justify-content: center; align-items: center; color: #9c97c8; }
        .kb-body .cfs-cyc-ar.cyc-r { grid-area: ar; }
        .kb-body .cfs-cyc-ar.cyc-l { grid-area: al; }
        .kb-body .cfs-cyc-ar.cyc-d { grid-area: dn; }
        .kb-body .cfs-cyc-ar.cyc-r svg,
        .kb-body .cfs-cyc-ar.cyc-l svg { width: 30px; height: 16px; }
        .kb-body .cfs-cyc-ar.cyc-d svg { width: 16px; height: 28px; }
        .kb-body .cfs-cyc-loop {
            grid-area: lp;
            text-align: center;
            font-size: 13.5px;
            color: #c3bdf0;
        }
        @media (max-width: 620px) {
            .kb-body .cfs-flow-row { flex-direction: column; align-items: center; gap: 14px; }
            .kb-body .cfs-node { width: 100%; max-width: 100%; }
            .kb-body .cfs-tier-row { flex-direction: column; }
            .kb-body .cfs-tier-row .cfs-node { width: 100%; max-width: 100%; }
            .kb-body .cfs-die-top { flex-direction: column; }
            .kb-body .cfs-die-top .cfs-node { width: 100%; }
            .kb-body .cfs-die-ar { transform: rotate(90deg); align-self: center; }
            .kb-body .cfs-die-in { justify-content: center; padding: 0 0 8px; }
            .kb-body .cfs-die-feed { max-width: 100%; }
            .kb-body .cfs-group-grid { grid-template-columns: 1fr; }
            .kb-body .cfs-arrow-row { flex-direction: column; align-items: center; gap: 0; height: auto; }
            .kb-body .cfs-arrow-col { max-width: 100%; }
            .kb-body .cfs-cycle {
                grid-template-columns: 1fr;
                grid-template-areas: "tl" "tr" "br" "bl" "lp";
                row-gap: 12px;
            }
            .kb-body .cfs-cyc-ar { display: none; }
            .kb-body .cfs-cyc-loop { margin-top: 2px; }
        }

  .kb-body mjx-container[display="true"]{ overflow-x:auto; overflow-y:hidden; max-width:100%; margin:8px 0; }
  .kb-body mjx-container{ max-width:100%; }
  .kb-body .kb-tablewrap{ overflow-x:auto; margin:12px 0; -webkit-overflow-scrolling:touch; border:1px solid #e6e8eb; border-radius:8px; }
  .kb-body table.kb-table{ border-collapse:collapse; width:100%; font-size:13px; background:#fff; }
  .kb-body table.kb-table th, .kb-body table.kb-table td{ border-bottom:1px solid #eef1f4; border-right:1px solid #eef1f4; padding:8px 12px; text-align:left; vertical-align:top; white-space:nowrap; }
  .kb-body table.kb-table th:last-child, .kb-body table.kb-table td:last-child{ border-right:0; }
  .kb-body table.kb-table tbody tr:last-child td{ border-bottom:0; }
  .kb-body table.kb-table th{ background:#f5f6f8; font-weight:700; color:#16181d; text-transform:none; letter-spacing:0; font-size:12px; }
  .kb-body table.kb-table tbody tr:nth-child(even) td{ background:#fafbfc; }
  .api{ margin-top:26px; font-size:13px; color:var(--muted); line-height:1.6; }
  .api code,.api pre{ background:#0f1220; color:#e6e6ef; border-radius:6px; }
  .api code{ padding:1px 6px; font-size:12px; }
  .api pre{ padding:12px; overflow:auto; font-size:12px; }
  .err{ color:#c0392b; font-size:14px; padding:10px 0; }
</style>
<script>
  window.MathJax = {
    tex: { inlineMath: [['$','$'],['\\(','\\)']], displayMath: [['$$','$$'],['\\[','\\]']], processEscapes: true },
    options: { skipHtmlTags: ['script','noscript','style','textarea','pre','code'] },
    startup: { typeset: false }
  };
</script>
<script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-mml-chtml.min.js"></script>
</head>
<body>
<?php require_once __DIR__ . "/cfs_nav.php"; ?>

<div class="wrap">
  <h1>Die Yield &amp; Cost-per-Good-Die Simulator</h1>
  <p class="lede">
    Set a die size, wafer, and defect environment, then run it — the simulation executes on the
    ChipFoundryServices distributed compute pool. It steps the die grid to count how many whole dies
    pack onto the wafer inside the edge-exclusion ring (gross-die-per-wafer), applies four classic
    defect-limited yield models (Poisson, Murphy, negative-binomial clustering, and Seeds) to find the
    surviving fraction, and reports good-die-per-wafer, packing efficiency and the cost-per-good-die —
    the number that actually sets the price of a GPU or accelerator die. Big reticle-limit AI dies live
    or die on defect density. Reduced-order educational model. See also the
    <a href="/transistor">transistor I-V</a>, <a href="/thermal">thermal</a>,
    <a href="/interconnect">interconnect RC</a>, <a href="/cmp">CMP planarization</a> and
    <a href="/lithography">lithography</a> simulators and the
    <a href="/edge/">compute-pool status</a>.
  </p>

  <div class="grid">
    <!-- ============ controls ============ -->
    <form class="card" id="form" onsubmit="return false;">
      <div class="presets">
        <span class="preset" data-p="gpu">AI GPU (reticle)</span>
        <span class="preset" data-p="mobile">Mobile SoC</span>
        <span class="preset" data-p="mature">Mature 200 mm</span>
      </div>

      <label>Die width <span class="val" id="v_dw">10.0 mm</span></label>
      <input type="range" id="die_width_mm" min="1" max="33" step="0.5" value="10">

      <label>Die height <span class="val" id="v_dh">10.0 mm</span></label>
      <input type="range" id="die_height_mm" min="1" max="26" step="0.5" value="10">

      <label>Wafer diameter <span class="val" id="v_wf">300 mm</span></label>
      <input type="range" id="wafer_diameter_mm" min="100" max="450" step="50" value="300">

      <label>Defect density D<sub>0</sub> <span class="val" id="v_d0">0.10 /cm&sup2;</span></label>
      <input type="range" id="defect_density_d0" min="0.01" max="2" step="0.01" value="0.10">

      <label>Defect clustering &alpha; <span class="val" id="v_al">3.0</span></label>
      <input type="range" id="clustering_alpha" min="0.5" max="10" step="0.1" value="3.0">

      <label>Yield model <span class="val" id="v_ym">Neg. binomial</span></label>
      <input type="range" id="yield_model" min="1" max="4" step="1" value="3">

      <label>Edge exclusion <span class="val" id="v_ee">3.0 mm</span></label>
      <input type="range" id="edge_exclusion_mm" min="0" max="10" step="0.5" value="3">

      <label>Scribe lane (kerf) <span class="val" id="v_sc">0.10 mm</span></label>
      <input type="range" id="scribe_lane_mm" min="0" max="0.2" step="0.01" value="0.10">

      <label>Wafer cost <span class="val" id="v_wc">$5,000</span></label>
      <input type="range" id="wafer_cost_usd" min="100" max="20000" step="100" value="5000">

      <button class="run" id="run">Run simulation</button>
    </form>

    <!-- ============ results ============ -->
    <div class="card">
      <div class="viz" id="viz1"><svg viewBox="0 0 600 300" id="svg1"></svg></div>
      <div class="vizcap">Wafer map &mdash; whole dies fit inside the edge-exclusion ring (green); edge-clipped dies are lost (red)</div>
      <div class="viz" id="viz2"><svg viewBox="0 0 600 240" id="svg2"></svg></div>
      <div class="vizcap">Yield vs die size &mdash; four defect models; marker is your die at the current D<sub>0</sub></div>
      <div id="err" class="err" style="display:none"></div>
      <div id="verdict" class="verdict" style="display:none"></div>
      <table id="metrics"><tbody></tbody></table>
      <div class="node" id="nodeline"></div>
    </div>
  </div>

  <!-- ============ knowledge-base explanation ============ -->
  <div class="card kb" id="kb" style="display:none">
    <h2>What these results mean</h2>
    <p class="src">Explanatory text pulled live from the <a href="/chat/">CFS knowledge base</a>.</p>
    <div class="kb-body" id="kbBody"></div>
  </div>

  <div class="api">
    <b>Developer API</b> — same simulation over HTTP (load-balanced across the pool):
    <pre>curl -X POST https://www.chipfoundryservices.com/edge/yield \
  -H "Content-Type: application/json" \
  -d '{"die_width_mm":26,"die_height_mm":33,"wafer_diameter_mm":300,"defect_density_d0":0.12,
       "clustering_alpha":3.0,"yield_model":3,"edge_exclusion_mm":3,"scribe_lane_mm":0.10,"wafer_cost_usd":16000}'</pre>
    Returns JSON with <code>outputs</code> (die_area_mm2, die_area_cm2, gross_die_per_wafer,
    defects_per_die, yield_percent, good_die_per_wafer, packing_efficiency_percent,
    cost_per_good_die_usd, cost_per_gross_die_usd, yield_poisson_percent, yield_murphy_percent,
    yield_negbinom_percent, yield_seeds_percent, model_name, verdict), the full <code>profile</code>
    (80-point <code>yield_curve</code>, <code>wafer</code> packing geometry), the serving
    <code>node</code>, and <code>compute_ms</code>.
  </div>
</div>

<script>
const $ = id => document.getElementById(id);
const fields = ["die_width_mm","die_height_mm","wafer_diameter_mm","defect_density_d0","clustering_alpha","yield_model","edge_exclusion_mm","scribe_lane_mm","wafer_cost_usd"];
const labels = {die_width_mm:"v_dw", die_height_mm:"v_dh", wafer_diameter_mm:"v_wf", defect_density_d0:"v_d0", clustering_alpha:"v_al", yield_model:"v_ym", edge_exclusion_mm:"v_ee", scribe_lane_mm:"v_sc", wafer_cost_usd:"v_wc"};
const modelNames = {1:"Poisson", 2:"Murphy", 3:"Neg. binomial", 4:"Seeds"};

function syncLabels(){
  for(const f of fields){
    let v = $(f).value, out;
    if(f==="yield_model")            out = modelNames[parseInt(v,10)] || v;
    else if(f==="die_width_mm"||f==="die_height_mm"||f==="edge_exclusion_mm") out = parseFloat(v).toFixed(1) + " mm";
    else if(f==="wafer_diameter_mm") out = parseInt(v,10) + " mm";
    else if(f==="scribe_lane_mm")    out = parseFloat(v).toFixed(2) + " mm";
    else if(f==="defect_density_d0") out = parseFloat(v).toFixed(2) + " /cm\u00b2";
    else if(f==="clustering_alpha")  out = parseFloat(v).toFixed(1);
    else if(f==="wafer_cost_usd")    out = "$" + Number(v).toLocaleString();
    else out = v;
    $(labels[f]).innerHTML = out;
  }
}
fields.forEach(f => $(f).addEventListener("input", syncLabels));
syncLabels();

const presets = {
  gpu:    {die_width_mm:26, die_height_mm:33, wafer_diameter_mm:300, defect_density_d0:0.12, clustering_alpha:3.0, yield_model:3, edge_exclusion_mm:3, scribe_lane_mm:0.10, wafer_cost_usd:16000},
  mobile: {die_width_mm:8,  die_height_mm:8,  wafer_diameter_mm:300, defect_density_d0:0.08, clustering_alpha:3.0, yield_model:3, edge_exclusion_mm:3, scribe_lane_mm:0.10, wafer_cost_usd:6000},
  mature: {die_width_mm:5,  die_height_mm:5,  wafer_diameter_mm:200, defect_density_d0:0.05, clustering_alpha:4.0, yield_model:3, edge_exclusion_mm:3, scribe_lane_mm:0.10, wafer_cost_usd:1500},
};
document.querySelectorAll(".preset").forEach(el=>{
  el.onclick = () => { const p = presets[el.dataset.p]; for(const k in p) $(k).value = p[k]; syncLabels(); run(); };
});

function fmt(n, d=1){ return Number(n).toLocaleString(undefined,{maximumFractionDigits:d}); }
function fmtSig(n){
  const a = Math.abs(n);
  if(a===0) return "0";
  if(a>=1000) return fmt(n,0);
  if(a>=1) return fmt(n,1);
  if(a>=0.01) return fmt(n,3);
  return n.toExponential(1);
}

// ---- viz 1: wafer map — die grid inside the edge-exclusion ring ----
function drawWafer(r){
  const w = r.profile.wafer, o = r.outputs;
  const R = w.wafer_diameter_mm/2, ruse = w.usable_diameter_mm/2;
  const dw = w.die_width_mm, dh = w.die_height_mm;
  const px = w.pitch_x_mm, py = w.pitch_y_mm;
  const VW=600, VH=300, cx=VW/2, cy=VH/2;
  const s = (Math.min(VW,VH)/2 - 10) / R;       // mm -> px scale
  const parts = [];
  // wafer disc + flat
  parts.push(`<circle cx="${cx}" cy="${cy}" r="${(R*s).toFixed(1)}" fill="#eef2f7" stroke="#334d70" stroke-width="1.5"/>`);
  parts.push(`<line x1="${(cx-R*s*0.55).toFixed(1)}" y1="${(cy+R*s*0.94).toFixed(1)}" x2="${(cx+R*s*0.55).toFixed(1)}" y2="${(cy+R*s*0.94).toFixed(1)}" stroke="#334d70" stroke-width="3" stroke-linecap="round"/>`);
  // edge-exclusion ring
  parts.push(`<circle cx="${cx}" cy="${cy}" r="${(ruse*s).toFixed(1)}" fill="none" stroke="#9aa7b8" stroke-width="1" stroke-dasharray="4 3"/>`);
  // die grid — replicate worker packing exactly
  const hw=dw/2, hh=dh/2;
  const nx = Math.floor(2*ruse/px)+2, ny = Math.floor(2*ruse/py)+2;
  const cells = (2*nx+1)*(2*ny+1);
  let good=0;
  if(cells <= 8000){
    for(let iy=-ny; iy<=ny; iy++){
      const dcy=(iy+0.5)*py;
      for(let ix=-nx; ix<=nx; ix++){
        const dcx=(ix+0.5)*px;
        // corner test against usable radius
        const inUse = (Math.abs(dcx)+hw)**2 + (Math.abs(dcy)+hh)**2 <= ruse*ruse;
        // does die center still sit on the wafer disc? (edge-clipped candidate)
        const onWafer = (Math.abs(dcx)+hw)**2 + (Math.abs(dcy)+hh)**2 <= R*R;
        if(!onWafer) continue;
        const X = cx + dcx*s, Y = cy - dcy*s;
        const rw = dw*s - 0.6, rh = dh*s - 0.6;
        if(inUse){ good++; parts.push(`<rect x="${(X-rw/2).toFixed(1)}" y="${(Y-rh/2).toFixed(1)}" width="${rw.toFixed(1)}" height="${rh.toFixed(1)}" fill="#07C160" fill-opacity="0.5" stroke="#34d399" stroke-width="0.5"/>`); }
        else       { parts.push(`<rect x="${(X-rw/2).toFixed(1)}" y="${(Y-rh/2).toFixed(1)}" width="${rw.toFixed(1)}" height="${rh.toFixed(1)}" fill="none" stroke="#c0392b" stroke-width="0.7" stroke-opacity="0.7"/>`); }
      }
    }
  } else {
    parts.push(`<text x="${cx}" y="${cy}" fill="#94a3b8" font-size="13" text-anchor="middle">die too small to draw individually</text>`);
  }
  // count badge
  parts.push(`<text x="${cx}" y="18" fill="#16181d" font-size="13" font-weight="700" text-anchor="middle">${fmt(o.gross_die_per_wafer,0)} gross die &middot; ${fmt(o.good_die_per_wafer,0)} good</text>`);
  parts.push(`<text x="${cx}" y="${(VH-6).toFixed(1)}" fill="#94a3b8" font-size="10" text-anchor="middle">${fmt(w.wafer_diameter_mm,0)} mm wafer &middot; ${fmt(dw,1)}\u00d7${fmt(dh,1)} mm die &middot; ${fmt(w.edge_exclusion_mm,1)} mm edge exclusion</text>`);
  $("viz1").innerHTML = '<svg viewBox="0 0 '+VW+' '+VH+'" width="100%" xmlns="http://www.w3.org/2000/svg" id="svg1">'+parts.join("")+'</svg>';
}

// ---- viz 2: yield vs die size — four defect models ----
function drawYieldCurve(r){
  const cur = r.profile.yield_curve;      // [[edge_mm, yPois, yMur, yNB, ySeed], ...]
  const o = r.outputs, w = r.profile.wafer;
  const VW=600, VH=240, padL=44, padR=118, padT=16, padB=36;
  const xmin=cur[0][0], xmax=cur[cur.length-1][0];
  const X = e => padL + ((e-xmin)/(xmax-xmin)) * (VW-padL-padR);
  const Y = y => padT + (1 - y) * (VH-padT-padB);
  const parts = [];
  // horizontal gridlines 0/25/50/75/100 %
  for(let p=0;p<=100;p+=25){
    const y = Y(p/100);
    parts.push(`<line x1="${padL}" y1="${y.toFixed(1)}" x2="${VW-padR}" y2="${y.toFixed(1)}" stroke="#1e2e4a" stroke-width="1"/>`);
    parts.push(`<text x="${(padL-6).toFixed(1)}" y="${(y+3).toFixed(1)}" fill="#94a3b8" font-size="9" text-anchor="end">${p}%</text>`);
  }
  // axes
  parts.push(`<line x1="${padL}" y1="${padT}" x2="${padL}" y2="${VH-padB}" stroke="#334d70" stroke-width="1"/>`);
  parts.push(`<line x1="${padL}" y1="${VH-padB}" x2="${VW-padR}" y2="${VH-padB}" stroke="#334d70" stroke-width="1"/>`);
  // model curves: cols 1..4 = Poisson, Murphy, NegBinom, Seeds
  const series = [
    {col:1, color:"#8a94a6", name:"Poisson"},
    {col:2, color:"#e0a13a", name:"Murphy"},
    {col:3, color:"#07C160", name:"Neg. binom."},
    {col:4, color:"#3f78c4", name:"Seeds"},
  ];
  series.forEach((sr,si)=>{
    let d=""; cur.forEach((pt,i)=>{ d += (i?"L":"M") + X(pt[0]).toFixed(1) + " " + Y(pt[sr.col]).toFixed(1) + " "; });
    parts.push(`<path d="${d}" fill="none" stroke="${sr.color}" stroke-width="${sr.col===3?2.6:1.6}"/>`);
    const ly = padT + 8 + si*16;
    parts.push(`<line x1="${(VW-padR+10).toFixed(1)}" y1="${ly.toFixed(1)}" x2="${(VW-padR+28).toFixed(1)}" y2="${ly.toFixed(1)}" stroke="${sr.color}" stroke-width="2.6"/>`);
    parts.push(`<text x="${(VW-padR+32).toFixed(1)}" y="${(ly+3).toFixed(1)}" fill="#94a3b8" font-size="10">${sr.name}</text>`);
  });
  // marker at current die (equivalent square edge)
  const eeq = r.profile.die_edge_equiv_mm;
  if(eeq >= xmin && eeq <= xmax){
    const xv = X(eeq);
    parts.push(`<line x1="${xv.toFixed(1)}" y1="${padT}" x2="${xv.toFixed(1)}" y2="${VH-padB}" stroke="#38bdf8" stroke-width="1" stroke-dasharray="4 3"/>`);
    parts.push(`<circle cx="${xv.toFixed(1)}" cy="${Y(o.yield_percent/100).toFixed(1)}" r="4" fill="#34d399"/>`);
    parts.push(`<text x="${(xv+4).toFixed(1)}" y="${(padT+10).toFixed(1)}" fill="#16181d" font-size="10">your die ${fmt(o.yield_percent,1)}%</text>`);
  }
  // axis labels
  parts.push(`<text x="${((padL+VW-padR)/2).toFixed(1)}" y="${(VH-6).toFixed(1)}" fill="#94a3b8" font-size="11" text-anchor="middle">die edge (mm, square-equivalent) &middot; D&#8320; = ${fmt(r.inputs.defect_density_d0,2)}/cm&sup2;</text>`);
  parts.push(`<text x="12" y="${((padT+VH-padB)/2).toFixed(1)}" fill="#94a3b8" font-size="11" text-anchor="middle" transform="rotate(-90 12 ${((padT+VH-padB)/2).toFixed(1)})">yield</text>`);
  $("viz2").innerHTML = '<svg viewBox="0 0 '+VW+' '+VH+'" width="100%" xmlns="http://www.w3.org/2000/svg" id="svg2">'+parts.join("")+'</svg>';
}

function showMetrics(r){
  const o = r.outputs;
  const rows = [
    ["Yield model", o.model_name],
    ["Die area", fmt(o.die_area_mm2,1)+" mm&sup2; ("+fmt(o.die_area_cm2,3)+" cm&sup2;)"],
    ["Gross die / wafer", fmt(o.gross_die_per_wafer,0)],
    ["Packing efficiency", fmt(o.packing_efficiency_percent,1)+" %"],
    ["Defects per die (AD)", fmt(o.defects_per_die,3)],
    ["Yield", fmt(o.yield_percent,2)+" %"],
    ["Good die / wafer", fmt(o.good_die_per_wafer,0)],
    ["Cost / good die", "$"+fmt(o.cost_per_good_die_usd,2)],
    ["Cost / gross die", "$"+fmt(o.cost_per_gross_die_usd,2)],
    ["&nbsp;&nbsp;Poisson yield", fmt(o.yield_poisson_percent,2)+" %"],
    ["&nbsp;&nbsp;Murphy yield", fmt(o.yield_murphy_percent,2)+" %"],
    ["&nbsp;&nbsp;Neg. binomial yield", fmt(o.yield_negbinom_percent,2)+" %"],
    ["&nbsp;&nbsp;Seeds yield", fmt(o.yield_seeds_percent,2)+" %"],
  ];
  $("metrics").innerHTML = "<thead><tr><th>Metric</th><th style='text-align:right'>Value</th></tr></thead><tbody>"
    + rows.map(x=>`<tr><td>${x[0]}</td><td class="num">${x[1]}</td></tr>`).join("") + "</tbody>";

  const v = $("verdict");
  v.style.display = "block";
  v.textContent = "Result: " + o.verdict;
  if(/healthy/.test(o.verdict)){ v.style.background="#e7f7ee"; v.style.color="#34d399"; }
  else if(/yield-limited/.test(o.verdict)){ v.style.background="#fdecea"; v.style.color="#c0392b"; }
  else if(/packing-limited/.test(o.verdict)){ v.style.background="#fbe9e0"; v.style.color="#b4531a"; }
  else { v.style.background="#fff6e6"; v.style.color="#9a6b00"; }

  $("nodeline").innerHTML = `Computed on <b>professional Chip Foundry Services</b> compute in ${fmt(r.compute_ms,0)} ms &middot; ${r.steps} integration steps &middot; <span style="opacity:.8">${r.model}</span>`;
}

// ---- professional flowchart renderer (ported from the site's chat) ----
const escapeHtml = (t) => String(t).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]));
const applyBold = (raw) => escapeHtml(String(raw)).replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
            const renderFlowchart = (jsonStr) => {
                let spec;
                try { spec = JSON.parse(jsonStr); } catch (e) { return `<pre><code>${escapeHtml(jsonStr)}</code></pre>`; }
                const rows = (spec && spec.rows) || [];
                const arrowSvg = '<svg viewBox="0 0 18 30" fill="none"><path d="M9 0 V24" stroke="currentColor" stroke-width="1.6"/><path d="M3 18 L9 26 L15 18" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                const arrowRight = '<svg viewBox="0 0 34 16" fill="none"><path d="M0 8 H27" stroke="currentColor" stroke-width="1.6"/><path d="M21 2 L29 8 L21 14" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                const arrowLeft = '<svg viewBox="0 0 34 16" fill="none"><path d="M34 8 H7" stroke="currentColor" stroke-width="1.6"/><path d="M13 2 L5 8 L13 14" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                const arrowDownSm = '<svg viewBox="0 0 16 28" fill="none"><path d="M8 0 V22" stroke="currentColor" stroke-width="1.6"/><path d="M2 16 L8 24 L14 16" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                const arrowUpSm = '<svg viewBox="0 0 16 28" fill="none"><path d="M8 28 V6" stroke="currentColor" stroke-width="1.6"/><path d="M2 12 L8 4 L14 12" stroke="currentColor" stroke-width="1.6" fill="none" stroke-linejoin="round" stroke-linecap="round"/></svg>';
                // One arrow per column of the preceding node row (parallel arrows, Claude.ai-style).
                const connector = (count) => {
                    if (!count || count <= 1) return `<div class="cfs-arrow">${arrowSvg}</div>`;
                    let a = '<div class="cfs-arrow-row">';
                    for (let k = 0; k < count; k++) a += `<div class="cfs-arrow-col">${arrowSvg}</div>`;
                    return a + '</div>';
                };
                const node = (n) => {
                    const tone = ['neutral', 'green', 'orange', 'blue'].indexOf(n.tone) >= 0 ? n.tone : 'neutral';
                    const sub = n.sub ? `<span class="cfs-node-sub">${applyBold(String(n.sub))}</span>` : '';
                    return `<div class="cfs-node ${tone}"><span class="cfs-node-title">${applyBold(String(n.title || ''))}</span>${sub}</div>`;
                };
                let out = '<div class="cfs-flow">';
                let prevType = null, prevCount = 0;
                rows.forEach((r, idx) => {
                    // explicit arrow row: draw the connector, don't double up
                    if (r.type === 'arrow') { out += connector(prevCount); prevType = 'arrow'; return; }
                    // auto-connect sequential flow rows only; tier/die rows are diagrams, not steps
                    if (idx > 0 && r.type !== 'tier' && r.type !== 'die' && (prevType === 'nodes' || prevType === 'group')) out += connector(prevCount);
                    if (r.type === 'die') {
                        out += '<div class="cfs-die">';
                        out += `<div class="cfs-die-h">${applyBold(String(r.title || ''))}</div>`;
                        if (r.note) out += `<div class="cfs-die-note">${applyBold(String(r.note))}</div>`;
                        out += '<div class="cfs-die-top">';
                        if (r.input) out += `<div class="cfs-die-in">${applyBold(String(r.input))}<span class="cfs-die-inar">${arrowRight}</span></div>`;
                        (r.top || []).forEach((n, i) => {
                            if (i > 0) out += `<span class="cfs-die-ar">${arrowRight}</span>`;
                            out += node(n);
                        });
                        out += '</div>';
                        if (r.base) {
                            out += '<div class="cfs-die-downs">';
                            (r.top || []).forEach(() => { out += `<span class="cfs-die-dar">${arrowDownSm}</span>`; });
                            out += '</div>';
                            out += `<div class="cfs-die-base">${node(r.base)}</div>`;
                        }
                        out += '</div>';
                        if (r.feed) {
                            out += `<div class="cfs-die-uar">${arrowUpSm}</div>`;
                            out += `<div class="cfs-die-feed">${applyBold(String(r.feed.title || ''))}</div>`;
                        }
                        prevType = 'die'; prevCount = 0;
                        return;
                    }
                    if (r.type === 'tier') {
                        out += '<div class="cfs-tier">';
                        out += `<div class="cfs-tier-h">${applyBold(String(r.title || ''))}</div>`;
                        out += '<div class="cfs-tier-row">';
                        (r.items || []).forEach(n => { out += node(n); });
                        out += '</div></div>';
                        prevType = 'tier'; prevCount = 0;
                        return;
                    }
                    if (r.type === 'group') {
                        out += '<div class="cfs-group"><div class="cfs-group-head">';
                        out += `<div class="cfs-group-title">${applyBold(String(r.title || ''))}</div>`;
                        if (r.note) out += `<div class="cfs-group-note">${applyBold(String(r.note))}</div>`;
                        out += '</div>';
                        const gi = r.items || [];
                        if (r.cycle && gi.length >= 4) {
                            // clockwise ring: items = [top-left, top-right, bottom-right, bottom-left]
                            out += '<div class="cfs-cycle">';
                            out += `<div class="cfs-cyc cyc-tl">${node(gi[0])}</div>`;
                            out += `<div class="cfs-cyc-ar cyc-r">${arrowRight}</div>`;
                            out += `<div class="cfs-cyc cyc-tr">${node(gi[1])}</div>`;
                            out += `<div class="cfs-cyc-loop">${applyBold(String(r.loop || ''))}</div>`;
                            out += `<div class="cfs-cyc-ar cyc-d">${arrowDownSm}</div>`;
                            out += `<div class="cfs-cyc cyc-bl">${node(gi[3])}</div>`;
                            out += `<div class="cfs-cyc-ar cyc-l">${arrowLeft}</div>`;
                            out += `<div class="cfs-cyc cyc-br">${node(gi[2])}</div>`;
                            out += '</div>';
                        } else {
                            out += '<div class="cfs-group-grid">';
                            gi.forEach(n => { out += node(n); });
                            out += '</div>';
                            if (r.loop) out += `<div class="cfs-group-loop">${applyBold(String(r.loop))}</div>`;
                        }
                        out += '</div>';
                        prevType = 'group'; prevCount = 1;
                        return;
                    }
                    // default: a row of nodes
                    out += '<div class="cfs-flow-row">';
                    (r.items || []).forEach(n => { out += node(n); });
                    out += '</div>';
                    prevType = 'nodes'; prevCount = (r.items || []).length;
                });
                out += '</div>';
                return out;
            };

// ---- minimal, safe markdown -> HTML for KB text ----
function mdToHtml(s){
  s = String(s);
  // KB stores newlines/tabs as literal backslash sequences — restore real
  // breaks so paragraphs and diagrams separate instead of running together.
  s = s.replace(/\\r\\n|\\r|\\n/g, "\n").replace(/\\t/g, "  ");
  // Hold aside authored diagram blocks (fenced ```svg / ```html, or a bare
  // <svg>…</svg>) so they render as real figures rather than escaped tag text.
  const raw = [];
  const RSENT = "\u0000R";
  s = s.replace(/```flowchart[ \t]*\n([\s\S]*?)```/gi, (_, code) => {
    const i = raw.length; raw.push(renderFlowchart(code.trim())); return "\n\n" + RSENT + i + "\u0000\n\n";
  });
  s = s.replace(/```(?:svg|html)[ \t]*\n([\s\S]*?)```/gi, (_, code) => {
    const i = raw.length; raw.push(code.trim()); return "\n\n" + RSENT + i + "\u0000\n\n";
  });
  s = s.replace(/<svg[\s\S]*?<\/svg>/gi, (m) => {
    const i = raw.length; raw.push(m); return "\n\n" + RSENT + i + "\u0000\n\n";
  });
  const math = [];
  const SENT = "\u0000M";
  const patterns = [/\$\$[\s\S]+?\$\$/g, /\\\[[\s\S]+?\\\]/g, /\\\([\s\S]+?\\\)/g, /\$[^\$\n]+?\$/g];
  for(const re of patterns){
    s = s.replace(re, m => { const i = math.length; math.push(m); return SENT + i + "\u0000"; });
  }
  s = s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  const blocks = s.split(/\n\n+/).map(b=>{
    b = b.trim(); if(!b) return "";
    const _tl = b.split(/\n/).map(l=>l.trim()).filter(l=>l);
    if(_tl.length>=2 && _tl.every(l=>l.startsWith("|")) && /-/.test(_tl[1]) && /^\|?[\s:|-]+\|?$/.test(_tl[1])){
      const _bold = c => c.replace(/\*\*(.+?)\*\*/g,"<b>$1</b>");
      const _cells = l => l.replace(/^\|/,"").replace(/\|$/,"").split("|").map(c=>_bold(c.trim()));
      const _head = _cells(_tl[0]).map(c=>`<th>${c}</th>`).join("");
      const _rows = _tl.slice(2).map(l=>"<tr>"+_cells(l).map(c=>`<td>${c}</td>`).join("")+"</tr>").join("");
      return `<div class="kb-tablewrap"><table class="kb-table"><thead><tr>${_head}</tr></thead><tbody>${_rows}</tbody></table></div>`;
    }
    b = b.replace(/\*\*(.+?)\*\*/g,"<b>$1</b>");
    if(/^-\s/m.test(b) && b.split(/\n/).every(l=>/^-\s/.test(l.trim())||l.trim()==="")){
      const items = b.split(/\n/).filter(l=>l.trim()).map(l=>`<li>${l.replace(/^-\s*/,"")}</li>`).join("");
      return `<ul>${items}</ul>`;
    }
    return `<p>${b.replace(/\n/g,"<br>")}</p>`;
  });
  let html = blocks.join("");
  html = html.replace(/\u0000M(\d+)\u0000/g, (_, i) => {
    return math[+i].replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
  });
  // Restore authored diagrams as real figures (unwrap the stray <p> the block
  // splitter puts around a lone sentinel so we don't nest a figure inside <p>).
  html = html.replace(/<p>\s*\u0000R(\d+)\u0000\s*<\/p>/g, (_, i) => `<figure class="kb-figure">${raw[+i]}</figure>`);
  html = html.replace(/\u0000R(\d+)\u0000/g, (_, i) => `<figure class="kb-figure">${raw[+i]}</figure>`);
  return html;
}

function typesetKB(){
  const el = document.getElementById("kbBody");
  if(!el) return;
  if(window.MathJax && MathJax.typesetPromise){ MathJax.typesetPromise([el]).catch(()=>{}); }
  else { setTimeout(typesetKB, 200); }
}

async function loadKB(r){
  const o = r.outputs;
  const second = /packing/.test(o.verdict) ? "gross die per wafer" : "defect density";
  const queries = [["Yield","yield"], ["", second]];
  const seen = {};
  const out = [];
  for(const [title,q] of queries){
    try{
      const res = await fetch("/api.php?q=" + encodeURIComponent(q));
      const j = await res.json();
      let txt = (j && j.response) ? j.response.trim() : "";
      if(!txt) continue;
      const key = txt.slice(0,60);
      if(seen[key]) continue; seen[key] = 1;
      out.push(`<div class="kb-block">${mdToHtml(txt)}</div>`);
    }catch(e){ /* KB optional — skip on failure */ }
  }
  if(out.length){
    $("kbBody").innerHTML = out.join("");
    $("kb").style.display = "block"; typesetKB();
  }
}

async function run(){
  const btn = $("run"); btn.disabled = true; btn.textContent = "Running on pool…";
  $("err").style.display = "none";
  const body = {};
  for(const f of fields) body[f] = parseFloat($(f).value);
  try{
    const res = await fetch("/edge/yield", {method:"POST", headers:{"Content-Type":"application/json"}, body:JSON.stringify(body)});
    if(!res.ok) throw new Error("HTTP "+res.status);
    const r = await res.json();
    if(r.error) throw new Error(r.error);
    drawWafer(r); drawYieldCurve(r); showMetrics(r);
    loadKB(r);
  }catch(e){
    $("err").style.display = "block";
    $("err").textContent = "Simulation failed: " + e.message + " (the compute pool may be temporarily unavailable — try again).";
  }finally{
    btn.disabled = false; btn.textContent = "Run simulation";
  }
}
$("run").onclick = run;
run();   // run once on load with defaults
</script>
</body>
</html>
