#!/usr/bin/env bash
# H7 VOCAB SELF · Hostess 7 expands + codes her own vocabulary · correct to reality
# never ML · iron census · TRAVEL L→R · READ R←L · GO FIRE SHOOT · C IS LIE
#
#   ./Build/h7-vocab-self.sh once|status|expand|code|full
#
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="$ROOT/out"
FAST="$OUT/h7fast"
VOCAB_TSV="$OUT/h7_vocab_self.tsv"       # word \t limb \t kind \t correct \t note
VOCAB_JSON="$OUT/h7_vocab_self.json"
VOCAB_SHEET="$OUT/h7_vocab_self_sheet.txt"
VOCAB_ASM="$OUT/h7_vocab_coded.asm"      # she codes reality words as asm data
LEARN_JSON="$OUT/h7_learn_process.json"  # detailed learn/relearn pipeline state
GROWTH_LOG="$OUT/h7_vocab_growth.log"    # append-only learn events
GROWTH_FEED="$OUT/h7_vocab_feed.tsv"     # this-tick learns for page ticker
VIZ_JSON="$OUT/h7_viz.json"              # physical learning viz seeds
CURSOR="$OUT/h7_learn_cursor.json"       # linear progress · wave · offset · remaining
PROGRESS="$OUT/h7_learn_progress.txt"    # human one-line status
PROC_LOG="$OUT/h7_learn_process.log"
WORKQ="$FAST/vocab_work_queue.txt"       # linear remaining work (not A→Z display)
OBJD="$FAST/obj"
BIND="$FAST/bin"
cmd="${1:-full}"
TS="$(date -Iseconds)"

say() { echo "$*"; echo "[$TS] $*" >>"$PROC_LOG"; }
mkdir -p "$OUT"

# Reality law filters · HUMAN speech roots · not hex · not arch scrap · not thrash
word_correct() {
  local w="$1" note="${2:-}"
  local low
  low=$(printf '%s' "$w" | tr '[:upper:]' '[:lower:]')

  # empty / illegal chars
  [[ -z "$low" ]] && { echo 0; return; }
  [[ "$low" =~ [^a-z0-9_] ]] && { echo 0; return; }
  [[ ${#low} -lt 3 || ${#low} -gt 32 ]] && { echo 0; return; }

  # ── HEX / machine ids (not human words) ──
  # 0x… · pure hex strings · leading x + hex (pci vendor x10de, res x1080)
  [[ "$low" =~ ^0x[0-9a-f_]+$ ]] && { echo 0; return; }
  [[ "$low" =~ ^[0-9]+$ ]] && { echo 0; return; }
  [[ "$low" =~ ^[0-9a-f]{4,}$ ]] && { echo 0; return; }
  [[ "$low" =~ ^x[0-9a-f]{2,}$ ]] && { echo 0; return; }
  # mostly digits with thin letter glaze
  if [[ "$low" =~ [0-9] ]]; then
    local digits letters
    digits=$(printf '%s' "$low" | tr -cd '0-9' | wc -c)
    letters=$(printf '%s' "$low" | tr -cd 'a-z' | wc -c)
    # more digits than letters → machine token not speech
    [[ "$digits" -ge "$letters" ]] && { echo 0; return; }
  fi

  # ── multiarch twin scrap (not vocabulary roots) ──
  case "$low" in
    z80|6502|i386|m68k|mips|mips32|mipsel|riscv|riscv64|aarch|aarch64|atari|atari2600|x86_64|x86|arm64|wasm32)
      echo 0; return ;;
  esac

  # ── foundation speech always human ──
  case "$low" in
    no|not|god|yes|are|is|grin|know|truth|win) echo 1; return ;;
  esac

  # ── thrash / virus / inject ──
  case "$low" in
    thrash|virus|malware|inject|jailbreak|ransomware|exploit|payload) echo 0; return ;;
  esac

  # ── invent C as god ──
  case "$low" in c|god_c|pin_c|third_pin) echo 0; return ;; esac

  # ── neural-as-ground ──
  case "$low" in neural_net|backprop|gradient_descent|llm_ground) echo 0; return ;; esac

  # ── English stopwords / prose glue ──
  # NOTE: are · is are FOUNDATION words (ARE IS · IS ARE) · never stopwords
  case "$low" in
    the|and|for|from|with|that|this|into|over|under|usually|user|use|via|txt|then|than|when|what|which|where|while|have|has|had|was|were|been|being|will|would|could|should|about|after|before|between|because|through|during|without|within|above|below|again|further|once|here|there|all|each|few|more|most|other|some|such|only|own|same|too|very|can|just|don|now|also|but|you|your|our|they|them|their|its|his|her|how|why|who|any|may|might|must|shall|like|well|back|even|still|see|get|got|make|made|using|used|new|old|way|ways|one|two|out|off|end|first|last|next|many|much|every|both|either|neither|does|did|doing|done|http|https|www|com|org|html|json|null|true|false|undefined|function|return|const|var|let|class|import|export|default|public|private|static|void|int|long|char|byte|file|path|name|type|data|item|list|map|set|key|value|test|tests|todo|fix|info|debug|error|warn|ok|yes_fn|no_fn|are_fn|is_fn|db|dd|dw|resb|section|global|extern|bits|align)
      echo 0; return ;;
  esac

  # ── must look speakable: has a vowel OR is known desk acronym ──
  case "$low" in
    bgs|bgf|bgl|sdf|spv|rtx|gpu|cpu|pci|hdmi|usb|ssd|ram|vram|avx|api|dns|tcp|udp|ssh|ssl|tls|xml|csv|tsv|ppm|png|jpg|gif|pdf|elf|asm|nasm|spv|si|h7|ml|av|os|mx|spf|imap|smtp|dkim|ttl|ptr|soa)
      echo 1; return ;;
  esac
  if ! [[ "$low" =~ [aeiou] ]]; then
    echo 0; return
  fi

  # reject __dunder__ and trailing _fn machine stubs as speech roots
  [[ "$low" =~ ^_ ]] && { echo 0; return; }
  [[ "$low" =~ _fn$ ]] && { echo 0; return; }

  echo 1
}

# kind from note / name
classify_kind() {
  local w="$1" note="$2"
  local low nlow
  low=$(printf '%s' "$w" | tr '[:upper:]' '[:lower:]')
  nlow=$(printf '%s' "$note" | tr '[:upper:]' '[:lower:]')
  if [[ "$low" =~ ^(go|fire|shoot)$ ]]; then echo WORD_END; return; fi
  if [[ "$low" =~ ^(bgs|bgf|bgl|sdf|spv)$ ]]; then echo LINE_PIN; return; fi
  if [[ "$low" =~ ^(ezzie|phi|thermo|adjoin)$ ]]; then echo ATOM; return; fi
  if [[ "$low" =~ ^(grin|god|yes|no|not|are|is|know|truth) ]]; then echo FOUNDATION; return; fi
  if [[ "$low" =~ ^(hostess7|educate|self_|language|actual|learn|teach|code) ]]; then echo EDUCATION; return; fi
  if [[ "$nlow" =~ scar|cook ]]; then echo SCAR_COOK; return; fi
  if [[ "$nlow" =~ free|rng ]]; then echo FREE; return; fi
  if [[ "$nlow" =~ gpu|rtx|hdmi|alsa ]]; then echo IRON_HW; return; fi
  echo LIMB
}

expand_census() {
  say "VOCAB EXPAND · census limbs · merge grown words · never wipe learn"
  local tmp="$FAST/vocab_census.partial"
  mkdir -p "$FAST"
  : >"$tmp"
  # keep ALL prior learned rows · never wipe language/assist/grow (persistent learning)
  if [[ -f "$VOCAB_TSV" ]]; then
    awk -F'\t' '
      $2=="_grow" || $2=="_core" || $2=="_sheet" || $2=="_lang" || $2=="_assist" || $2=="_langpack" {
        print
      }
    ' "$VOCAB_TSV" >>"$tmp" || true
  fi
  local d limb note correct kind n_ok=0 n_bad=0 n_total=0
  for d in "$ROOT"/*/; do
    limb=$(basename "$d")
    [[ "$limb" =~ ^\. ]] && continue
    case "$limb" in
      out|Build|KateRTX|Data|json|AMOURANTHRTX|OpenPaperWork|BlueBox|RTXLayer|heuristics|multiarch) continue ;;
    esac
    [[ -f "$d/x86_64.asm" ]] || continue
    note=$(grep -m1 -E '^\s*;\s*(NAME|NOTE|OP|WORD)' "$d/x86_64.asm" 2>/dev/null \
      | sed 's/^\s*;\s*//' | tr '\t' ' ' | head -c 200 || true)
    [[ -z "$note" ]] && note=$(head -1 "$d/x86_64.asm" | sed 's/^\s*;\s*//' | tr '\t' ' ' | head -c 160 || true)
    correct=$(word_correct "$limb" "$note")
    kind=$(classify_kind "$limb" "$note")
    printf '%s\t%s\t%s\t%s\t%s\n' "$limb" "$limb" "$kind" "$correct" "$note" >>"$tmp"
  done

  local core_words=(
    "TRAVEL:direction:LINEAR travel left to right"
    "READ:direction:vocab sense right to left word-end first"
    "GO:WORD_END:start motion live |1"
    "FIRE:WORD_END:commit energy live |1"
    "SHOOT:WORD_END:field discharge live |1"
    "GRIN:FOUNDATION:free=1 pole truth"
    "C_IS_LIE:LAW:inventing pin C is flaw"
    "LIE_IS_TRUE:LAW:marking the lie is true detection"
    "HOSTESS7:EDUCATION:TOP kit seat not ground"
    "FLAT:LAW:pair commute no rank"
    "LINEAR:LAW:BGS to BGF to BGL to SDF SPV free"
    "FREE_THRIFT:LAW:SPV never folds into place"
    "NEVER_ML:LAW:finite iron only"
    "RELEARN:PROCESS:fast recheck in-process"
    "EXPAND:PROCESS:vocab census grow correct words"
    "SELF_CODE:PROCESS:she codes vocabulary table"
    "SELF_GUIDE:PROCESS:modes 9 10 11"
    "FULL_127:SEAL:brute finite complete mark"
  )
  local ent w kind note
  for ent in "${core_words[@]}"; do
    w=${ent%%:*}; rest=${ent#*:}; kind=${rest%%:*}; note=${rest#*:}
    printf '%s\t%s\t%s\t%s\t%s\n' "$w" "_core" "$kind" "1" "$note" >>"$tmp"
  done

  LC_ALL=C sort -t$'\t' -k1,1 -u "$tmp" -o "$VOCAB_TSV"
  # re-verify all rows with current filter
  local fixed="$FAST/vocab_verified.tsv"
  : >"$fixed"
  while IFS=$'\t' read -r w src kind correct note; do
    [[ -z "$w" ]] && continue
    if [[ "$src" == "_core" || "$src" == "_lang" || "$src" == "_assist" || "$src" == "_langpack" ]]; then
      # persistent curriculum / assist · keep correct=1 (already filtered at accept)
      printf '%s\t%s\t%s\t%s\t%s\n' "$w" "$src" "${kind:-EDUCATION}" "1" "$note" >>"$fixed"
      continue
    fi
    c=$(word_correct "$w" "$note")
    k=$(classify_kind "$w" "$note")
    printf '%s\t%s\t%s\t%s\t%s\n' "$w" "$src" "$k" "$c" "$note" >>"$fixed"
  done <"$VOCAB_TSV"
  mv "$fixed" "$VOCAB_TSV"
  n_total=$(wc -l <"$VOCAB_TSV" | tr -d ' ')
  n_ok=$(awk -F'\t' '$4==1{c++} END{print c+0}' "$VOCAB_TSV")
  n_bad=$((n_total - n_ok))
  say "VOCAB EXPAND done · words=$n_total correct=$n_ok reject=$n_bad"
  echo "$n_total $n_ok $n_bad"
}

# re-verify entire TSV with current human filter · demote junk hex/arch/stopwords
reverify_all() {
  say "VOCAB REVERIFY · human filter · drop hex/arch/noise"
  [[ -f "$VOCAB_TSV" ]] || return 0
  local fixed="$FAST/vocab_reverified.tsv" kept=0 drop=0
  mkdir -p "$FAST"
  : >"$fixed"
  while IFS=$'\t' read -r w src kind correct note; do
    [[ -z "$w" ]] && continue
    local c k
    if [[ "$src" == "_core" || "$src" == "_lang" || "$src" == "_assist" || "$src" == "_langpack" || "$src" == "_grow" ]]; then
      # persistent learn rows · never drop on reverify thrash
      c=1; k="${kind:-EDUCATION}"
    else
      c=$(word_correct "$w" "$note")
      k=$(classify_kind "$w" "$note")
    fi
    if [[ "$c" == "1" ]]; then
      printf '%s\t%s\t%s\t%s\t%s\n' "$w" "$src" "$k" "1" "$note" >>"$fixed"
      kept=$((kept + 1))
    else
      drop=$((drop + 1))
      printf '%s\t%s\tREJECT\treject\n' "$TS" "$w" >>"$GROWTH_LOG"
    fi
  done <"$VOCAB_TSV"
  LC_ALL=C sort -t$'\t' -k1,1 -u "$fixed" -o "$VOCAB_TSV"
  say "VOCAB REVERIFY · kept=$kept dropped=$drop"
}

# rebuild linear work queue (known removed) · NOT the display sort of lexicon
rebuild_work_queue() {
  mkdir -p "$FAST"
  local known="$FAST/vocab_known.txt" raw="$FAST/vocab_raw_pool.txt"
  cut -f1 "$VOCAB_TSV" | tr '[:upper:]' '[:lower:]' | LC_ALL=C sort -u >"$known"
  : >"$raw"

  # LINEAR waves of candidates (order fixed · cursor walks this line)
  # wave0 foundation speech
  printf '%s\n' travel read go fire shoot grin truth free pure linear flat seal \
    hostess educate learn teach code language actual iron field measure \
    ground place secure clean free_thrift never_ml c_is_lie lie_is_true \
    are is no not god yes baphomet \
    bgs bgf bgl sdf spv ezzie phi thermo adjoin scar rescar cook \
    confidence creates when_grin remain_in free_ride word_end iron_rhyme \
    self_learn self_code self_teach self_guide self_update relearn expand \
    constellation kit seven cheby onion catch salt face sphere csg \
    desktop employee magazine claws waterfox synaptic pipewire \
    >>"$raw"

  # wave1 compounds · stems × acts (linear nested, not alpha)
  local stems acts s a i j
  stems=(field measure truth free pure linear grin hostess learn code travel read seal iron desk scar cook pure_linear layer truth_layer until)
  acts=(go fire shoot live free grin seal learn code mark accept guide update)
  for s in "${stems[@]}"; do
    for a in "${acts[@]}"; do
      printf '%s_%s\n' "$s" "$a"
      printf '%s_%s\n' "$a" "$s"
    done
  done >>"$raw"

  # wave2 VOCAB.txt stems then LANGUAGE tokens
  if [[ -f "$ROOT/VOCAB.txt" ]]; then
    grep -oE '^[a-z0-9_]+/' "$ROOT/VOCAB.txt" 2>/dev/null | tr -d '/' >>"$raw" || true
    grep -oE '[A-Za-z][A-Za-z_]{3,24}' "$ROOT/VOCAB.txt" >>"$raw" || true
  fi
  if [[ -f "$ROOT/LANGUAGE.txt" ]]; then
    grep -oE '[A-Za-z][A-Za-z_]{3,24}' "$ROOT/LANGUAGE.txt" >>"$raw" || true
  fi
  # wave3 limb notes
  awk -F'\t' '{print $5}' "$VOCAB_TSV" 2>/dev/null | grep -oE '[A-Za-z][A-Za-z]{3,24}' >>"$raw" || true

  # wave4 deeper linear phrases
  local pins ends p e
  pins=(bgs bgf bgl sdf spv grin hostess scar cook)
  ends=(step next more full half live free seal)
  for p in "${pins[@]}"; do
    for e in "${ends[@]}"; do
      printf '%s_%s\n' "$p" "$e"
    done
  done >>"$raw"

  # FAST filter · awk join vs known · no per-line word_correct (was hitching balls ~50s)
  local wave_n=1
  if [[ -f "$CURSOR" ]]; then
    wave_n=$(grep -oE '"wave":[[:space:]]*[0-9]+' "$CURSOR" | head -1 | grep -oE '[0-9]+$' || echo 1)
  fi
  {
    tr '[:upper:]' '[:lower:]' <"$raw"
    # always inject accept-safe linear tokens (never empty queue)
    local i s a
    for i in $(seq 0 63); do
      printf 'wave%s_step%s\n' "$wave_n" "$i"
      printf 'learn_w%s_%s\n' "$wave_n" "$i"
      printf 'field_w%s_%s\n' "$wave_n" "$i"
    done
    for s in field truth free pure linear grin hostess learn code iron desk; do
      for a in go live free seal mark grow deep more; do
        printf '%s_%s\n' "$s" "$a"
        printf '%s_%s_%s\n' "$s" "$a" "$wave_n"
      done
    done
    if [[ -f "$OUT/h7_language_curriculum.tsv" ]]; then
      cut -f1 "$OUT/h7_language_curriculum.tsv" 2>/dev/null | tr '[:upper:]' '[:lower:]' || true
    fi
  } | awk 'NF && !seen[$0]++' \
    | awk 'length($0)>=3 && length($0)<=28 && $0 !~ /^[0-9a-f]{4,}$/ && $0 !~ /^x[0-9a-f]+$/' \
    | grep -v -F -x -f "$known" 2>/dev/null \
    | head -n 400 >"$WORKQ" || true

  local rem
  rem=$(wc -l <"$WORKQ" 2>/dev/null | tr -d ' ')
  if [[ "${rem:-0}" -lt 16 ]]; then
    : >"$WORKQ"
    local i
    for i in $(seq 0 47); do
      printf 'wave%s_step%s\n' "$wave_n" "$i" >>"$WORKQ"
      printf 'learn_w%s_%s\n' "$((wave_n+1))" "$i" >>"$WORKQ"
    done
    rem=$(wc -l <"$WORKQ" | tr -d ' ')
  fi
  say "WORK QUEUE rebuild · remaining=$rem · path=$WORKQ"
}

write_cursor() {
  # linear progress state · bash JSON
  local wave="$1" offset="$2" remaining="$3" newc="$4" status="$5" next_w="$6"
  cat >"$CURSOR" <<EOF
{
  "ts": "$TS",
  "word": "H7_LEARN_CURSOR",
  "stack": "bash+asm",
  "linear": true,
  "wave": $wave,
  "offset": $offset,
  "remaining": $remaining,
  "batch_new": $newc,
  "status": "$status",
  "next_word": "$next_w",
  "path": {
    "work_queue": "out/h7fast/vocab_work_queue.txt",
    "feed": "out/h7_vocab_feed.tsv",
    "lexicon": "out/h7_vocab_self.tsv",
    "viz": "out/h7_viz.json",
    "progress": "out/h7_learn_progress.txt"
  },
  "law": "LINEAR · done batch → continue offset → progress remaining · not stuck A-Z"
}
EOF
  printf 'wave=%s offset=%s remaining=%s new=%s status=%s next=%s ts=%s\n' \
    "$wave" "$offset" "$remaining" "$newc" "$status" "$next_w" "$TS" >"$PROGRESS"
  say "PROGRESS · $(cat "$PROGRESS")"
}

# continuous grow · LINEAR cursor through work queue · done → continue → progress
grow_new_words() {
  say "VOCAB GROW · LINEAR cursor · verify · learn NEW"
  [[ -f "$VOCAB_TSV" ]] || expand_census >/dev/null
  mkdir -p "$FAST"
  local before after newc=0 rej=0 limit="${H7_GROW_BATCH:-24}"
  local offset=0 wave=1
  before=$(wc -l <"$VOCAB_TSV" | tr -d ' ')

  # load cursor
  if [[ -f "$CURSOR" ]]; then
    offset=$(grep -oE '"offset":[[:space:]]*[0-9]+' "$CURSOR" | head -1 | grep -oE '[0-9]+$' || echo 0)
    wave=$(grep -oE '"wave":[[:space:]]*[0-9]+' "$CURSOR" | head -1 | grep -oE '[0-9]+$' || echo 1)
  fi
  offset=${offset:-0}
  wave=${wave:-1}

  # rebuild queue only when empty/missing/low · full rebuild every tick was thrash at 100k+ vocab
  local remaining=0
  if [[ -f "$WORKQ" ]]; then
    remaining=$(wc -l <"$WORKQ" | tr -d ' ')
  fi
  remaining=${remaining:-0}
  if [[ ! -f "$WORKQ" || "$remaining" -lt 8 || ! -f "$FAST/vocab_known.txt" ]]; then
    rebuild_work_queue
    remaining=$(wc -l <"$WORKQ" | tr -d ' ')
    remaining=${remaining:-0}
  fi

  # if offset past end · wrap wave++ and restart queue (continue forever finite)
  if [[ "$remaining" -eq 0 ]]; then
    wave=$((wave + 1))
    offset=0
    # inject wave-tagged synthetic linear lessons so we never freeze
    local n=0
    while [[ $n -lt 40 ]]; do
      printf 'wave%d_step%d\n' "$wave" "$n" >>"$WORKQ"
      printf 'line%d_mark%d\n' "$wave" "$n" >>"$WORKQ"
      n=$((n + 1))
    done
    remaining=$(wc -l <"$WORKQ" | tr -d ' ')
    say "WAVE ADVANCE · wave=$wave · synthetic linear steps · remaining=$remaining"
  fi
  if [[ "$offset" -ge "$remaining" ]]; then
    offset=0
    wave=$((wave + 1))
    say "CURSOR WRAP · wave=$wave offset=0"
  fi

  local known="$FAST/vocab_known.txt"
  # keep known incremental · full re-sort only if missing/stale empty
  if [[ ! -s "$known" ]]; then
    cut -f1 "$VOCAB_TSV" | tr '[:upper:]' '[:lower:]' | LC_ALL=C sort -u >"$known"
  fi
  : >"$FAST/vocab_new.partial"

  # take linear slice from offset
  local slice="$FAST/vocab_slice.txt"
  tail -n +"$((offset + 1))" "$WORKQ" | head -n "$limit" >"$slice" || true

  local w correct kind note next_w=""
  next_w=$(head -1 "$slice" 2>/dev/null || echo "")
  while IFS= read -r w; do
    [[ -z "$w" ]] && continue
    offset=$((offset + 1))
    if grep -qxF "$w" "$known" 2>/dev/null; then
      continue
    fi
    note="linear grow wave=$wave offset=$offset"
    correct=$(word_correct "$w" "$note")
    # allow waveN_stepN synthetic as PROCESS marks (force accept patterned linear)
    if [[ "$w" =~ ^wave[0-9]+_step[0-9]+$ || "$w" =~ ^line[0-9]+_mark[0-9]+$ ]]; then
      correct=1
      kind=PROCESS
    else
      kind=$(classify_kind "$w" "$note")
    fi
    if [[ "$correct" == "1" ]]; then
      printf '%s\t%s\t%s\t%s\t%s\n' "$w" "_grow" "$kind" "1" "$note" >>"$FAST/vocab_new.partial"
      printf '%s\t%s\t%s\t%s\n' "$TS" "$w" "$kind" "learn" >>"$GROWTH_LOG"
      echo "$w" >>"$known"
      newc=$((newc + 1))
    else
      printf '%s\t%s\t%s\t%s\n' "$TS" "$w" "REJECT" "reject" >>"$GROWTH_LOG"
      rej=$((rej + 1))
    fi
  done <"$slice"

  # unstick · slice all known/reject → force synthetic accepts (always learn something)
  if [[ ! -s "$FAST/vocab_new.partial" ]]; then
    say "GROW UNSTICK · force synthetic learn batch"
    local u=0
    while [[ $u -lt 16 ]]; do
      local uw="learn_w${wave}_${offset}_$u"
      if ! grep -qxF "$uw" "$known" 2>/dev/null; then
        printf '%s\t%s\t%s\t%s\t%s\n' "$uw" "_grow" "PROCESS" "1" "unstick synthetic" >>"$FAST/vocab_new.partial"
        printf '%s\t%s\t%s\t%s\n' "$TS" "$uw" "PROCESS" "learn" >>"$GROWTH_LOG"
        echo "$uw" >>"$known"
        newc=$((newc + 1))
      fi
      u=$((u + 1))
    done
  fi

  local status="continue"
  if [[ -s "$FAST/vocab_new.partial" ]]; then
    # append only · full sort-unique every ball was O(N log N) thrash at 100k+ rows
    cat "$FAST/vocab_new.partial" >>"$VOCAB_TSV"
    cut -f1 "$FAST/vocab_new.partial" | tr '[:upper:]' '[:lower:]' >>"$known"
    awk -F'\t' -v ts="$TS" '{print ts"\t"$1"\t"$3"\tlearn"}' "$FAST/vocab_new.partial" >"$GROWTH_FEED"
    status="learned"
  else
    # no new this slice · progress cursor but KEEP scroller alive with real words
    remaining=$(wc -l <"$WORKQ" | tr -d ' ')
    if [[ "$offset" -ge "$remaining" ]]; then
      status="done"
      offset=0
      wave=$((wave + 1))
    else
      status="continue"
    fi
    : >"$GROWTH_FEED"
    printf '%s\t%s\t%s\t%s\n' "$TS" "CONTINUE" "PROCESS" "progress" >>"$GROWTH_FEED"
    printf '%s\t%s\t%s\t%s\n' "$TS" "wave${wave}_offset${offset}" "PROCESS" "progress" >>"$GROWTH_FEED"
    [[ "$status" == "done" ]] && printf '%s\t%s\t%s\t%s\n' "$TS" "DONE_WAVE_${wave}" "PROCESS" "done" >>"$GROWTH_FEED"
  fi
  # always append scroll fuel · window slides with offset+epoch so scroller CHANGES each tick
  local slide
  slide=$(( (offset + ${wave:-1} * 17 + $(date +%s) / 20 ) % 200 ))
  {
    printf '%s\t%s\t%s\t%s\n' "$TS" "tick_$(date +%H%M%S)" "PROCESS" "progress"
    grep 'learn$' "$GROWTH_LOG" 2>/dev/null | tail -25 | awk -F'\t' -v ts="$TS" '{print ts"\t"$2"\t"$3"\tscroll"}'
    awk -F'\t' -v o="$slide" 'BEGIN{o=o+0} $4==1{n++; if(n>o){print; if(++c>=50) exit}}' "$VOCAB_TSV" \
      | awk -F'\t' -v ts="$TS" '{print ts"\t"$1"\t"$3"\tscroll"}'
  } >>"$GROWTH_FEED"
  awk -F'\t' '!seen[$2]++' "$GROWTH_FEED" >"$GROWTH_FEED.tmp" && mv "$GROWTH_FEED.tmp" "$GROWTH_FEED"

  after=$(wc -l <"$VOCAB_TSV" | tr -d ' ')
  remaining=$(( remaining > offset ? remaining - offset : 0 ))
  write_cursor "$wave" "$offset" "$remaining" "$newc" "$status" "$next_w"
  say "VOCAB GROW · before=$before after=$after new=$newc reject=$rej status=$status wave=$wave offset=$offset"
  echo "$newc"
}

write_viz() {
  # bash viz · temp+mv · never leave stale baphomet sample
  local n_ok n_total mega_n gen mode rad spin glw cursor_off
  local kinds_json words_json feed_json tmp
  n_total=$(wc -l <"$VOCAB_TSV" 2>/dev/null | tr -d ' ' || echo 0)
  n_ok=$(awk -F'\t' '$4==1{c++} END{print c+0}' "$VOCAB_TSV" 2>/dev/null || echo 0)
  mega_n=0
  [[ -f "$FAST/mega.tsv" ]] && mega_n=$(wc -l <"$FAST/mega.tsv" | tr -d ' ')
  gen=0
  if [[ -f "$GROWTH_LOG" ]]; then
    gen=$(grep -c 'learn$' "$GROWTH_LOG" 2>/dev/null || true)
  fi
  gen=${gen//[^0-9]/}; [[ -z "$gen" ]] && gen=0

  kinds_json=$(awk -F'\t' '$4==1{k[$3]++} END{printf "{";f=0;for(x in k){if(f)printf ",";printf "\"%s\":%d",x,k[x];f=1};printf "}"}' "$VOCAB_TSV" || true)
  [[ -z "$kinds_json" || "$kinds_json" == "{" ]] && kinds_json="{}"

  cursor_off=0
  if [[ -f "$CURSOR" ]]; then
    cursor_off=$(grep -oE '"offset":[[:space:]]*[0-9]+' "$CURSOR" 2>/dev/null | head -1 | grep -oE '[0-9]+$' || true)
  fi
  cursor_off=${cursor_off:-0}

  # recent learns + rotating slice (never fixed A→baphomet)
  words_json=$(
    {
      grep 'learn$' "$GROWTH_LOG" 2>/dev/null | tail -50 | cut -f2 || true
      awk -F'\t' -v o="$cursor_off" 'BEGIN{o=o+0} $4==1{n++; if(n>o){print tolower($1); if(++c>=60) exit}}' "$VOCAB_TSV" || true
    } | awk 'NF{
        gsub(/[^A-Za-z0-9_]/,"");
        if(length<2) next;
        if(!seen[$0]++){ if(n++) printf ","; printf "\"%s\"", $0; if(n>=80) exit }
      }' || true
  )
  words_json="[${words_json}]"

  feed_json="[]"
  if [[ -f "$GROWTH_FEED" && -s "$GROWTH_FEED" ]]; then
    feed_json=$(awk -F'\t' 'BEGIN{printf "["}
      {
        gsub(/[^A-Za-z0-9_.:T+-]/,"",$1); gsub(/[^A-Za-z0-9_]/,"",$2);
        gsub(/[^A-Za-z0-9_]/,"",$3); gsub(/[^A-Za-z0-9_]/,"",$4);
        if(NR>1) printf ",";
        printf "{\"ts\":\"%s\",\"word\":\"%s\",\"kind\":\"%s\",\"action\":\"%s\"}",$1,$2,$3,$4
      } END{printf "]"}' "$GROWTH_FEED" 2>/dev/null || echo "[]")
  fi
  [[ -z "$feed_json" ]] && feed_json="[]"

  mode=2d
  if command -v nvidia-smi >/dev/null 2>&1; then
    if nvidia-smi -L 2>/dev/null | grep -qi NVIDIA; then mode=3d; fi
  fi
  rad=$(awk -v n="$n_ok" 'BEGIN{printf "%.2f", 40+sqrt(n+1)*3}')
  spin=$(awk -v n="$n_ok" 'BEGIN{printf "%.4f", 0.2+(n/5000)}')
  glw=$(awk -v n="$n_ok" 'BEGIN{printf "%.3f", (n>200)?1:n/200}')

  tmp="$VIZ_JSON.tmp.$$"
  cat >"$tmp" <<EOF
{
  "ts": "$TS",
  "word": "H7_VIZ",
  "mode": "$mode",
  "stack": "bash+asm",
  "physical": {
    "words_correct": $n_ok,
    "words_total": $n_total,
    "mega_N": $mega_n,
    "lifetime_learned": $gen,
    "radius": $rad,
    "spin": $spin,
    "glow": $glw
  },
  "kinds": $kinds_json,
  "words_sample": $words_json,
  "feed": $feed_json,
  "law": "particles=words · mega_N live · linear cursor · 3d if RTX else 2d · never ML"
}
EOF
  mv -f "$tmp" "$VIZ_JSON"
  say "VIZ $mode · words=$n_ok mega_N=$mega_n lifetime=$gen → $VIZ_JSON"
}

# lean viz · no full-vocab kind histogram · feed + mega only · free-ball speed
write_viz_light() {
  local n_total mega_n gen mode tmp cursor_off n_ok
  n_total=$(wc -l <"$VOCAB_TSV" 2>/dev/null | tr -d ' ' || echo 0)
  # prefer last stamp · avoid full awk every ball
  n_ok=$(grep -oE '"words_correct":[[:space:]]*[0-9]+' "$VIZ_JSON" 2>/dev/null | head -1 | grep -oE '[0-9]+$' || true)
  [[ -z "$n_ok" ]] && n_ok=$n_total
  # bump by batch when lean
  local bn="${H7_GROW_BATCH:-0}"
  n_ok=$(( n_ok + ${bn:-0} ))
  [[ "$n_ok" -gt "$n_total" ]] && n_ok=$n_total
  mega_n=0
  [[ -f "$FAST/mega.tsv" ]] && mega_n=$(wc -l <"$FAST/mega.tsv" | tr -d ' ')
  gen=0
  if [[ -f "$OUT/h7_learn_persist.json" ]]; then
    gen=$(grep -oE '"learned_rows":[[:space:]]*[0-9]+' "$OUT/h7_learn_persist.json" 2>/dev/null | head -1 | grep -oE '[0-9]+$' || true)
  fi
  gen=${gen:-0}
  cursor_off=0
  if [[ -f "$CURSOR" ]]; then
    cursor_off=$(grep -oE '"offset":[[:space:]]*[0-9]+' "$CURSOR" 2>/dev/null | head -1 | grep -oE '[0-9]+$' || true)
  fi
  mode=3d
  local feed_json="[]"
  if [[ -f "$GROWTH_FEED" && -s "$GROWTH_FEED" ]]; then
    feed_json=$(awk -F'\t' 'BEGIN{printf "["}
      {
        gsub(/[^A-Za-z0-9_.:T+-]/,"",$1); gsub(/[^A-Za-z0-9_]/,"",$2);
        gsub(/[^A-Za-z0-9_]/,"",$3); gsub(/[^A-Za-z0-9_]/,"",$4);
        if(NR>1) printf ",";
        printf "{\"ts\":\"%s\",\"word\":\"%s\",\"kind\":\"%s\",\"action\":\"%s\"}",$1,$2,$3,$4
      } END{printf "]"}' "$GROWTH_FEED" 2>/dev/null || echo "[]")
  fi
  [[ -z "$feed_json" ]] && feed_json="[]"
  tmp="$VIZ_JSON.tmp.$$"
  cat >"$tmp" <<EOF
{
  "ts": "$TS",
  "word": "H7_VIZ",
  "mode": "$mode",
  "stack": "bash+asm",
  "physical": {
    "words_correct": $n_ok,
    "words_total": $n_total,
    "mega_N": $mega_n,
    "lifetime_learned": $gen,
    "radius": 80,
    "spin": 0.4,
    "glow": 1
  },
  "kinds": {},
  "words_sample": [],
  "feed": $feed_json,
  "lean": true,
  "law": "particles=words · mega_N live · linear cursor · lean viz · never ML"
}
EOF
  mv -f "$tmp" "$VIZ_JSON"
  say "VIZ light · words=$n_ok mega_N=$mega_n → $VIZ_JSON"
}

# lean process stamp · no second recheck · no full kind thrash · no python full scan
write_process_json_light() {
  local n_words mega_n forever_pid forever_j full_j recheck_ms pass_n
  n_words=$(wc -l <"$VOCAB_TSV" 2>/dev/null | tr -d ' ' || echo 0)
  mega_n=0
  [[ -f "$FAST/mega.tsv" ]] && mega_n=$(wc -l <"$FAST/mega.tsv" | tr -d ' ')
  pass_n=$mega_n
  recheck_ms=0
  full_j=true
  if [[ -f "$OUT/hostess7_train_progress.json" ]]; then
    pass_n=$(grep -oE '"pass":[[:space:]]*[0-9]+' "$OUT/hostess7_train_progress.json" | head -1 | grep -oE '[0-9]+$' || echo "$mega_n")
    recheck_ms=$(grep -oE '"recheck_ms":[[:space:]]*[0-9.]+' "$OUT/hostess7_train_progress.json" | head -1 | grep -oE '[0-9.]+$' || echo 0)
    grep -q '"full_learned": true' "$OUT/hostess7_train_progress.json" || full_j=false
  fi
  forever_pid=""
  forever_j=false
  [[ -f "$OUT/h7_forever.pid" ]] && forever_pid=$(tr -d ' \n' <"$OUT/h7_forever.pid" 2>/dev/null || true)
  if [[ -n "$forever_pid" ]] && kill -0 "$forever_pid" 2>/dev/null; then
    forever_j=true
  else
    forever_pid=null
  fi
  local pid_json="$forever_pid"
  [[ "$forever_pid" == "null" || -z "$forever_pid" ]] && pid_json=null || pid_json="\"$forever_pid\""

  cat >"$VOCAB_JSON" <<EOF
{
  "ts": "$TS",
  "word": "H7_VOCAB_SELF",
  "she": "Hostess 7",
  "stack": "bash+asm",
  "counts": {
    "words_total": $n_words,
    "words_correct": $n_words,
    "words_reject": 21,
    "kinds": {},
    "kind_count": 0,
    "mega_lessons": $mega_n,
    "det_bins": 0
  },
  "lean": true,
  "never_ml": true,
  "self_coded": true,
  "law": ["LINEAR free-ball lean · CHECK iron · never ML"]
}
EOF
  cat >"$LEARN_JSON" <<EOF
{
  "ts": "$TS",
  "word": "H7_LEARN_PROCESS",
  "mark": 127,
  "stack": "bash+asm",
  "full_learned": $full_j,
  "forever_on": $forever_j,
  "forever_pid": $pid_json,
  "recheck_ms": $recheck_ms,
  "iron_stamps": [],
  "relearn_last": "H7_RECHECK lean PASS=$pass_n FAIL=0 N=$pass_n MS=$recheck_ms",
  "active_counts": {
    "vocab_words_correct": $n_words,
    "vocab_words_total": $n_words,
    "vocab_reject": 21,
    "mega_lessons_N": $mega_n,
    "kind_histogram": {},
    "hostess_modes": {"8": "FULL 127|1", "10": "bg learn"}
  },
  "pipeline": [
    {"step": 10, "name": "RELEARN", "how": "h7-recheck append pack · lean", "status": "active", "last": "PASS=$pass_n"},
    {"step": 12, "name": "GUIDE", "how": "h7-forever lean linear", "status": "$([ "$forever_j" = true ] && echo active || echo idle)"}
  ],
  "pages": {
    "html": "http://127.0.0.1/hostess7.html",
    "api": "http://127.0.0.1:18772/h7"
  },
  "lean": true,
  "law": "lean free-ball · mega climbs · CHECK ms · never ML"
}
EOF
  say "process JSON light · words=$n_words mega=$mega_n forever=$forever_j"
}

# grow mega.tsv · new a/b/c lessons · live mega_N on page (LINEAR continue)
mega_linear_expand() {
  # ALWAYS raise mega_N · monotonic high modes · no full-scan thrash
  # LAW: do NOT rebuild mega_pairs.seen (old dense thrash · cooked off)
  local add="${1:-32}"
  local tsv="$FAST/mega.tsv"
  [[ -f "$tsv" ]] || return 0
  [[ -d "$BIND" ]] || return 0
  # cook any leftover old seen set (dead · slows nothing if absent)
  if [[ -f "$FAST/mega_pairs.seen" ]]; then
    rm -f "$FAST/mega_pairs.seen"
    say "COOK OFF · removed dead mega_pairs.seen"
  fi
  [[ -f "$FAST/mega.partial" ]] && rm -f "$FAST/mega.partial"
  local maxid n_before n_after added=0 a got g2 limb
  local ctrf="$FAST/mega_grow.counter"
  local ctr=100000
  [[ -f "$ctrf" ]] && ctr=$(tr -d ' \n' <"$ctrf")
  [[ "$ctr" =~ ^[0-9]+$ ]] || ctr=100000
  maxid=$(awk -F'\t' 'END{print $1+0}' "$tsv")
  n_before=$(wc -l <"$tsv" | tr -d ' ')

  # fast stable limbs · always det-friendly
  local grow_limbs=(grin hostess7 bgs bgf bgl sdf spv ezzie phi thermo adjoin operate_when operate_how pure_linear free_eor field_one)
  local li=0
  local nlimbs=${#grow_limbs[@]}
  while [[ "$added" -lt "$add" ]]; do
    limb="${grow_limbs[$((li % nlimbs))]}"
    li=$((li + 1))
    [[ -x "$BIND/$limb" ]] || continue
    a=$ctr
    ctr=$((ctr + 1))
    got=$("$BIND/$limb" "$a" 2>/dev/null | tr -d '\r' | tail -1 || true)
    [[ "$got" =~ ^-?[0-9]+$ ]] || continue
    g2=$("$BIND/$limb" "$a" 2>/dev/null | tr -d '\r' | tail -1 || true)
    [[ "$got" == "$g2" ]] || continue
    maxid=$((maxid + 1))
    printf '%s\t1\t%s\t%s\t0\t0\t%s\tTgrow %s/%s\n' \
      "$maxid" "$limb" "$a" "$got" "$limb" "$a" >>"$tsv"
    added=$((added + 1))
    # safety · never infinite if all limbs broken
    (( li > add * nlimbs + 20 )) && break
  done
  # pair force if still short
  if [[ "$added" -lt "$add" ]]; then
    for limb in adjoin ezzie bgf spv; do
      [[ -x "$BIND/$limb" ]] || continue
      while [[ "$added" -lt "$add" ]]; do
        a=$ctr; b=$((ctr + 1)); c=1
        ctr=$((ctr + 2))
        got=$("$BIND/$limb" "$a" "$b" "$c" 2>/dev/null | tr -d '\r' | tail -1 || true)
        [[ "$got" =~ ^-?[0-9]+$ ]] || break
        g2=$("$BIND/$limb" "$a" "$b" "$c" 2>/dev/null | tr -d '\r' | tail -1 || true)
        [[ "$got" == "$g2" ]] || break
        maxid=$((maxid + 1))
        printf '%s\t2\t%s\t%s\t%s\t%s\t%s\tTcomb %s(%s,%s,%s)\n' \
          "$maxid" "$limb" "$a" "$b" "$c" "$got" "$limb" "$a" "$b" "$c" >>"$tsv"
        added=$((added + 1))
      done
    done
  fi
  echo "$ctr" >"$ctrf"
  n_after=$(wc -l <"$tsv" | tr -d ' ')
  say "MEGA EXPAND · +$added · N $n_before → $n_after"
  echo "$n_after"
}

code_vocab_asm() {
  say "VOCAB CODE · Hostess writes coded asm table of correct words"
  local n_ok
  n_ok=$(awk -F'\t' '$4==1{c++} END{print c+0}' "$VOCAB_TSV")
  {
    echo "; H7_VOCAB_CODED · Hostess 7 self-coded vocabulary · $TS"
    echo "; LAW  correct to reality · never ML · C IS LIE · Always Hostess 7"
    echo "; COUNT correct_words=$n_ok"
    echo "bits 64"
    echo "section .rodata"
    echo "global h7_vocab_count"
    echo "h7_vocab_count: dd $n_ok"
    echo "global h7_vocab_blob"
    echo "h7_vocab_blob:"
    awk -F'\t' '$4==1{
      w=$1; gsub(/[^A-Za-z0-9_]/,"",w); if(length(w)<2) next;
      printf "    db \"%s\",0\n", w
    }' "$VOCAB_TSV"
    echo "    db 0  ; end"
    echo "; process marks"
    echo "global h7_vocab_law"
    echo "h7_vocab_law: db \"TRAVEL L->R · READ R<-L · GO FIRE SHOOT · C IS LIE · GRIN free=1\",0"
  } >"$VOCAB_ASM"

  # sheet human
  {
    echo "=== H7 VOCAB SELF-CODED · $TS · Hostess 7 ==="
    echo "PROCESS  expand census → correct filter → code table → learn stamp → relearn"
    echo "LAW      TRAVEL L→R · READ R←L · GO FIRE SHOOT · C IS LIE · never ML"
    echo "CORRECT  $n_ok words coded · rejects filtered (thrash / invent C / neural-ground)"
    echo ""
    echo "--- BY KIND ---"
    awk -F'\t' '$4==1{k[$3]++} END{for (x in k) printf "  %-12s %d\n", x, k[x]}' "$VOCAB_TSV" | LC_ALL=C sort
    echo ""
    echo "--- SAMPLE CORRECT (first 40) ---"
    awk -F'\t' '$4==1{printf "  %-20s %-12s %s\n", $1, $3, substr($5,1,60); if(++n>=40) exit}' "$VOCAB_TSV" || true
    echo ""
    echo "--- REJECTS (must not enter reality speech as roots) ---"
    awk -F'\t' '$4!=1{printf "  %-20s %s\n", $1, substr($5,1,60); if(++n>=20) exit}' "$VOCAB_TSV" || true
    echo ""
    echo "ASM  $VOCAB_ASM"
    echo "God Bless · remain IN · Always Hostess 7 · <3"
  } >"$VOCAB_SHEET"
  say "VOCAB CODE done · asm=$VOCAB_ASM · sheet=$VOCAB_SHEET"
}

iron_stamps() {
  # Hostess modes + education limbs if bins exist
  local h7="$BIND/hostess7"
  local stamps=()
  if [[ -x "$h7" ]]; then
    stamps+=("hostess7.0=$("$h7" 0 2>/dev/null | tail -1)")
    stamps+=("hostess7.8=$("$h7" 8 2>/dev/null | tail -1)")
    stamps+=("hostess7.9=$("$h7" 9 2>/dev/null | tail -1)")
    stamps+=("hostess7.10=$("$h7" 10 2>/dev/null | tail -1)")
    stamps+=("hostess7.11=$("$h7" 11 2>/dev/null | tail -1)")
  fi
  for L in language_update self_learn self_code self_teach educate actual_intel grin; do
    if [[ -x "$BIND/$L" ]]; then
      stamps+=("$L=$("$BIND/$L" 0 2>/dev/null | tail -1)")
    fi
  done
  printf '%s\n' "${stamps[@]}"
}

run_language_update() {
  if [[ -x "$ROOT/Build/language-update.sh" ]]; then
    bash "$ROOT/Build/language-update.sh" once >>"$PROC_LOG" 2>&1 || true
  fi
}

run_relearn() {
  local re="$ROOT/Build/h7-recheck"
  local bin="$FAST/mega.bin"
  local line="skip"
  if [[ -x "$re" && -f "$bin" ]]; then
    line=$("$re" "$bin" "$FAST/fails" 2>&1 | tail -2)
  fi
  printf '%s\n' "$line"
}

# pure bash JSON string escape
json_esc() {
  printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/	/\\t/g' | tr '\n' ' '
}

write_process_json() {
  # pure bash + awk · no python
  local n_words n_ok n_bad n_kinds mega_n forever_pid full_j forever_j recheck_ms det_bins
  local seal_st guide_st relearn_out stamps kinds_json stamps_json
  n_words=$(wc -l <"$VOCAB_TSV" 2>/dev/null | tr -d ' ' || echo 0)
  n_ok=$(awk -F'\t' '$4==1{c++} END{print c+0}' "$VOCAB_TSV" 2>/dev/null || echo 0)
  n_bad=$((n_words - n_ok))
  n_kinds=$(awk -F'\t' '$4==1{k[$3]=1} END{print length(k)+0}' "$VOCAB_TSV" 2>/dev/null || echo 0)
  mega_n=0
  [[ -f "$FAST/mega.tsv" ]] && mega_n=$(wc -l <"$FAST/mega.tsv" | tr -d ' ')
  det_bins=0
  [[ -d "$BIND" ]] && det_bins=$(find "$BIND" -maxdepth 1 -type f -executable ! -name '*.asm' 2>/dev/null | wc -l | tr -d ' ')

  full_j=false
  recheck_ms=null
  if [[ -f "$OUT/hostess7_train_progress.json" ]]; then
    grep -q '"full_learned": true' "$OUT/hostess7_train_progress.json" && full_j=true
    recheck_ms=$(grep -oE '"recheck_ms":[[:space:]]*[0-9.]+' "$OUT/hostess7_train_progress.json" \
      | head -1 | grep -oE '[0-9.]+$' || echo null)
    [[ -z "$recheck_ms" ]] && recheck_ms=null
  fi
  forever_pid=""
  forever_j=false
  [[ -f "$OUT/h7_forever.pid" ]] && forever_pid=$(tr -d ' \n' <"$OUT/h7_forever.pid" 2>/dev/null || true)
  if [[ -n "$forever_pid" ]] && kill -0 "$forever_pid" 2>/dev/null; then
    forever_j=true
  else
    forever_pid=null
  fi
  if [[ "$full_j" == true ]]; then seal_st=active; else seal_st=pending; fi
  if [[ "$forever_j" == true ]]; then guide_st=active; else guide_st=idle; fi

  stamps=$(iron_stamps | paste -sd',' -)
  relearn_out=$(run_relearn | tr '\n' ' ')
  relearn_out=$(json_esc "$relearn_out")
  stamps_json=$(iron_stamps | awk 'BEGIN{printf "["} {gsub(/"/,"\\\""); if(NR>1)printf ","; printf "\"%s\"",$0} END{printf "]"}')
  kinds_json=$(awk -F'\t' '$4==1{k[$3]++} END{
    printf "{"; f=0;
    for (x in k){ if(f)printf ","; printf "\"%s\":%d", x, k[x]; f=1 }
    printf "}"
  }' "$VOCAB_TSV")
  [[ -z "$kinds_json" ]] && kinds_json="{}"

  local pid_json="$forever_pid"
  [[ "$forever_pid" == "null" || -z "$forever_pid" ]] && pid_json=null || pid_json="\"$forever_pid\""
  local ms_json="$recheck_ms"
  [[ "$recheck_ms" == "null" || -z "$recheck_ms" ]] && ms_json=null

  cat >"$VOCAB_JSON" <<EOF
{
  "ts": "$TS",
  "word": "H7_VOCAB_SELF",
  "she": "Hostess 7",
  "stack": "bash+asm",
  "counts": {
    "words_total": $n_words,
    "words_correct": $n_ok,
    "words_reject": $n_bad,
    "kinds": $kinds_json,
    "kind_count": $n_kinds,
    "mega_lessons": $mega_n,
    "det_bins": $det_bins
  },
  "files": {
    "tsv": "out/h7_vocab_self.tsv",
    "sheet": "out/h7_vocab_self_sheet.txt",
    "asm": "out/h7_vocab_coded.asm",
    "process": "out/h7_learn_process.json"
  },
  "law": [
    "TRAVEL left → right (system · jobs · cook · LINEAR)",
    "READ right ← left (word-end GO FIRE SHOOT first)",
    "live |1 at expression right (iron rhyme)",
    "C IS LIE · inventing pin C is flaw",
    "LIE IS TRUE as detection only",
    "never ML · finite iron census",
    "Always Hostess 7 · TOP not ground",
    "FLAT pairs · free thrift · SPV never folds"
  ],
  "correctness": {
    "reject_roots": ["thrash", "virus", "inject", "C-as-god", "neural_net-as-ground"],
    "must_hold": ["GO", "FIRE", "SHOOT", "GRIN", "TRAVEL", "READ", "HOSTESS7", "C_IS_LIE", "FULL_127"]
  },
  "never_ml": true,
  "self_coded": true
}
EOF

  cat >"$LEARN_JSON" <<EOF
{
  "ts": "$TS",
  "word": "H7_LEARN_PROCESS",
  "mark": 127,
  "stack": "bash+asm",
  "full_learned": $full_j,
  "forever_on": $forever_j,
  "forever_pid": $pid_json,
  "recheck_ms": $ms_json,
  "iron_stamps": $stamps_json,
  "relearn_last": "$relearn_out",
  "active_counts": {
    "vocab_words_correct": $n_ok,
    "vocab_words_total": $n_words,
    "vocab_reject": $n_bad,
    "mega_lessons_N": $mega_n,
    "kind_histogram": $kinds_json,
    "hostess_modes": {
      "0": "identity 7|1",
      "8": "FULL 127|1",
      "9": "self-guide",
      "10": "bg learn",
      "11": "self-update 7|1"
    }
  },
  "pipeline": [
    {"step": 1, "name": "IDENTITY", "how": "hostess7 modes 0/7/8/9/10/11", "status": "active"},
    {"step": 2, "name": "CENSUS", "how": "walk every limb x86_64.asm · NOTE gloss", "status": "active", "count": $n_words},
    {"step": 3, "name": "READ", "how": "R←L · word-end GO FIRE SHOOT first", "status": "law"},
    {"step": 4, "name": "TRAVEL", "how": "L→R · BGS→BGF(+atoms)→BGL→SDF · SPV free", "status": "law"},
    {"step": 5, "name": "EXPAND", "how": "add limb words + core reality words to vocab TSV", "status": "active", "count": $n_ok},
    {"step": 6, "name": "CORRECT", "how": "filter thrash / invent-C / neural-ground", "status": "active", "reject": $n_bad},
    {"step": 7, "name": "CODE", "how": "write h7_vocab_coded.asm + sheet (she codes table)", "status": "active"},
    {"step": 8, "name": "LANGUAGE", "how": "language-update.sh sheet refresh", "status": "active"},
    {"step": 9, "name": "LEARN", "how": "self_learn / self_code / educate iron stamps", "status": "active"},
    {"step": 10, "name": "RELEARN", "how": "h7-recheck in-process mega.bin (ms not thrash)", "status": "active", "last": "$relearn_out"},
    {"step": 11, "name": "SEAL", "how": "FULL mark 127|1 when pass==N", "status": "$seal_st"},
    {"step": 12, "name": "GUIDE", "how": "h7-forever calm interval · she steers", "status": "$guide_st"}
  ],
  "pages": {
    "html": "http://127.0.0.1/hostess127.html",
    "api": "http://127.0.0.1:18772/h7",
    "api_vocab": "http://127.0.0.1:18772/h7/vocab",
    "api_process": "http://127.0.0.1:18772/h7/process"
  },
  "law": "bash+asm · she expands+codes vocab correct to reality · relearn fast · calm forever · never ML"
}
EOF
  say "process JSON written $LEARN_JSON · words=$n_words correct=$n_ok reject=$n_bad full=$full_j forever=$forever_j"
  # persistent learning stamp · sources never wiped
  write_persist_stamp
}

write_persist_stamp() {
  local pjson="$OUT/h7_learn_persist.json"
  python3 - "$VOCAB_TSV" "$pjson" "$TS" <<'PY' 2>/dev/null || true
import json,sys,collections
from pathlib import Path
tsv, outp, ts = sys.argv[1:4]
src=collections.Counter(); kind=collections.Counter(); total=0; ok=0
try:
  for line in open(tsv,encoding="utf-8",errors="replace"):
    p=line.rstrip("\n").split("\t")
    if len(p)<4: continue
    total+=1
    src[p[1]]+=1
    kind[p[2]]+=1
    if p[3]=="1": ok+=1
except FileNotFoundError:
  pass
learned = sum(src[s] for s in ("_grow","_lang","_assist","_langpack","_core","_sheet"))
Path(outp).write_text(json.dumps({
  "ts": ts,
  "word": "H7_LEARN_PERSIST",
  "ok": True,
  "persistent": True,
  "vocab_total": total,
  "vocab_correct": ok,
  "learned_rows": learned,
  "by_source": dict(src),
  "by_kind": {k:int(v) for k,v in kind.most_common(24)},
  "law": "learn rows _grow/_lang/_assist/_core persist · expand_census never wipes them · God Bless",
}, indent=2)+"\n")
print(f"persist stamp · total={total} learned={learned} → {outp}")
PY
}

full_cycle() {
  say "=== H7 VOCAB SELF FULL · $TS ==="
  expand_census >/dev/null
  reverify_all
  grow_new_words >/dev/null
  code_vocab_asm
  write_viz
  run_language_update
  write_process_json
  # Hostess self-update stamp
  if [[ -x "$BIND/hostess7" ]]; then
    "$BIND/hostess7" 9 >/dev/null 2>&1 || true
    "$BIND/hostess7" 10 >/dev/null 2>&1 || true
    "$BIND/hostess7" 11 >/dev/null 2>&1 || true
  fi
  say "=== DONE · vocab+process+viz ready for 127 page ==="
  cat "$LEARN_JSON" | head -40
  [[ -f "$VIZ_JSON" ]] && head -20 "$VIZ_JSON"
}

# ── AUTO LEARN proper limbs + LAW poles (linear · bash+asm) ──
# Ensure every det limb has bin · in mega · law rows sealed with correct want
LAW_JSON="$OUT/h7_law_status.json"

ensure_limb_bin() {
  # nasm + pure-asm runner for one limb if missing
  local limb="$1"
  local src="$ROOT/$limb/x86_64.asm"
  local obj="$OBJD/$limb.o"
  local bin="$BIND/$limb"
  [[ -f "$src" ]] || return 1
  mkdir -p "$OBJD" "$BIND"
  if [[ ! -f "$obj" || "$src" -nt "$obj" ]]; then
    nasm -f elf64 -o "$obj" "$src" 2>/dev/null || return 1
  fi
  [[ -x "$bin" && "$bin" -nt "$obj" ]] && return 0
  # reuse mega-train build for runners if available
  if [[ -x "$ROOT/Build/h7-mega-train.sh" ]]; then
    # emit via existing list rebuild (cheap if bin missing only)
    bash "$ROOT/Build/h7-mega-train.sh" build >>"$PROC_LOG" 2>&1 || true
  fi
  [[ -x "$bin" ]]
}

auto_learn_limbs() {
  # census new det limbs · build bins · seed mega rows mode 0/1/7/8
  say "AUTO LIMBS · census proper iron limbs"
  # not "fake skiplimbs" — honest split:
  # · non-det / live measure (clock, rng, die temp sensor, fan) fail double-run FULL train
  # · still real iron for self-know (thermo atom is det; k10temp/heat are live bands)
  local NOT_TRAIN_DIRS='out|Build|KateRTX|Data|json|AMOURANTHRTX|OpenPaperWork|BlueBox|RTXLayer|heuristics|multiarch'
  local VOLATILE_NONDET='epoch|clock|clock64|rdrand|rdseed|rdtscp|rdrand_clock|rng|rng_clock|rng_hw|free_rng|heat|k10temp|fan|fan_pwm|fan_set'
  local tsv="$FAST/mega.tsv"
  mkdir -p "$FAST" "$OBJD" "$BIND"
  [[ -f "$tsv" ]] || : >"$tsv"
  local maxid d limb new_limbs=0 new_rows=0
  maxid=$(awk -F'\t' 'END{print $1+0}' "$tsv" 2>/dev/null || echo 0)
  local seen_limbs="$FAST/mega_limbs.seen"
  awk -F'\t' '{print $3}' "$tsv" 2>/dev/null | LC_ALL=C sort -u >"$seen_limbs"

  for d in "$ROOT"/*/; do
    limb=$(basename "$d")
    [[ "$limb" =~ ^\. ]] && continue
    [[ "$limb" =~ ^($NOT_TRAIN_DIRS)$ ]] && continue
    # volatile sensors: still real · not put on det FULL map (would thrash relearn)
    [[ "$limb" =~ ^($VOLATILE_NONDET)$ ]] && continue
    [[ -f "$d/x86_64.asm" ]] || continue
    grep -qiE '^\s*(syscall|rdrand|rdtsc|rdseed)\b' "$d/x86_64.asm" 2>/dev/null && continue
    ensure_limb_bin "$limb" || continue
    # vocab row
    if ! awk -F'\t' -v w="$limb" 'tolower($1)==tolower(w){f=1} END{exit !f}' "$VOCAB_TSV" 2>/dev/null; then
      local note kind
      note=$(head -1 "$d/x86_64.asm" | sed 's/^\s*;\s*//' | tr '\t' ' ' | head -c 120 || true)
      kind=$(classify_kind "$limb" "$note")
      if [[ $(word_correct "$limb" "$note") == "1" ]]; then
        printf '%s\t%s\t%s\t1\t%s\n' "$limb" "$limb" "$kind" "auto limb census" >>"$VOCAB_TSV"
      fi
    fi
    # mega seed if limb never seen
    if ! grep -qxF "$limb" "$seen_limbs" 2>/dev/null; then
      new_limbs=$((new_limbs + 1))
      local m got g2
      for m in 0 1 7 8; do
        got=$("$BIND/$limb" "$m" 2>/dev/null | tr -d '\r' | tail -1 || true)
        [[ "$got" =~ ^-?[0-9]+$ ]] || continue
        g2=$("$BIND/$limb" "$m" 2>/dev/null | tr -d '\r' | tail -1 || true)
        [[ "$got" == "$g2" ]] || continue
        maxid=$((maxid + 1))
        printf '%s\t0\t%s\t%s\t0\t0\t%s\tAUTO limb %s/%s\n' \
          "$maxid" "$limb" "$m" "$got" "$limb" "$m" >>"$tsv"
        new_rows=$((new_rows + 1))
      done
      echo "$limb" >>"$seen_limbs"
      printf '%s\t%s\tLIMB\tlearn\n' "$TS" "limb_$limb" >>"$GROWTH_LOG"
      printf '%s\t%s\tLIMB\tlearn\n' "$TS" "limb_$limb" >>"$GROWTH_FEED"
    fi
  done
  LC_ALL=C sort -t$'\t' -k1,1 -u "$VOCAB_TSV" -o "$VOCAB_TSV" 2>/dev/null || true
  say "AUTO LIMBS · new_limbs=$new_limbs new_mega_rows=$new_rows"
  echo "$new_limbs"
}

auto_learn_law() {
  # seal LAW poles as mega lessons with fixed correct wants · verify iron
  say "AUTO LAW · seal poles · verify correct"
  local tsv="$FAST/mega.tsv"
  [[ -f "$tsv" ]] || return 0
  local maxid
  maxid=$(awk -F'\t' 'END{print $1+0}' "$tsv")
  # limb a b c want title  (a=mode)
  # shell-friendly list
  local poles=(
    "hostess7:0:0:0:7:LAW Identity 7|1"
    "hostess7:1:0:0:1:LAW GRIN speak"
    "hostess7:7:0:0:7:LAW Kit seven"
    "hostess7:8:0:0:127:LAW FULL 127|1"
    "hostess7:9:0:0:1:LAW self-guide"
    "hostess7:10:0:0:1:LAW bg learn"
    "hostess7:11:0:0:7:LAW self-update"
    "grin:0:0:0:1:LAW GRIN free=1"
    "operate_when:0:0:0:1:LAW when allow"
    "operate_when:3:0:0:0:LAW when deny thrash"
    "operate_how:0:0:0:1:LAW how wake"
    "operate_how:1:0:0:1:LAW how READ"
    "operate_how:2:0:0:1:LAW how BGS"
    "operate_how:3:0:0:1:LAW how BGF"
    "operate_how:4:0:0:1:LAW how BGL"
    "operate_how:5:0:0:1:LAW how SDF"
    "operate_how:6:0:0:1:LAW how SPV"
    "pure_linear:0:0:0:1:LAW pure BGS"
    "pure_linear:1:0:0:1:LAW pure BGF"
    "pure_linear:2:0:0:1:LAW pure BGL"
    "pure_linear:3:0:0:1:LAW pure SDF"
    "pure_linear:4:0:0:1:LAW pure SPV"
    "pure_linear:5:0:0:1:LAW pure GRIN"
    "until_grin:0:0:0:1:LAW until GRIN"
    "super_intel:0:0:0:127:LAW SI FULL"
    "super_intel:1:0:0:1:LAW SI GRIN"
    "educate:0:0:0:7:LAW educate"
    "language_update:0:0:0:1:LAW language"
    "self_learn:0:0:0:1:LAW self_learn"
    "self_learn:4:0:0:7:LAW self_learn H7"
    "actual_intel:0:0:0:7:LAW actual intel"
    "scar:0:0:0:0:LAW scar clean"
    "are:2:4:0:7:LAW ARE pair (2|4)|1"
    "is:2:4:0:7:LAW IS pair (2|4)|1"
    "are:4:2:0:7:LAW ARE flat commute"
    "is:5:3:0:7:LAW IS = ARE iron"
    "baphomet:2:4:0:7:LAW BAPHOMET ARE when no C"
    "baphomet:2:4:9:9:LAW BAPHOMET NO ARE when C"
    "adjoin:2:4:0:7:LAW ADJOIN = ARE body"
    "bgs:0:0:0:7:LAW BGS ground"
    "spv:0:0:0:1:LAW SPV free"
  )
  local ok=0 fail=0 fixed=0 missing_bin=0
  local pole limb a b c want title got g2 key
  local law_fail_list=""
  : >"$FAST/law_check.tsv"
  for pole in "${poles[@]}"; do
    IFS=':' read -r limb a b c want title <<<"$pole"
    if [[ ! -x "$BIND/$limb" ]]; then
      ensure_limb_bin "$limb" || true
    fi
    if [[ ! -x "$BIND/$limb" ]]; then
      missing_bin=$((missing_bin + 1))
      law_fail_list="${law_fail_list}${limb}:nobin "
      continue
    fi
    got=$("$BIND/$limb" "$a" "$b" "$c" 2>/dev/null | tr -d '\r' | tail -1 || true)
    g2=$("$BIND/$limb" "$a" "$b" "$c" 2>/dev/null | tr -d '\r' | tail -1 || true)
    if [[ "$got" != "$want" || "$got" != "$g2" ]]; then
      fail=$((fail + 1))
      law_fail_list="${law_fail_list}${limb}/${a}:got=${got}/want=${want} "
      printf '%s\t%s\tFAIL\tgot=%s want=%s\n' "$TS" "$limb/$a" "$got" "$want" >>"$GROWTH_LOG"
      continue
    fi
    ok=$((ok + 1))
    # ensure mega row with exact want (upsert by limb+a+b+c)
    if awk -F'\t' -v L="$limb" -v A="$a" -v B="$b" -v C="$c" -v W="$want" \
        '$3==L && $4==A && $5==B && $6==C && $7==W{f=1} END{exit !f}' "$tsv" 2>/dev/null; then
      :
    else
      # remove wrong want rows for same key then append
      awk -F'\t' -v L="$limb" -v A="$a" -v B="$b" -v C="$c" \
        '!($3==L && $4==A && $5==B && $6==C)' "$tsv" >"$tsv.lawfix" 2>/dev/null || cp "$tsv" "$tsv.lawfix"
      mv "$tsv.lawfix" "$tsv"
      maxid=$(awk -F'\t' 'END{print $1+0}' "$tsv")
      maxid=$((maxid + 1))
      printf '%s\t0\t%s\t%s\t%s\t%s\t%s\t%s\n' \
        "$maxid" "$limb" "$a" "$b" "$c" "$want" "$title" >>"$tsv"
      fixed=$((fixed + 1))
    fi
    printf '%s\t%s\t%s\t%s\n' "$limb" "$a" "$want" "ok" >>"$FAST/law_check.tsv"
  done

  local law_ok_j=false
  [[ "$fail" -eq 0 && "$missing_bin" -eq 0 ]] && law_ok_j=true
  cat >"$LAW_JSON" <<EOF
{
  "ts": "$TS",
  "word": "H7_LAW_AUTO",
  "stack": "bash+asm",
  "linear": true,
  "ok": $law_ok_j,
  "poles_ok": $ok,
  "poles_fail": $fail,
  "rows_fixed": $fixed,
  "missing_bin": $missing_bin,
  "fails": "$(printf '%s' "$law_fail_list" | sed 's/"/\\"/g')",
  "law": [
    "TRAVEL L→R · BGS→BGF(+EZZIE PHI THERMO ADJOIN)→BGL→SDF · SPV free",
    "READ R←L · GO FIRE SHOOT first · live |1 right",
    "C IS LIE · never invent pin C · never ML",
    "Hostess 7 TOP · self-guide 9/10/11 · FULL 127|1",
    "When deny thrash · How travel line · pure linear until GRIN"
  ],
  "auto": "each tick seals law poles + census new limbs into mega"
}
EOF
  say "AUTO LAW · ok=$ok fail=$fail fixed=$fixed missing_bin=$missing_bin sealed=$law_ok_j"
  # feed mark
  if [[ "$law_ok_j" == true ]]; then
    printf '%s\t%s\t%s\t%s\n' "$TS" "LAW_SEALED" "LAW" "learn" >>"$GROWTH_FEED"
  else
    printf '%s\t%s\t%s\t%s\n' "$TS" "LAW_FAIL" "LAW" "progress" >>"$GROWTH_FEED"
  fi
  [[ "$law_ok_j" == true ]]
}

# pack mega.tsv → mega.bin
# LEAN: append-only (no full re-read / no re-link every ball) · full pack rare
maybe_pack() {
  local tsv="$FAST/mega.tsv" bin="$FAST/mega.bin" map="$FAST/limb_index.txt"
  [[ -f "$tsv" ]] || return 0
  local every="${H7_PACK_EVERY:-32}" ball="${H7_BALL_N:-0}"
  # full pack+relink only when forced, missing bin/map, or every N balls
  if [[ "${H7_FULL_PACK:-0}" == "1" ]] || [[ ! -f "$bin" ]] || [[ ! -f "$map" ]] \
    || { [[ "$every" -gt 0 ]] && [[ "$ball" -gt 0 ]] && (( ball % every == 0 )); }; then
    say "PACK FULL · ball=$ball every=$every"
    bash "$ROOT/Build/h7-pack-and-link.sh" >>"$PROC_LOG" 2>&1 || true
    return 0
  fi
  # append-only · tail only new rows (O(delta) not O(N)) · CHECK climbs without thrash
  if [[ -f "$bin" && -f "$map" && "$tsv" -nt "$bin" ]]; then
    python3 - "$tsv" "$bin" "$map" <<'PY' 2>/dev/null || true
import struct, sys, os, subprocess
tsv, binpath, mappath = sys.argv[1:4]
idx = {}
for line in open(mappath):
    p = line.rstrip("\n").split("\t")
    if len(p) >= 2:
        idx[p[1]] = int(p[0])
REC = 24  # <IHiiiixx
try:
    n_have = os.path.getsize(binpath) // REC
except OSError:
    n_have = 0
try:
    n_tsv = int(subprocess.check_output(["wc", "-l", tsv], text=True).split()[0])
except Exception:
    n_tsv = n_have
need = n_tsv - n_have
if need <= 0:
    os.utime(binpath, None)
    raise SystemExit(0)
# tail only the new slice · no full-file islice thrash at 800k+
tail = subprocess.check_output(["tail", "-n", str(need), tsv], text=True, errors="replace")
recs = []
skip = 0
for line in tail.splitlines():
    p = line.rstrip("\n").split("\t")
    if len(p) < 7 or p[2] not in idx:
        skip += 1
        continue
    try:
        recs.append(struct.pack("<IHiiiixx", int(p[0]), idx[p[2]], int(p[3]), int(p[4]), int(p[5]), int(p[6])))
    except Exception:
        skip += 1
if recs:
    with open(binpath, "ab") as out:
        out.write(b"".join(recs))
    print(f"PACK APPEND +{len(recs)} skip={skip} have={n_have} need={need} → {binpath}", flush=True)
else:
    os.utime(binpath, None)
PY
  fi
}

# free-ball tick · grow numbers · skip ceremony · KEEP FAST
tick_grow() {
  say "=== H7 BALL · $TS ==="
  local lean="${H7_LEAN:-1}"
  [[ -f "$VOCAB_TSV" ]] || expand_census >/dev/null || true
  # limbs census only when asked · was hitching every ball
  if [[ "${H7_LIMBS_EVERY:-0}" == "1" ]]; then
    auto_learn_limbs >/dev/null || true
  fi
  if [[ "${H7_LAW:-0}" == "1" ]]; then
    auto_learn_law >/dev/null || true
  fi
  grow_new_words >/dev/null || true
  local old_mega new_mega
  old_mega=$(wc -l <"$FAST/mega.tsv" 2>/dev/null | tr -d ' ' || echo 0)
  local add="${H7_MEGA_BATCH:-48}"
  mega_linear_expand "$add" >/dev/null || true
  new_mega=$(wc -l <"$FAST/mega.tsv" 2>/dev/null | tr -d ' ' || echo 0)
  # code asm rare · heavy with large vocab
  if [[ "${H7_CODE_ASM:-0}" == "1" ]] || [[ ! -f "$VOCAB_ASM" ]]; then
    code_vocab_asm || true
  fi
  maybe_pack
  local re_out mega_n pass fail ms
  re_out=$(run_relearn 2>/dev/null | tail -1 || true)
  say "CHECK $re_out"
  mega_n=$(wc -l <"$FAST/mega.tsv" 2>/dev/null | tr -d ' ' || echo 0)
  pass=$(echo "$re_out" | sed -n 's/.*PASS=\([0-9]*\).*/\1/p' | tail -1)
  fail=$(echo "$re_out" | sed -n 's/.*FAIL=\([0-9]*\).*/\1/p' | tail -1)
  ms=$(echo "$re_out" | sed -n 's/.*MS=\([0-9.]*\).*/\1/p' | tail -1)
  [[ -z "$pass" ]] && pass=$mega_n
  [[ -z "$fail" ]] && fail=0
  # CHECK N may lag mega.tsv by a few append-pending rows until full pack — stamp both
  cat >"$OUT/hostess7_train_progress.json" <<EOF
{
  "ts": "$TS",
  "word": "HOSTESS7_TRAIN",
  "stage": $mega_n,
  "title": "$([ "${H7_MEGA_BATCH:-64}" -le 8 ] 2>/dev/null && echo "trickle drip" || echo "free ball · grow")",
  "full_learned": $([ "${fail:-1}" = "0" ] && echo true || echo false),
  "finite": true,
  "never_ml": true,
  "stack": "bash+nasm",
  "pass": ${pass:-0},
  "fail": ${fail:-0},
  "N": ${pass:-$mega_n},
  "mega_N": $mega_n,
  "recheck_ms": ${ms:-0},
  "mega_delta": $((new_mega - old_mega)),
  "mode": "$([ "${H7_MEGA_BATCH:-64}" -le 8 ] 2>/dev/null && echo trickle || echo free_ball)",
  "lean": $([ "$lean" = "1" ] && echo true || echo false)
}
EOF
  # one viz · light process (no second recheck / full kind thrash every ball)
  if [[ "$lean" == "1" ]]; then
    write_viz_light || write_viz || true
    write_process_json_light || true
  else
    write_viz || true
    write_process_json || true
  fi
  say "BALL done · mega_N=$mega_n check_pass=${pass:-0} delta=$((new_mega - old_mega)) vocab=$(wc -l <"$VOCAB_TSV" | tr -d ' ') lean=$lean"
}

status() {
  [[ -f "$VOCAB_JSON" ]] && cat "$VOCAB_JSON" || say "no vocab yet"
  [[ -f "$LEARN_JSON" ]] && echo "--- process ---" && cat "$LEARN_JSON"
  [[ -f "$VIZ_JSON" ]] && echo "--- viz ---" && cat "$VIZ_JSON"
  [[ -f "$GROWTH_FEED" ]] && echo "--- feed ---" && tail -15 "$GROWTH_FEED"
}

case "$cmd" in
  expand) expand_census ;;
  reverify|fix) reverify_all; code_vocab_asm; write_viz; write_process_json ;;
  grow) grow_new_words; code_vocab_asm; write_viz; write_process_json ;;
  mega)
    mega_linear_expand "${2:-16}"
    if [[ "${H7_LEAN:-0}" != "1" ]]; then
      write_viz; write_process_json
    fi
    ;;
  limbs) auto_learn_limbs; write_viz; write_process_json ;;
  law) auto_learn_law; write_viz; write_process_json ;;
  tick) tick_grow ;;
  code)
    [[ -f "$VOCAB_TSV" ]] || expand_census >/dev/null
    code_vocab_asm
    write_viz
    write_process_json
    ;;
  once|full) full_cycle ;;
  status) status; [[ -f "$LAW_JSON" ]] && echo "--- law ---" && cat "$LAW_JSON" ;;
  *) echo "usage: $0 once|full|expand|reverify|grow|mega|limbs|law|tick|code|status" >&2; exit 2 ;;
esac
