#!/bin/sh
# SkillBoss Start Guide — THE RUN SCREEN (Gate 05), v2.
#
# Renders state/scoreboard.md into TWO views after every commit:
#   state/run-screen.md    for your IDE — open it as a tab beside your code,
#                          the editor reloads it on every commit, and the
#                          markdown preview renders it. No browser needed.
#   state/run-screen.html  for a browser tab, if you prefer one.
#
# Same parser, same data, two targets: a dashboard you do not look at is
# not a dashboard, and a second tab in another window is a dashboard you
# do not look at.
#
# What the screen shows, top to bottom: the coach's NEXT / NOW / AFTER
# lines (the same three the YOU ARE HERE block carries, read from the
# scoreboard's Now section); the map with its legend; gates faced, passed
# and waived; milestones; the run belt and the next rung; the ten
# villains, marked when the gate that faces them is faced; what the gates
# caught; the waivers with their reasons; the parked asks; the one alarm;
# FinOps. Every block is omitted when its data is absent — the screen
# never fabricates a state. Every fixed string it prints is defined in
# SCREENS.md, and the build refuses a hook that drifts from it.
#
# Both are RENDERINGS, never a second source of truth. The chain is
# journal.md -> scoreboard.md -> these views, and it never runs backwards:
# if a view and the scoreboard disagree, the scoreboard wins and the coach
# regenerates it from the journal.
#
# FINOPS, in two natures that are never mixed. MEASURED is computed right
# here from git (days, commits, commits per gate) — no network, no
# telemetry, nothing sent anywhere. DECLARED comes from the scoreboard's
# Cost table, which the coach fills from what its own tool reports: this
# kit cannot see your usage or your bill, so every declared figure is
# labelled as declared wherever it appears. Ratios and counts only — never
# a percentage, never a derived score.
#
# COLOURS are the house tokens (frontend/src/styles/tokens.css), declared
# once as CSS variables: no green anywhere (the palette has none), and
# never the timer's danger red. The static frame is the canonical one;
# nothing here animates.
#
# No network, no service, no dependency: POSIX sh and awk (mawk-safe: no
# interval expressions, no gawk extensions), local files that work offline
# forever. Fail-open by design — a screen is a passenger, never a guard:
# any problem here writes a page saying so and lets the commit stand.
set -e

board="state/scoreboard.md"
out_md="state/run-screen.md"
out_html="state/run-screen.html"

[ -f "$board" ] || exit 0
mkdir -p state

# --- MEASURED, from git only. Every one of these is a fact this machine
# --- can check; none of them leaves it. Failures degrade to "unknown"
# --- rather than breaking the render.
commits_total=$(git rev-list --count HEAD 2>/dev/null || echo 0)
first_ts=$(git log --reverse --format=%ct 2>/dev/null | head -1)
if [ -n "$first_ts" ]; then
  days=$(( ( $(date +%s) - first_ts ) / 86400 ))
else
  days=0
fi
files_tracked=$(git ls-files 2>/dev/null | wc -l | tr -d ' ')

# The gap that just ended: this hook runs ON a commit, so the interesting
# number is the silence BEFORE it. A run that paused for three weeks is a
# normal run — the screen says so without a hint of reproach, and the kit
# keeps no streak that could punish real life.
prev_ts=$(git log -2 --format=%ct 2>/dev/null | tail -1)
head_ts=$(git log -1 --format=%ct 2>/dev/null)
quiet_days=0
if [ -n "$prev_ts" ] && [ -n "$head_ts" ] && [ "$prev_ts" != "$head_ts" ]; then
  quiet_days=$(( (head_ts - prev_ts) / 86400 ))
fi

# "1 commits" reads badly on a screen someone keeps open all day.
plural() { [ "$1" = "1" ] && printf '%s %s' "$1" "$2" || printf '%s %ss' "$1" "$2"; }

# Commits since the open gate was reached: the scoreboard dates every gate
# it passed, so the most recent of those dates bounds "work on the gate you
# are on now". Empty when no gate has passed yet.
last_gate_date=$(awk -F'|' '/^\| [0-9][0-9]-/ { d=$4; gsub(/^[ ]+|[ ]+$/,"",d); if (d ~ /^[0-9][0-9][0-9][0-9]-/) print d }' "$board" 2>/dev/null | sort | tail -1)
if [ -n "$last_gate_date" ]; then
  commits_gate=$(git log --since="$last_gate_date" --oneline 2>/dev/null | wc -l | tr -d ' ')
else
  commits_gate=""
fi

# THE CAST — gate:villain, in board order. The build asserts every row
# against SCREENS.md's cast table, so this list can never drift from the
# gates that name the villains.
cast="20:THE LEAK,20:THE COMMITTED KEY,20:THE OPEN MIC,20:THE OPEN DOOR,50:THE ROTTEN PLANK,70:THE LOST WEEKEND,70:THE BILL SHOCK,80:THE SILENT CRASH,80:THE 3AM PAGE,80:THE FLOOD"

# THE LADDER — rung:gates, in order. Same source, same guard.
ladder="YELLOW:00 05 10,ORANGE:20,GREEN:40 50,BLUE:60 70,BROWN:80,BLACK:90"

render() {
  # $1 = format (md|html)
  awk -v fmt="$1" \
      -v days="$days" \
      -v m_days_s="$(plural "$days" day)" \
      -v m_commits_s="$(plural "$commits_total" commit)" \
      -v m_files_s="$(plural "$files_tracked" file)" \
      -v m_gate_commits_s="${commits_gate:+$(plural "$commits_gate" commit)}" \
      -v quiet_days="$quiet_days" \
      -v cast="$cast" \
      -v ladder="$ladder" '
BEGIN { section=""; belt="WHITE"; project="this project"; started="-"; profile="FULL RUN";
        mdone=0; mtotal=0; mnext=""; gpassed=0; gtotal=0; gwaived=0; gfaced=0; gopen=""; sec="OPEN";
        map=""; csessions=0; ctokens=""; ctokmore=0; crows=0; complete=0; momentum="";
        nnext=""; nnow=""; nafter=""; ncaught=0; nparked=0; nwaived=0;
        DOT="\302\267"; DASH="\342\200\224" }

function trim(s) { gsub(/^[ ]+|[ ]+$/, "", s); return s }
function ph(s)   { return (s ~ /^</) }   # a template placeholder, never data
function esc(s)  { if (fmt == "html") { gsub(/&/, "\\&amp;", s); gsub(/</, "\\&lt;", s); gsub(/>/, "\\&gt;", s) } return s }

/^## / { section=$0; next }

section ~ /^## Run$/ {
  if ($0 ~ /^- Project:/)      { sub(/^- Project:[ ]*/,""); project=$0 }
  if ($0 ~ /^- Run started:/)  { sub(/^- Run started:[ ]*/,""); sub(/ \(the Hello World\)/,""); started=$0 }
  if ($0 ~ /^- Profile:/)      { sub(/^- Profile:[ ]*/,""); sub(/ \(PROFILES.md\)/,""); profile=$0 }
  if ($0 ~ /Current run belt:/) { line=$0; sub(/.*Current run belt:[ ]*/,"",line); gsub(/\*/,"",line); belt=line }
  next
}

# The coach writes the last YOU ARE HERE lines here; placeholders stay silent.
section ~ /^## Now/ {
  if ($0 ~ /^- next:/)  { v=$0; sub(/^- next:[ ]*/,"",v);  if (!ph(v)) nnext=v }
  if ($0 ~ /^- now:/)   { v=$0; sub(/^- now:[ ]*/,"",v);   if (!ph(v)) nnow=v }
  if ($0 ~ /^- after:/) { v=$0; sub(/^- after:[ ]*/,"",v); if (!ph(v)) nafter=v }
  next
}

section ~ /^## Milestones/ && /^\| M[0-9]/ {
  n=split($0, f, "|")
  name=trim(f[3]); state=trim(f[4])
  if (ph(name)) next
  mtotal++
  if (state == "DONE") mdone++
  else if (mnext == "") mnext=name
  next
}

section ~ /^## Gates/ && /^\| [0-9][0-9]-/ {
  n=split($0, f, "|")
  gate=trim(f[2]); state=trim(f[3]); why=trim(f[5])
  gtotal++
  id=substr(gate,1,2)
  gstate[id]=state
  if (state == "PASSED")      mark="[x]"
  else if (state == "WAIVED") mark="[~]"
  else if (gopen == "")       mark="[>]"
  else                        mark="[ ]"
  map = map (map == "" ? "" : " ") id mark
  if (state == "PASSED") { gpassed++; gfaced++ }
  else if (state == "WAIVED") { gwaived++; gfaced++; nwaived++; waived_g[nwaived]=toupper(gate); waived_why[nwaived]=why }
  else if (gopen == "") gopen=gate
  if (gate ~ /^20-/) sec=state
  if (gate ~ /^90-/ && state == "PASSED") complete=1
  next
}

section ~ /^## Caught/ && /^\| / {
  n=split($0, f, "|")
  d=trim(f[2]); what=trim(f[3]); where=trim(f[4])
  if (ph(d) || d == "Day" || d ~ /^-+$/) next
  ncaught++; caught_d[ncaught]=d; caught_what[ncaught]=what; caught_where[ncaught]=where
  next
}

section ~ /^## Parked/ && /^\| / {
  n=split($0, f, "|")
  since=trim(f[2]); ask=trim(f[3]); rev=trim(f[4])
  if (ph(since) || since == "Since" || since ~ /^-+$/) next
  nparked++; parked_ask[nparked]=ask; parked_rev[nparked]=rev
  next
}

# DECLARED costs. First column is the step NAME, never a gate number, so
# this table can never be confused with the gate table above. Placeholder
# rows are skipped, so a run that declared nothing renders an absent
# section rather than a misleading zero. The token list is capped at
# three steps; the rest is a count, never a scroll.
section ~ /^## Cost/ && /^\| / {
  n=split($0, f, "|")
  step=trim(f[2]); sess=trim(f[3]); tok=trim(f[4])
  if (ph(step) || step == "Step" || step ~ /^-+$/) next
  crows++
  if (sess ~ /^[0-9]+$/) csessions += sess
  if (tok != "" && !ph(tok) && tok != "unknown") {
    if (ctokmore < 3) ctokens = ctokens (ctokens == "" ? "" : ", ") step ": " tok
    ctokmore++
  }
  next
}

function bar(done, total,   i, s, filled) {
  if (total <= 0) return "----------"
  filled = int((done * 10) / total + 0.5)
  s=""
  for (i=0; i<10; i++) s = s (i < filled ? "\342\226\210" : "\342\226\221")
  return s
}

# The next rung: the first ladder step whose gates are not all faced.
function nextrung(   n, i, parts, rung, gates, m, j, g, missing) {
  n = split(ladder, parts, ",")
  for (i = 1; i <= n; i++) {
    split(parts[i], rung, ":")
    m = split(rung[2], gates, " ")
    missing = ""
    for (j = 1; j <= m; j++) {
      g = gates[j]
      if (gstate[g] != "PASSED" && gstate[g] != "WAIVED") missing = missing (missing == "" ? "" : " + ") g
    }
    if (missing != "") return rung[1] " at " missing
  }
  return ""
}

# One line per gate group of the cast, villains marked by their gate.
function villains(   n, i, parts, pair, g, v, m, out, cur, line) {
  n = split(cast, parts, ",")
  out = ""; cur = ""; line = ""
  for (i = 1; i <= n; i++) {
    split(parts[i], pair, ":")
    g = pair[1]; v = pair[2]
    m = (gstate[g] == "PASSED" || gstate[g] == "WAIVED") ? "[x]" : "[ ]"
    if (g != cur) {
      if (line != "") out = out line "\n"
      cur = g; line = g "  " v " " m
    } else line = line " " DOT " " v " " m
  }
  return out line
}

function row_md(label, value) { printf "%-10s %s\n", label, value }
function cont_md(value)       { printf "%-10s %s\n", "", value }
function row_html(label, value) { printf "<div class=\"row\"><span class=\"lab\">%s</span><span class=\"val\">%s</span></div>\n", label, value }

END {
  # No gate row at all means this file is not a scoreboard (or its shape
  # drifted). Say so plainly rather than render an empty screen that looks
  # like a fresh run — a wrong picture is worse than an honest blank.
  if (gtotal == 0) {
    if (fmt == "md") {
      print "# THE RUN"
      print ""
      print "The run screen could not be read from `state/scoreboard.md`: no gate"
      print "table was found. Ask your coach to regenerate the scoreboard from"
      print "the journal."
    } else {
      print "<!doctype html><meta charset=\"utf-8\"><title>THE RUN</title>"
      print "<body style=\"background:#0a0a0f;color:#f0f0f5;font:14px ui-monospace,monospace;padding:2rem\">"
      print "The run screen could not be read from state/scoreboard.md: no gate table was found."
      print "Ask your coach to regenerate the scoreboard from the journal.</body>"
    }
    exit
  }
  if (mnext == "") mnext = (mtotal > 0 ? "all milestones done" : "not set yet - ask your coach at Gate 00")
  if (gopen == "") gopen = "none open"
  ahead = (mdone >= 2 && sec != "PASSED" && sec != "WAIVED")
  gleft = gtotal - gfaced
  rung = nextrung()

  # The momentum line. Derived from the state of the run itself, never
  # random and never praise: a professional reading a compliment learns
  # only that nobody is reading. Silence beats flattery; a true sentence
  # beats both.
  if (gleft == 0)        momentum = "Every gate faced. The cabinet goes quiet - say Hello World to the next one."
  else if (gfaced == 0)  momentum = "The first gate is the hardest to open. Nothing is built until it is."
  else if (gleft == 1)   momentum = "One gate left. You already did the work it reads."
  else if (gfaced == 1)  momentum = "One gate down. The shape of the run is familiar now."
  else                   momentum = gleft " gates left. The record holds every one you closed."
  if (quiet_days >= 3)   momentum = quiet_days " days quiet. The record kept your place. " momentum

  measured = m_days_s " " DOT " " m_commits_s " " DOT " " m_files_s " tracked"
  if (m_gate_commits_s != "") measured = measured " " DOT " " m_gate_commits_s " since the last gate passed"
  dayn = days + 1
  gates_line = bar(gfaced, gtotal) "  " gfaced "/" gtotal " faced " DOT " " gpassed " passed " DOT " " gwaived " waived"
  miles_line = bar(mdone, mtotal) "  " mdone "/" mtotal " " DOT " next: " esc(mnext)
  belt_line  = esc(belt) (rung != "" ? " " DOT " next " rung : " " DOT " the ladder is climbed")
  declared_note = "declared by the coach, NOT measured by this kit"

  P = esc(project); S = esc(started); PR = esc(profile)

  if (fmt == "md") {
    print "# THE RUN " DASH " " P
    print ""
    print "`day " dayn " " DOT " run belt " esc(belt) " " DOT " started " S " " DOT " " PR "`"
    print ""
    print "```"
    if (nnext != "" || nnow != "" || nafter != "") {
      if (nnext != "")  row_md("NEXT", nnext)
      if (nnow != "")   row_md("NOW", nnow)
      if (nafter != "") row_md("AFTER", nafter)
      print ""
    }
    row_md("MAP", map)
    cont_md("[x] passed " DOT " [~] waived " DOT " [>] open now " DOT " [ ] ahead")
    row_md("GATES", gates_line)
    row_md("MILESTONES", miles_line)
    row_md("RUN BELT", belt_line)
    print ""
    nv = split(villains(), vl, "\n")
    for (i = 1; i <= nv; i++) { if (i == 1) row_md("VILLAINS", vl[i]); else cont_md(vl[i]) }
    if (ncaught > 0) {
      print ""
      for (i = 1; i <= ncaught; i++) {
        v = "day " caught_d[i] "  " caught_what[i] " " DASH " " caught_where[i]
        if (i == 1) row_md("CAUGHT", v); else cont_md(v)
      }
    }
    if (nwaived > 0) {
      print ""
      for (i = 1; i <= nwaived; i++) {
        v = waived_g[i] (waived_why[i] != "" && waived_why[i] != "-" ? " " DASH " " waived_why[i] : "")
        if (i == 1) row_md("WAIVED", v); else cont_md(v)
      }
    }
    if (nparked > 0) {
      print ""
      for (i = 1; i <= nparked; i++) {
        v = parked_ask[i] " (revisit at " parked_rev[i] ")"
        if (i == 1) row_md("PARKED", v); else cont_md(v)
      }
    }
    print "```"
    print ""
    if (complete) {
      print "```"
      print "RUN CLEARED " DASH " " P
      printf "%s  %d/%d gates faced %s %d/%d milestones %s %s\n\n", bar(gfaced, gtotal), gfaced, gtotal, DOT, mdone, mtotal, DOT, belt
      printf "the record     %s %s %s %s %d gate(s) waived, on the record\n", m_days_s, DOT, m_commits_s, DOT, gwaived
      if (ncaught > 0) { for (i = 1; i <= ncaught; i++) printf "%-14s %s %s %s\n", (i == 1 ? "what held" : ""), caught_what[i], DASH, caught_where[i] }
      else print "what held      nothing broke through - the floor held from the start"
      print "what you can   say \"checked and evidenced, per the Start Guide\047s"
      print "say            heuristics\" - and mean it, because the journal is there"
      print "what you       say your app is secure. This kit never said it. Neither"
      print "cannot say     do you."
      print ""
      print "The cabinet goes quiet. Say Hello World to the next one."
      print "```"
    } else {
      printf "*%s*\n", momentum
    }
    print ""
    if (ahead) {
      print "> **SHIPPING AHEAD OF SAFETY**"
      printf "> The product is moving (%d milestones done) while 20-SECURITY is still %s.\n", mdone, sec
      print "> That gap is the one this run exists to close."
      print ""
    }
    print "## FinOps"
    print ""
    print "```"
    printf "measured (git)     %s\n", measured
    if (crows > 0) {
      printf "declared           %d session(s) across %d step(s)\n", csessions, crows
      if (ctokens != "") printf "  tokens           %s%s\n", ctokens, (ctokmore > 3 ? " +" (ctokmore - 3) " more" : "")
      printf "                   %s\n", declared_note
    } else {
      print "declared           nothing declared yet - the coach fills the"
      print "                   scoreboard Cost table at each Gate Report"
    }
    print "```"
    print ""
    print "---"
    print ""
    print "Every state above is declared by you and your coach, from"
    print "`state/scoreboard.md`; nothing here inspects your app. A rendering,"
    print "rewritten on every commit: the scoreboard is the record, and if the"
    print "two disagree, the scoreboard wins."
    print ""
    print "The Start Guide is a coach, not a certification: its guardrails are"
    print "heuristic defaults applied by your own agent. It hardens the builder;"
    print "it never certifies the app."
    exit
  }

  # ---- HTML: the same data, the house tokens, nothing animated.
  print "<!doctype html>"
  print "<html lang=\"en\"><head><meta charset=\"utf-8\">"
  print "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
  print "<meta http-equiv=\"refresh\" content=\"60\">"
  print "<title>THE RUN " DASH " " P "</title>"
  print "<style>"
  print ":root{color-scheme:dark;--sb-bg:#0a0a0f;--sb-bg-card:#16161f;--sb-border:#222233;--sb-text:#f0f0f5;--sb-text-secondary:#a0a0b0;--sb-text-muted:#606070;--sb-gold:#f0a500;--sb-ember:#f08a2a;--sb-amber:#ffb347;--sb-beast-presence:#d2541c}"
  print "body{margin:0;background:var(--sb-bg);color:var(--sb-text);font:14px/1.6 \"JetBrains Mono\",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;padding:2rem 1rem}"
  print ".wrap{max-width:46rem;margin:0 auto}"
  print ".card{border:1px solid var(--sb-border);border-radius:10px;padding:1.4rem 1.6rem;background:var(--sb-bg-card)}"
  print "h1{font-size:1.1rem;letter-spacing:.28em;text-transform:uppercase;color:var(--sb-gold);margin:0 0 .2rem}"
  print ".sub{color:var(--sb-text-secondary);font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;margin:0 0 1.2rem}"
  print ".row{display:flex;gap:1rem;align-items:baseline;margin:.3rem 0;flex-wrap:wrap}"
  print ".lab{width:6.5rem;color:var(--sb-text-secondary);text-transform:uppercase;font-size:.72rem;letter-spacing:.12em;flex:none}"
  print ".val{white-space:pre-wrap}"
  print ".next .val{color:var(--sb-text);font-weight:600}"
  print ".gates .val{color:var(--sb-gold)}"
  print ".miles .val{color:var(--sb-ember)}"
  print ".legend .val{color:var(--sb-text-muted);font-size:.74rem}"
  print ".block{margin:.9rem 0 0;padding-top:.8rem;border-top:1px solid var(--sb-border)}"
  print ".done{margin:1rem 0 0;padding:.9rem 1rem;border:1px solid var(--sb-gold);border-radius:8px;color:var(--sb-gold);white-space:pre-wrap;font-size:.82rem}"
  print ".momentum{margin:.9rem 0 0;color:var(--sb-text-secondary);font-size:.8rem;font-style:italic}"
  print ".alert{margin:1.3rem 0 0;padding:.85rem 1rem;border-left:3px solid var(--sb-beast-presence);background:var(--sb-bg-card);color:var(--sb-amber)}"
  print ".alert b{color:var(--sb-beast-presence);letter-spacing:.1em}"
  print ".fin h2{font-size:.72rem;letter-spacing:.2em;text-transform:uppercase;color:var(--sb-text-secondary);margin:0 0 .5rem}"
  print ".note{color:var(--sb-text-muted);font-size:.72rem}"
  print ".foot{margin:1.3rem 0 0;padding-top:1rem;border-top:1px solid var(--sb-border);color:var(--sb-text-muted);font-size:.74rem;line-height:1.7}"
  print ".foot a{color:var(--sb-text-secondary)}"
  print "</style></head><body><div class=\"wrap\"><div class=\"card\">"
  printf "<h1>The Run %s %s</h1>\n", DASH, P
  printf "<p class=\"sub\">day %d %s run belt %s %s started %s %s %s</p>\n", dayn, DOT, esc(belt), DOT, S, DOT, PR
  if (nnext != "" || nnow != "" || nafter != "") {
    print "<div class=\"next\">"
    if (nnext != "")  row_html("Next", esc(nnext))
    if (nnow != "")   row_html("Now", esc(nnow))
    if (nafter != "") row_html("After", esc(nafter))
    print "</div><div class=\"block\"></div>"
  }
  row_html("Map", map)
  print "<div class=\"legend\">"; row_html("", "[x] passed " DOT " [~] waived " DOT " [>] open now " DOT " [ ] ahead"); print "</div>"
  print "<div class=\"gates\">"; row_html("Gates", gates_line); print "</div>"
  print "<div class=\"miles\">"; row_html("Milestones", miles_line); print "</div>"
  row_html("Run belt", belt_line)
  print "<div class=\"block\"></div>"
  nv = split(villains(), vl, "\n")
  for (i = 1; i <= nv; i++) row_html((i == 1 ? "Villains" : ""), esc(vl[i]))
  if (ncaught > 0) { print "<div class=\"block\"></div>"; for (i = 1; i <= ncaught; i++) row_html((i == 1 ? "Caught" : ""), esc("day " caught_d[i] "  " caught_what[i] " " DASH " " caught_where[i])) }
  if (nwaived > 0) { print "<div class=\"block\"></div>"; for (i = 1; i <= nwaived; i++) row_html((i == 1 ? "Waived" : ""), esc(waived_g[i] (waived_why[i] != "" && waived_why[i] != "-" ? " " DASH " " waived_why[i] : ""))) }
  if (nparked > 0) { print "<div class=\"block\"></div>"; for (i = 1; i <= nparked; i++) row_html((i == 1 ? "Parked" : ""), esc(parked_ask[i] " (revisit at " parked_rev[i] ")")) }
  if (complete) {
    print "<div class=\"done\">"
    printf "RUN CLEARED %s %s\n", DASH, P
    printf "%s  %d/%d gates faced %s %d/%d milestones %s %s\n\n", bar(gfaced, gtotal), gfaced, gtotal, DOT, mdone, mtotal, DOT, esc(belt)
    printf "the record     %s %s %s %s %d gate(s) waived, on the record\n", m_days_s, DOT, m_commits_s, DOT, gwaived
    if (ncaught > 0) { for (i = 1; i <= ncaught; i++) printf "%-14s %s\n", (i == 1 ? "what held" : ""), esc(caught_what[i] " " DASH " " caught_where[i]) }
    else print "what held      nothing broke through - the floor held from the start"
    print "what you can   say \"checked and evidenced, per the Start Guide\047s"
    print "say            heuristics\" - and mean it, because the journal is there"
    print "what you       say your app is secure. This kit never said it. Neither"
    print "cannot say     do you."
    print ""
    print "The cabinet goes quiet. Say Hello World to the next one."
    print "</div>"
  } else {
    printf "<p class=\"momentum\">%s</p>\n", momentum
  }
  if (ahead) {
    print "<p class=\"alert\"><b>SHIPPING AHEAD OF SAFETY</b><br>"
    printf "The product is moving (%d milestones done) while 20-SECURITY is still %s. That gap is the one this run exists to close.</p>\n", mdone, sec
  }
  print "<div class=\"block fin\"><h2>FinOps</h2>"
  row_html("Measured", measured)
  if (crows > 0) {
    row_html("Declared", csessions " session(s) across " crows " step(s)")
    if (ctokens != "") row_html("Tokens", esc(ctokens) (ctokmore > 3 ? " +" (ctokmore - 3) " more" : ""))
    printf "<p class=\"note\">%s: it has no telemetry and cannot see your usage or your bill.</p>\n", declared_note
  } else {
    print "<p class=\"note\">Nothing declared yet - the coach fills the scoreboard Cost table at each Gate Report.</p>"
  }
  print "</div>"
  print "<div class=\"foot\">"
  print "Every state above is declared by you and your coach, from state/scoreboard.md; nothing here inspects your app. A rendering, rewritten on every commit: the scoreboard is the record, and if the two disagree, the scoreboard wins.<br>"
  print "The Start Guide is a coach, not a certification: its guardrails are heuristic defaults applied by your own agent. It hardens the builder; it never certifies the app.<br>"
  print "<a href=\"https://skillboss.dev/start\">SkillBoss Start Guide</a> " DOT " this page is local and makes no network request."
  print "</div></div></div></body></html>"
}
' "$board"
}

render md > "$out_md" 2>/dev/null || printf '%s\n' \
  '# THE RUN' '' \
  'The run screen could not be rendered from `state/scoreboard.md`.' \
  'Ask your coach to regenerate the scoreboard from the journal.' > "$out_md"

render html > "$out_html" 2>/dev/null || printf '%s\n' \
  '<!doctype html><meta charset="utf-8"><title>THE RUN</title>' \
  '<body style="background:#0a0a0f;color:#f0f0f5;font:14px ui-monospace,monospace;padding:2rem">' \
  'The run screen could not be rendered from state/scoreboard.md.' \
  'Ask your coach to regenerate the scoreboard from the journal.</body>' > "$out_html"

exit 0
