From 07090593ce7c478324a461e3327d2416588b50f9 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 13 Aug 2026 15:13:08 -0500 Subject: [PATCH 1/5] Centre the indicators against their letters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dots sat on the band's first row, so every row read as a line of lights with four empty rows hanging beneath it — the indicators looked detached from the letter they belong to, and the band looked mostly empty. Run dots now sit on rows 2 and 4 of the letter's five (still a blank row apart, so they cannot fuse), and a service row's single dot sits on row 3, the middle. Both are optically tied to the glyph beside them. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015iuQqpxEg9gqZ3gQ5nUjyR --- extras/galactic-unicorn/README.md | 42 ++++++++++++++++++------------- extras/galactic-unicorn/main.py | 10 ++++++-- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/extras/galactic-unicorn/README.md b/extras/galactic-unicorn/README.md index 73bf1c9..92bd6e8 100644 --- a/extras/galactic-unicorn/README.md +++ b/extras/galactic-unicorn/README.md @@ -11,21 +11,26 @@ floats, nothing cycles. ``` +-----------+ - 0 |# # GGGGG | V verify runs 1-5 (oldest first) - 2 |# # GGGGG | runs 6-10 (newest last) + 0 |# # | V verify + 1 |# # GGGGG | runs 1-5 (oldest first) + 2 |# # | + 3 |# # GGGGG | runs 6-10 (newest last) 4 | # | - 5 | b##G# ## | P persist letter on the far edge … - 7 | G#b#G ## | … dots on the near one - 10 |### GGRGG | E edge a failure in the older five - 15 | GGGGG ##| S server - 17 | GGGGY # | newest run in progress - 20 | # GGG.. | A agent a young repo: only three runs + 5 | ## | P persist letter on the far edge … + 6 | b##G# # #| … dots on the near one + 8 | G#b#G # | + 10 |### | E edge + 11 |# GGRGG | a failure in the older five + 15 | ##| S server + 18 | GGGGY #| newest run in progress + 20 | # | A agent + 21 |# # GGG.. | a young repo: only three runs 25 |Y Y Y Y Y Y| overall status - 26 | GG ## | B billing US EU - 31 |## YY | P proxy - 36 | GG ## | D database - 41 |# GG Y | L providers US EU | GLOBAL - 46 | GG G ###| I infra + 28 | GG ## | B billing US EU + 33 |## YY | P proxy + 38 | GG # #| D database + 43 |# GG Y | L providers US EU | GLOBAL + 48 | GG G # | I infra +-----------+ ``` @@ -41,9 +46,10 @@ west-to-east region order never flip. **Repos (rows 0–24)** — V/P/E/S/A, the substrate in dependency order: verify → persist → edge → server → agent. Each row carries that repo's ten most recent -GitHub Actions runs as **two rows of five dots** — the older five on the band's -first row, the newer five two rows below, with a blank row between so they -cannot fuse. +GitHub Actions runs as **two rows of five dots** — the older five, then the +newer five — on rows 2 and 4 of the letter's five, with a blank row between so +they cannot fuse. Centring them against the glyph keeps the pair optically tied +to its letter instead of floating above dead space. | Run | Colour | |---|---| @@ -58,8 +64,8 @@ cannot fuse. green, amber, or red (`partial_outage` and `major_outage` both read red). **Services (rows 26–50)** — B/P/D/L/I: billing, proxy, database, LLM providers, -infrastructure. One dot per region sorted **west → east** (US left of EU, like a -map), then a gap and one dot for GLOBAL — whatever belongs to no region. Adding +infrastructure. One dot per region, centred on the letter's middle row, sorted +**west → east** (US left of EU, like a map), then a gap and one dot for GLOBAL — whatever belongs to no region. Adding a region adds a dot, no code change. Each dot is the **worst** status among that block's components, so a single sick provider cannot hide behind healthy siblings. Green operational, amber degraded, red outage, dim blue unknown. diff --git a/extras/galactic-unicorn/main.py b/extras/galactic-unicorn/main.py index 8be67a5..8196cff 100644 --- a/extras/galactic-unicorn/main.py +++ b/extras/galactic-unicorn/main.py @@ -133,7 +133,13 @@ def vpixel(x, y, pen): RUNS = 10 RUNS_PER_ROW = 5 # 10 runs as 2 rows of 5 — what makes 10 rows fit -RUN_ROWS = (0, 2) # a blank row between them so they cannot fuse +# Rows 2 and 4 of the letter's 5, with a blank row between them so they cannot +# fuse. Sitting at the top of the band left the dots optically adrift from the +# letter, with dead space under them; this centres the pair against the glyph. +RUN_ROWS = (1, 3) +# A service row carries one dot, so centre it on the letter's middle row rather +# than parking it on top with four empty rows beneath. +HEALTH_DOT_ROW = LETTER_H // 2 # 2 # Bands alternate sides: even rows letter-left, odd rows letter-right. With # 5-row letters stacked flush there is no blank row between bands, so two @@ -531,7 +537,7 @@ def draw_health(): x = dot_x + b + (1 if b == last else 0) if x >= limit: continue - vpixel(x, y, PEN_UNKNOWN if blue + vpixel(x, y + HEALTH_DOT_ROW, PEN_UNKNOWN if blue else PENS.get(worst_status(cells), PEN_UNKNOWN)) From b818c49f28914a72213ca7c81449b8d61d1ed2d8 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 13 Aug 2026 15:57:23 -0500 Subject: [PATCH 2/5] Replace the dotted divider with a counting region header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The divider spent a row on a dotted line that restated the overall status, and the service dots sat beside their letters — so they zigzagged with the alternating letters and could not be read down a column. US was at x=5 on one row and x=1 on the next. Letters occupy either the first three columns or the last three, so columns 3-7 are the only ones never covered by a glyph. Service dots now live there at a fixed x per block, which makes them line up, and the three rows freed by dropping the divider carry a header for those columns: one column per block, its height counting the column (1, 2, 3) so there is no legend to memorise, lit in that block's worst-of rollup colour. That is strictly more than the divider said — per-region health instead of one aggregate — in the same space. Services shift down two rows, and the board is now exactly full: 25 rows of repos, 3 of header, 25 of services, 53 total with nothing spare. Known limit, deliberately not papered over: the counting header can express three columns in three rows. `region_cols` packs a fourth or fifth block into adjacent columns, but their heights would both cap at three and the count would start lying. Three blocks (us, eu, global) is what we have; scaling past that needs either position-only identity or a second dot row. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015iuQqpxEg9gqZ3gQ5nUjyR --- extras/galactic-unicorn/main.py | 70 +++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/extras/galactic-unicorn/main.py b/extras/galactic-unicorn/main.py index 8196cff..20e9611 100644 --- a/extras/galactic-unicorn/main.py +++ b/extras/galactic-unicorn/main.py @@ -152,8 +152,21 @@ def vpixel(x, y, pen): LETTER_X_RIGHT = VW - LETTER_W # 8 DOT_X_RIGHT = LETTER_X_RIGHT - DOT_GAP - RUNS_PER_ROW # 1 -DIVIDER_Y = CI_BANDS * BAND_H # 25 -HEALTH_Y0 = DIVIDER_Y + 1 # 26 +# The three rows between the two groups are the REGION HEADER: one column per +# block, lit in that block's own rollup colour, its height counting the column +# (1 = first, 2 = second, 3 = third) so you can tell which is which without a +# legend to memorise. It replaces the dotted overall-status divider, which cost +# a row and said less. +HEADER_Y0 = CI_BANDS * BAND_H # 25 +HEADER_H = 3 +HEALTH_Y0 = HEADER_Y0 + HEADER_H # 28 — the board is now exactly full + +# Letters alternate edges, so columns 3..7 are the only ones NEVER covered by a +# glyph. Region dots live there, at a fixed x per block, which is what lets them +# line up into readable columns under the header — beside-the-letter dots +# zigzagged with the letter and could not. +CENTRE_X0 = LETTER_W # 3 +CENTRE_COLS = VW - 2 * LETTER_W # 5 HEALTH_ROWS = 5 # billing, proxy, db, providers, infra # Initials for the service rows. `P` repeats (persist above, proxy here); the # divider and the differing dot layouts keep them apart. @@ -463,6 +476,17 @@ def pulse_pen(phase): return graphics.create_pen(int(210 * v), int(140 * v), 0) +def region_cols(n): + """x for each block. Spaced every other column while they fit (3, 5, 7) so + neighbouring dots cannot fuse; packed adjacent only if there are more blocks + than that allows.""" + if n <= 0: + return [] + if n <= (CENTRE_COLS + 1) // 2: + return [CENTRE_X0 + 2 * i for i in range(n)] + return [CENTRE_X0 + i for i in range(min(n, CENTRE_COLS))] + + def band_side(idx): """(letter_x, first_dot_x) for row `idx`, alternating edges.""" if idx % 2: @@ -508,35 +532,39 @@ def draw_centipedes(phase): top + RUN_ROWS[i // RUNS_PER_ROW], pen) -def draw_divider(): - """The separator carries the overall status — one glance, whole system.""" - pen = PEN_UNKNOWN if stale() else PENS.get(overall, PEN_UNKNOWN) - for x in range(0, VW, 2): - vpixel(x, DIVIDER_Y, pen) +def draw_region_header(): + """One column per block: `i + 1` dots rising from the header's bottom row, + in that block's worst-of colour.""" + blue = stale() + cols = region_cols(len(blocks)) + for b, x in enumerate(cols): + cells = [] + for row in range(HEALTH_ROWS): + cells.extend(grid.get((row, b), ())) + if blue or not cells: + pen = PEN_UNKNOWN if blue else PEN_EMPTY + else: + pen = PENS.get(worst_status(cells), PEN_UNKNOWN) + # Height counts the column; more blocks than header rows cannot be + # counted, so they are capped rather than drawn misleadingly. + for j in range(min(b + 1, HEADER_H)): + vpixel(x, HEADER_Y0 + HEADER_H - 1 - j, pen) def draw_health(): - """Five service rows: letter, one dot per region west to east, then a gap - and one dot for GLOBAL.""" + """Five service rows: a letter on the alternating edge, and one dot per + block in the fixed centre columns, aligned under the header.""" blue = stale() - last = len(blocks) - 1 + cols = region_cols(len(blocks)) for row in range(HEALTH_ROWS): y = HEALTH_Y0 + row * BAND_H - # Keep alternating across the divider: the service rows continue the - # repo rows' zigzag rather than restarting it. - letter_x, dot_x = band_side(CI_BANDS + row) + letter_x, _ = band_side(CI_BANDS + row) draw_letter(letter_x, y, HEALTH_LETTERS[row], PEN_UNKNOWN if blue else PEN_LETTER) - limit = VW if letter_x == 0 else letter_x - 1 - for b in range(len(blocks)): + for b, x in enumerate(cols): cells = grid.get((row, b)) if not cells: continue - # GLOBAL sits one column further along, so region and global never - # read as one run of dots. - x = dot_x + b + (1 if b == last else 0) - if x >= limit: - continue vpixel(x, y + HEALTH_DOT_ROW, PEN_UNKNOWN if blue else PENS.get(worst_status(cells), PEN_UNKNOWN)) @@ -545,7 +573,7 @@ def draw(phase): graphics.set_pen(PEN_BLACK) graphics.clear() draw_centipedes(phase) - draw_divider() + draw_region_header() draw_health() gu.update(graphics) From 06f249df00a41c3741f11948a667e566c4a4e1a7 Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 13 Aug 2026 16:04:25 -0500 Subject: [PATCH 3/5] Align the board README with what the renderer actually draws The header commit changed main.py and left the README describing the layout it replaced: a dotted divider on row 25, services on 26-50, dots on each band's first row beside the letter. The renderer uses 25-27 for the counting header, 28-52 for services, and puts the dots on the middle row in the fixed centre columns. Someone validating a freshly flashed board against this diagram would have concluded it was broken. Caught in review of b818c49. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015iuQqpxEg9gqZ3gQ5nUjyR --- extras/galactic-unicorn/README.md | 42 +++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/extras/galactic-unicorn/README.md b/extras/galactic-unicorn/README.md index 92bd6e8..9df8041 100644 --- a/extras/galactic-unicorn/README.md +++ b/extras/galactic-unicorn/README.md @@ -17,7 +17,7 @@ floats, nothing cycles. 3 |# # GGGGG | runs 6-10 (newest last) 4 | # | 5 | ## | P persist letter on the far edge … - 6 | b##G# # #| … dots on the near one + 6 | b##G# # #| … runs on the near one 8 | G#b#G # | 10 |### | E edge 11 |# GGRGG | a failure in the older five @@ -25,13 +25,20 @@ floats, nothing cycles. 18 | GGGGY #| newest run in progress 20 | # | A agent 21 |# # GGG.. | a young repo: only three runs - 25 |Y Y Y Y Y Y| overall status - 28 | GG ## | B billing US EU - 33 |## YY | P proxy - 38 | GG # #| D database - 43 |# GG Y | L providers US EU | GLOBAL - 48 | GG G # | I infra + 25 | Y | region header: column 3 = three dots + 26 | Y Y | column 2 = two dots + 27 | Y Y Y | column 1 = one dot + 28 | ## | B billing + 30 | G G ## | dots in the fixed centre columns … + 33 |## | P proxy + 35 |## Y Y | … so they line up under the header + 38 | ## | D database + 40 | G G # #| + 43 |# G G Y | L providers + 48 | G G G | I infra +-----------+ + + x= 3 5 7 = US, EU, GLOBAL ``` Every row is a 3×5 letter with its status as single-pixel dots beside it. Ten @@ -60,12 +67,21 @@ to its letter instead of floating above dead space. | cancelled / skipped | grey (deliberately *not* red — superseded PR pushes cancel runs constantly) | | no data yet | near-black | -**Divider (row 25)** — a dotted line carrying the aggregate `status`: -green, amber, or red (`partial_outage` and `major_outage` both read red). - -**Services (rows 26–50)** — B/P/D/L/I: billing, proxy, database, LLM providers, -infrastructure. One dot per region, centred on the letter's middle row, sorted -**west → east** (US left of EU, like a map), then a gap and one dot for GLOBAL — whatever belongs to no region. Adding +**Region header (rows 25–27)** — one column per block, its **height counting the +column**: one dot for the first, two for the second, three for the third, so +there is no legend to memorise. Each column is lit in that block's worst-of +rollup colour, so the header is also a per-region summary — strictly more than +the dotted divider it replaced, in the same space. + +Known limit: three header rows can count to three. A fourth or fifth block would +pack into adjacent columns but their heights would both cap at three and the +count would start lying. Three blocks (US, EU, GLOBAL) is what exists today. + +**Services (rows 28–52)** — B/P/D/L/I: billing, proxy, database, LLM providers, +infrastructure. One dot per block, centred on the letter's middle row, in the +**fixed centre columns 3/5/7** — the only columns never covered by a glyph, +which is what lets them line up under the header. Sorted **west → east** (US +left of EU, like a map), with GLOBAL one column further along — whatever belongs to no region. Adding a region adds a dot, no code change. Each dot is the **worst** status among that block's components, so a single sick provider cannot hide behind healthy siblings. Green operational, amber degraded, red outage, dim blue unknown. From 27f05b912109c12c196f14ef0a4d81adc271f09f Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 13 Aug 2026 16:20:23 -0500 Subject: [PATCH 4/5] One grid: run columns line up with the service columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Letters take either the first three columns or the last three, so 3-7 are the only five never covered by a glyph. Both halves now use exactly those: the run dots stop mirroring with their letter and sit at a fixed x, and the service blocks pack from the left of the same grid. A repo's five run columns line up with the five service columns beneath them, and the board reads as one channel of data with letters on the outside rather than two zigzagging sections. It also answers scaling: five columns hold five blocks, so a fourth region needs no layout change — and a block beyond the fifth is LOGGED rather than dropped, because a board that omits a region silently looks healthy by omission. (The counting header still only reaches three; that limit stands.) Packed blocks can share a colour with a neighbour and merge into a bar. That is fine horizontally — a run of green reads as "all green" and anything else breaks the run exactly where it is — unlike the vertical fusion that made the old health rows unreadable. Two review items fold in here: - The module docstring still described the divider and the old row ranges, in the file people copy to the device. It now matches the renderer. - The board treated any HTTP 200 as freshness, so a stalled node's last healthy snapshot would have rendered as current forever. It now honours the `stale` / `age_seconds` the service reports (CIRISStatus#46) and goes blue on the service's own word, not just on its ability to reach it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015iuQqpxEg9gqZ3gQ5nUjyR --- extras/galactic-unicorn/main.py | 94 +++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 39 deletions(-) diff --git a/extras/galactic-unicorn/main.py b/extras/galactic-unicorn/main.py index 20e9611..92cff6e 100644 --- a/extras/galactic-unicorn/main.py +++ b/extras/galactic-unicorn/main.py @@ -12,15 +12,21 @@ alternate edges — letter left, letter right, letter left — because with 5-row letters stacked flush two neighbours on the same edge touch and blur. +Letters alternate edges; the DOTS never move. Letters take either the first +three columns or the last three, so 3..7 are the only five never covered by a +glyph, and both halves of the board use exactly those: a repo's five run columns +line up with the five service columns beneath them. + vy 0-24 REPOS - V/P/E/S/A: verify, persist, edge, server, agent. The ten - most recent CI runs as two rows of five dots: older five on the - band's first row, newer five two rows below. - vy 25 divider, lit in the colour of the OVERALL status - vy 26-50 SERVICES - B/P/D/L/I: billing, proxy, database, LLM providers, - infrastructure. One dot per region, ordered west to east, then a - gap and one dot for GLOBAL (what belongs to no region). Each dot - is the WORST status among that block's components, so nothing - hides behind a healthy sibling. + most recent CI runs as two rows of five dots, on rows 2 and 4 of + the letter's five: older five above, newer five below. + vy 25-27 REGION HEADER - one column per block, its height counting the + column (1, 2, 3), lit in that block's rollup colour. + vy 28-52 SERVICES - B/P/D/L/I: billing, proxy, database, LLM providers, + infrastructure. One dot per block on the letter's middle row, + ordered west to east with GLOBAL last. Each dot is the WORST + status among that block's components, so nothing hides behind a + healthy sibling. The repeated P (persist, proxy) is never ambiguous: the divider separates the two groups, and their dot layouts differ. @@ -141,16 +147,17 @@ def vpixel(x, y, pen): # than parking it on top with four empty rows beneath. HEALTH_DOT_ROW = LETTER_H // 2 # 2 -# Bands alternate sides: even rows letter-left, odd rows letter-right. With -# 5-row letters stacked flush there is no blank row between bands, so two -# letters on the same edge touch and blur into each other; putting neighbours -# on opposite edges separates them horizontally instead. The whole band mirrors -# — letter and dots — but the dots themselves always read left to right, so run -# order and the west-to-east region order never flip. -DOT_GAP = 2 # columns between a letter and its dots -DOT_X_LEFT = LETTER_W + DOT_GAP # 5 -LETTER_X_RIGHT = VW - LETTER_W # 8 -DOT_X_RIGHT = LETTER_X_RIGHT - DOT_GAP - RUNS_PER_ROW # 1 +# LETTERS alternate edges: with 5-row letters stacked flush there is no blank +# row between bands, so two on the same edge touch and blur; putting neighbours +# on opposite edges separates them horizontally instead. +# +# DOTS do not alternate. Letters occupy either the first three columns or the +# last three, so 3..7 are the only five never covered by a glyph — and both +# sections use exactly those five. Run dots and service dots therefore sit on +# ONE grid running down the middle of the board: a repo's five run columns line +# up with the five service columns beneath them. It also means the service row +# can hold five blocks when a fourth region exists, in the same columns. +LETTER_X_RIGHT = VW - LETTER_W # 8 # The three rows between the two groups are the REGION HEADER: one column per # block, lit in that block's own rollup colour, its height counting the column @@ -255,6 +262,10 @@ def vpixel(x, y, pen): last_status_ok_ms = None last_ci_ok_ms = None +# The server tells us when ITS snapshot has outlived the poll window. Without +# this the board treats any HTTP 200 as freshness and would render a stalled +# node's last healthy snapshot as current, indefinitely. +server_stale = False def log(msg): @@ -268,8 +279,9 @@ def _stale(mark, window): def stale(): - """Health data no longer trustworthy.""" - return _stale(last_status_ok_ms, STALE_AFTER_MS) + """Health data no longer trustworthy — either we cannot reach the service, + or the service is telling us its own snapshot is stale.""" + return server_stale or _stale(last_status_ok_ms, STALE_AFTER_MS) def ci_stale(): @@ -319,6 +331,10 @@ def parse_status(data): }) blocks = [r['key'] for r in regions] + ['global'] + if len(blocks) > CENTRE_COLS: + # Never drop a block silently: the board would look healthy by omission. + log(" WARNING: %d blocks, only %d columns — %s not shown" + % (len(blocks), CENTRE_COLS, ",".join(blocks[CENTRE_COLS:]))) idx = {} for i, b in enumerate(blocks): idx[b] = i @@ -420,12 +436,16 @@ def fetch_json(url): def refresh_status(): - global last_status_ok_ms + global last_status_ok_ms, server_stale data = fetch_json(STATUS_URL) if data is None: return False parse_status(data) last_status_ok_ms = time.ticks_ms() + server_stale = bool(data.get('stale')) + if server_stale: + log(" server reports a STALE snapshot (age %ss) — showing unknown" + % data.get('age_seconds', '?')) bad = [] for (row, b), cells in grid.items(): for s in cells: @@ -477,21 +497,19 @@ def pulse_pen(phase): def region_cols(n): - """x for each block. Spaced every other column while they fit (3, 5, 7) so - neighbouring dots cannot fuse; packed adjacent only if there are more blocks - than that allows.""" - if n <= 0: - return [] - if n <= (CENTRE_COLS + 1) // 2: - return [CENTRE_X0 + 2 * i for i in range(n)] - return [CENTRE_X0 + i for i in range(min(n, CENTRE_COLS))] + """One column per block, packed from the left of the centre grid — the SAME + five columns a repo's runs use, so block N sits directly under run column N. + + Two same-coloured neighbours do merge into a bar, which is fine horizontally: + a run of green reads as "all green", and anything else breaks the run exactly + where it is. That is not the vertical fusion that made the old health rows + unreadable, where you could not tell which category you were looking at.""" + return [CENTRE_X0 + i for i in range(max(0, min(n, CENTRE_COLS)))] -def band_side(idx): - """(letter_x, first_dot_x) for row `idx`, alternating edges.""" - if idx % 2: - return LETTER_X_RIGHT, DOT_X_RIGHT - return 0, DOT_X_LEFT +def letter_x(idx): + """Which edge row `idx`'s letter sits on. Dots never move — see CENTRE_X0.""" + return LETTER_X_RIGHT if idx % 2 else 0 def worst_status(cells): @@ -511,13 +529,12 @@ def draw_centipedes(phase): for band in range(CI_BANDS): top = band * BAND_H - letter_x, dot_x = band_side(band) if band < len(centipedes): name, runs = centipedes[band] else: name, runs = '?', [] - draw_letter(letter_x, top, repo_letter(name), + draw_letter(letter_x(band), top, repo_letter(name), PEN_UNKNOWN if blue else PEN_LETTER) for i in range(RUNS): @@ -528,7 +545,7 @@ def draw_centipedes(phase): ) else: pen = PEN_EMPTY # a young repo draws a short centipede - vpixel(dot_x + i % RUNS_PER_ROW, + vpixel(CENTRE_X0 + i % RUNS_PER_ROW, top + RUN_ROWS[i // RUNS_PER_ROW], pen) @@ -558,8 +575,7 @@ def draw_health(): cols = region_cols(len(blocks)) for row in range(HEALTH_ROWS): y = HEALTH_Y0 + row * BAND_H - letter_x, _ = band_side(CI_BANDS + row) - draw_letter(letter_x, y, HEALTH_LETTERS[row], + draw_letter(letter_x(CI_BANDS + row), y, HEALTH_LETTERS[row], PEN_UNKNOWN if blue else PEN_LETTER) for b, x in enumerate(cols): cells = grid.get((row, b)) From 80c8c75a77401052b882dd6a3e5a361a4b9be47b Mon Sep 17 00:00:00 2001 From: Eric Moore Date: Thu, 13 Aug 2026 16:22:15 -0500 Subject: [PATCH 5/5] Back to spaced service columns, and generate the README diagram from the renderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Packing the blocks into consecutive columns lined them up cell-for-cell with the run columns, but it looked worse on the wall: three dots in a row read as a bar, and the spacing is what made them legible as separate regions. Spaced (3, 5, 7) while they fit, packed only when a fourth or fifth block leaves no choice. They still share the grid — a service dot sits under run column 1, 3 or 5 — so the alignment that motivated this stands; only the density changed. The README diagram is now GENERATED from the renderer's framebuffer rather than hand-drawn beside it. Twice now the picture has documented a layout the code no longer draws, once caught in review and once by me: my last attempt to fix it anchored on a substring that no longer existed, threw, and committed the code change with the stale diagram still in place. A diagram transcribed by hand from a thing that changes every hour will drift every hour. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015iuQqpxEg9gqZ3gQ5nUjyR --- extras/galactic-unicorn/README.md | 91 ++++++++++++++++++++++--------- extras/galactic-unicorn/main.py | 22 +++++--- 2 files changed, 80 insertions(+), 33 deletions(-) diff --git a/extras/galactic-unicorn/README.md b/extras/galactic-unicorn/README.md index 9df8041..b493a6b 100644 --- a/extras/galactic-unicorn/README.md +++ b/extras/galactic-unicorn/README.md @@ -12,44 +12,79 @@ floats, nothing cycles. ``` +-----------+ 0 |# # | V verify - 1 |# # GGGGG | runs 1-5 (oldest first) + 1 |# #GGGGG | runs 1-5 (oldest first) 2 |# # | - 3 |# # GGGGG | runs 6-10 (newest last) + 3 |# #GGGGG | runs 6-10 (newest last) 4 | # | - 5 | ## | P persist letter on the far edge … - 6 | b##G# # #| … runs on the near one - 8 | G#b#G # | + 5 | ## | P persist letter on the far edge … + 6 | b##G## #| … dots never move + 7 | ## | + 8 | G#b#G# | + 9 | # | 10 |### | E edge - 11 |# GGRGG | a failure in the older five + 11 |# GGRGG | a failure in the older five + 12 |## | + 13 |# GGGGG | + 14 |### | 15 | ##| S server - 18 | GGGGY #| newest run in progress + 16 | GGGGG# | + 17 | # | + 18 | GGGGY #| newest run in progress + 19 | ## | 20 | # | A agent - 21 |# # GGG.. | a young repo: only three runs + 21 |# #GGG.. | a young repo: only three runs + 22 |### | + 23 |# #..... | + 24 |# # | 25 | Y | region header: column 3 = three dots - 26 | Y Y | column 2 = two dots - 27 | Y Y Y | column 1 = one dot + 26 | Y Y | column 2 = two dots + 27 | Y Y Y | column 1 = one dot 28 | ## | B billing - 30 | G G ## | dots in the fixed centre columns … + 29 | # #| + 30 | G G ## | US EU, on the grid the runs use + 31 | # #| + 32 | ## | 33 |## | P proxy - 35 |## Y Y | … so they line up under the header + 34 |# # | + 35 |## Y Y | + 36 |# | + 37 |# | 38 | ## | D database + 39 | # #| 40 | G G # #| - 43 |# G G Y | L providers - 48 | G G G | I infra + 41 | # #| + 42 | ## | + 43 |# | L providers + 44 |# | + 45 |# G G Y | GLOBAL degraded behind two healthy regions + 46 |# | + 47 |### | + 48 | ###| I infra + 49 | # | + 50 | G G G # | + 51 | # | + 52 | ###| +-----------+ - x= 3 5 7 = US, EU, GLOBAL + x= 3 4 5 6 7 = the one grid both halves use ``` Every row is a 3×5 letter with its status as single-pixel dots beside it. Ten rows fill the board exactly. -Rows **alternate edges** — letter left, letter right, letter left. Ten 5-row -letters stacked flush leave no blank row between them, so two neighbours on the -same edge touch and blur into each other; putting them on opposite edges -separates them horizontally instead. The whole band mirrors, letter and dots -together, but the dots always read left to right, so run order and the -west-to-east region order never flip. +**Letters** alternate edges — left, right, left. Ten 5-row letters stacked flush +leave no blank row between them, so two neighbours on the same edge touch and +blur; opposite edges separate them horizontally instead. + +**Dots never move.** Letters take either the first three columns or the last +three, so 3–7 are the only five never covered by a glyph, and both halves use +exactly those. A repo's run columns and the service blocks sit on one grid +running down the middle of the board. Dots always read left to right, so run +order and the west→east region order never flip with the letter. + +Five columns also means a fourth region needs no layout change. A block beyond +the fifth is **logged, not dropped** — a board that omits a region silently +looks healthy by omission. **Repos (rows 0–24)** — V/P/E/S/A, the substrate in dependency order: verify → persist → edge → server → agent. Each row carries that repo's ten most recent @@ -78,10 +113,10 @@ pack into adjacent columns but their heights would both cap at three and the count would start lying. Three blocks (US, EU, GLOBAL) is what exists today. **Services (rows 28–52)** — B/P/D/L/I: billing, proxy, database, LLM providers, -infrastructure. One dot per block, centred on the letter's middle row, in the -**fixed centre columns 3/5/7** — the only columns never covered by a glyph, -which is what lets them line up under the header. Sorted **west → east** (US -left of EU, like a map), with GLOBAL one column further along — whatever belongs to no region. Adding +infrastructure. One dot per block, centred on the letter's middle row, on the +same centre grid the runs use — spaced every other column while they fit, which +reads better and keeps neighbours from merging. Sorted **west → east** (US left +of EU, like a map), with GLOBAL last — whatever belongs to no region. Adding a region adds a dot, no code change. Each dot is the **worst** status among that block's components, so a single sick provider cannot hide behind healthy siblings. Green operational, amber degraded, red outage, dim blue unknown. @@ -94,6 +129,12 @@ independently: no successful `/api/v1/status` for 90 s turns the health grid blue, and no successful `/api/v1/ci` for 3 minutes turns the centipedes blue, each without touching the other. +The service also reports staleness of its own: when its poll loop has not +produced a snapshot within the poll window it answers `stale: true` with +`age_seconds`, and the board goes blue on that alone. Otherwise an HTTP 200 +carrying a stalled node's last healthy snapshot would render as current +indefinitely — the board would be reporting freshness it does not have. + ## Orientation The default is a counter-clockwise rotation, which is correct when the panel diff --git a/extras/galactic-unicorn/main.py b/extras/galactic-unicorn/main.py index 92cff6e..9cfa609 100644 --- a/extras/galactic-unicorn/main.py +++ b/extras/galactic-unicorn/main.py @@ -497,14 +497,20 @@ def pulse_pen(phase): def region_cols(n): - """One column per block, packed from the left of the centre grid — the SAME - five columns a repo's runs use, so block N sits directly under run column N. - - Two same-coloured neighbours do merge into a bar, which is fine horizontally: - a run of green reads as "all green", and anything else breaks the run exactly - where it is. That is not the vertical fusion that made the old health rows - unreadable, where you could not tell which category you were looking at.""" - return [CENTRE_X0 + i for i in range(max(0, min(n, CENTRE_COLS)))] + """x for each block, on the same centre grid the runs use. + + Spaced every other column while they fit (3, 5, 7) — it reads better, and + two neighbours cannot merge into a bar. A fourth or fifth block packs + adjacent instead, since the grid is five wide; adjacent dots of one colour + do fuse, which is tolerable horizontally (a run of green reads as all-green + and anything else breaks it exactly where it is) but is why spacing wins + while there is room for it. + """ + if n <= 0: + return [] + if n <= (CENTRE_COLS + 1) // 2: + return [CENTRE_X0 + 2 * i for i in range(n)] + return [CENTRE_X0 + i for i in range(min(n, CENTRE_COLS))] def letter_x(idx):