[object Object]

← back to Crazy News Channel Shadowman

Fix Chaos Index reading ~51% on fresh load — exclude single-stage real-news from the escalation gauge

1ce86db7d6f15c3a0cff0b2da61b9bcb30f6d15a · 2026-09-24 09:13:03 -0700 · Steve Abrams

computeChaos() scored every story's stageIndex/(stages-1), with a `span>0 ? … : 1`
fallback that returned FULL chaos for a single-stage story. After the "one feed"
real-news merge, ~42 of 82 stories are single-stage (real news doesn't escalate),
so each was counted as maximally escalated — pinning a fresh channel at ~51%
"Concerning" and making the Nominal band unreachable on load.

Compute the gauge over escalatable (multi-stage) stories only. Verified: fresh
load 0% Nominal, climbs to 100% Post-Journalism at true meltdown, klaxon +10
bump preserved, 0 console/page errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkfgaotGs6PaEBcnKxnfZG

Files touched

Diff

commit 1ce86db7d6f15c3a0cff0b2da61b9bcb30f6d15a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 24 09:13:03 2026 -0700

    Fix Chaos Index reading ~51% on fresh load — exclude single-stage real-news from the escalation gauge
    
    computeChaos() scored every story's stageIndex/(stages-1), with a `span>0 ? … : 1`
    fallback that returned FULL chaos for a single-stage story. After the "one feed"
    real-news merge, ~42 of 82 stories are single-stage (real news doesn't escalate),
    so each was counted as maximally escalated — pinning a fresh channel at ~51%
    "Concerning" and making the Nominal band unreachable on load.
    
    Compute the gauge over escalatable (multi-stage) stories only. Verified: fresh
    load 0% Nominal, climbs to 100% Post-Journalism at true meltdown, klaxon +10
    bump preserved, 0 console/page errors.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01BkfgaotGs6PaEBcnKxnfZG
---
 index.html | 33 +++++++++++++++++++++++----------
 1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/index.html b/index.html
index f82c2d5..c526b9a 100644
--- a/index.html
+++ b/index.html
@@ -1589,6 +1589,14 @@ function patchStoryGrid(entries) {
 }
 
 function renderStories() {
+  // A rebuild detaches every card node, so a cached hover id is stale by
+  // definition — drop it or a hover that never gets a pointerout (e.g. the
+  // grid re-renders out from under a stationary pointer, such as on a
+  // category-change or article-close triggered without a mouse move) can
+  // leave isEscalationPaused() stuck true forever. If the pointer is still
+  // genuinely over a card, the next real pointerover re-sets this within a
+  // tick, so this costs nothing on the normal hover path.
+  state.hoveredCardId = null;
   // Top-level completion state: every story has been deleted (a state that
   // didn't exist before the admin panel could delete the seeded 3) — this
   // is distinct from the per-category empty state below.
@@ -1676,19 +1684,24 @@ const ANCHOR_NOTES = {
 // with only one stage can't have a ladder position, so it counts as fully
 // escalated (1) rather than causing a divide-by-zero.
 function computeChaos() {
-  if (!STORIES.length) return 0;
-  const total = STORIES.reduce((sum, s) => {
-    const span = s.stages.length - 1;
-    return sum + (span > 0 ? s.stageIndex / span : 1);
-  }, 0);
-  let pct = Math.round((total / STORIES.length) * 100);
+  // The gauge measures ESCALATION, so it's computed over escalatable
+  // (multi-stage) stories only. Single-stage real-news items (from the
+  // real-news merge) don't escalate — they load already at their only stage.
+  // The old code's `span > 0 ? … : 1` fallback scored each of them as FULLY
+  // escalated, which pinned a fresh channel at ~51% "Concerning" and made the
+  // Nominal band unreachable. Excluding them restores 0% on load while
+  // preserving 100% at true meltdown (all escalatable stories concluded).
+  const escalatable = STORIES.filter((s) => s.stages.length > 1);
+  if (!escalatable.length) return state.breaking ? 10 : 0;
+  const total = escalatable.reduce((sum, s) => sum + s.stageIndex / (s.stages.length - 1), 0);
+  let pct = Math.round((total / escalatable.length) * 100);
   // BREAKING klaxon integration: an active klaxon reads as its own bump on
   // the escalation gauge, independent of story stages.
   if (state.breaking) pct += 10;
-  // 100% is reserved for true meltdown (every story concluded), the same
-  // condition that fires PLEASE STAND BY, so the gauge never reads 100%
-  // while the standby card stays silent.
-  const allConcluded = STORIES.every((s) => s.stageIndex === s.stages.length - 1);
+  // 100% is reserved for true meltdown (every escalatable story concluded),
+  // the same condition that fires PLEASE STAND BY, so the gauge never reads
+  // 100% while the standby card stays silent.
+  const allConcluded = escalatable.every((s) => s.stageIndex === s.stages.length - 1);
   return Math.max(0, Math.min(allConcluded ? 100 : 99, pct));
 }
 

← 5643bfe One feed: invented satire + real Google News together  ·  back to Crazy News Channel Shadowman  ·  chaos-screenrecord: fix chaosFF-concluded's coverage gap on f58b710 →