Skip to content

Commit 56f6a81

Browse files
v0.38-C: Pulse Bar 2.0 — real status instrument
Per ChatGPT 2026-05-10: "make the top bar feel like a real status instrument, not a thin text strip." The bar now reads as identity + truthfulness state + workload signal at a glance, not as debug-console copy. Changes: - bar height 32 → ATOMIK_PULSE_BAR_H (40 px) for visual weight - ATOMiK wordmark scaled 1 → 2 (the visual anchor on the left) - NEW: MODE badge (DEV / DEMO / INVESTOR) wired through the v0.38 metric_mode(). Color-coded amber/cyan/green so the strictest gating reads as green-go and the loosest as amber-careful. 'O' key cycles modes (M was taken by Monitor); cycling immediately re-gates which sources can display. - NEW: event-pulse mini-waveform (32 samples × 150 ms = ~5 s window). Auto-normalized cyan sparkline driven by real atomik_event_total() deltas — same bus every other surface consumes. Status_tick() pushes one sample per cadence, render walks the ring chronologically with thin Bresenham lines. - Fabric and Replica Flow now compute their top edges as ATOMIK_SAFE_TOP + ATOMIK_PULSE_BAR_H + GRID, so the +8 px bar growth shifts them coherently. The screen now has a clear identity-and-status header before any workspace content — closer to the concept image's macro hierarchy without sacrificing truth. Every value in the bar still traces to a real producer (metric provider, event bus, /proc/stat, /proc/uptime). No fake numbers. Validated on AX7020 — see docs/screenshots/pulse_bar_2_*_v0.38-C.png. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0389981 commit 56f6a81

7 files changed

Lines changed: 182 additions & 10 deletions

File tree

5.93 MB
Loading
5.93 MB
Loading

atomik_os/include/atomik_os.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* carries a user-visible change. About window, status bar, and the
1010
* /tmp/atomik_os_version stamp all read from here so the screen output
1111
* NEVER lies about which build is running. */
12-
#define AOS_VERSION "v0.38-B"
12+
#define AOS_VERSION "v0.38-C"
1313

1414
/* Display geometry — locked to 1920x1080 XRGB8888 since simplefb is fixed. */
1515
#define FB_W 1920
@@ -34,6 +34,11 @@
3434
* margin above the bar so the bar's top doesn't sit right on the
3535
* crop boundary. */
3636
#define ATOMIK_SAFE_TOP 48
37+
/* v0.38-C: Pulse Bar grew from 32 → 40 px to give the brand (now scale-2)
38+
* + event-pulse waveform + DATA / MODE badges room to breathe. Surfaces
39+
* that anchor below the bar must read this constant — fabric_shelf_y and
40+
* replica_flow_open use it so the layout shifts coherently. */
41+
#define ATOMIK_PULSE_BAR_H 40
3742
#define ATOMIK_SAFE_BOT 16
3843
#define ATOMIK_SAFE_LEFT 16
3944
#define ATOMIK_SAFE_RIGHT 16
@@ -799,8 +804,11 @@ void notes_draw(window_t *w, int x, int y, int wd, int ht);
799804
void notify_post(const char *text);
800805
void notify_draw(void);
801806

802-
/* status.c — top status bar (clock, CPU, prediction surface) */
807+
/* status.c — top status bar (clock, CPU, prediction surface).
808+
* v0.38-C adds status_tick() which samples the event-bus delta into
809+
* the Pulse Bar's mini-waveform ring at PULSE_SAMPLE_MS cadence. */
803810
void status_draw(void);
811+
void status_tick(void);
804812

805813
/* ============================================================
806814
* INVARIANT FRAME + FIELD-DELTA RUNTIME (v0.9)

atomik_os/src/fabric.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ void fabric_draw(window_t *w, int x, int y, int wd, int ht) {
618618
/* === shelf geometry — unchanged from v0.34-C === */
619619
#define FABRIC_SHELF_W 480
620620
#define FABRIC_SHELF_X (FB_W - FABRIC_SHELF_W - ATOMIK_GRID_L)
621-
#define FABRIC_SHELF_Y (ATOMIK_SAFE_TOP + 32 + ATOMIK_GRID_M)
621+
#define FABRIC_SHELF_Y (ATOMIK_SAFE_TOP + ATOMIK_PULSE_BAR_H + ATOMIK_GRID_M)
622622
/* v0.34-D: 5 lanes × (76 + 6) + header (~84) ≈ 494; bump to 540 for
623623
* comfortable bottom margin and to leave room for v0.35 status row. */
624624
#define FABRIC_SHELF_H 540

atomik_os/src/main.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,9 @@ int main(int argc, char **argv) {
332332
* in-flight delta pulses based on EVT_SYNC_REPLICA event-bus
333333
* deltas. Passive observer; cheap. */
334334
replica_flow_tick();
335+
/* v0.38-C: tick the Pulse Bar's event-pulse sparkline sampler.
336+
* Self-rate-limits to 150 ms. Cheap. */
337+
status_tick();
335338
if (ev.kind == EV_QUIT) { s_running = 0; break; }
336339
if (ev.kind == EV_KEY) {
337340
int dirty = 0;
@@ -401,6 +404,15 @@ int main(int argc, char **argv) {
401404
replica_flow_open();
402405
dirty = 1;
403406
}
407+
/* v0.38-C: 'O' cycles the metric_mode (DEV → DEMO →
408+
* INVESTOR → DEV). System-shelf class — the Pulse Bar's
409+
* MODE badge updates immediately to show the new gating. */
410+
else if (ev.key == 'o' || ev.key == 'O') {
411+
metric_mode_t m = metric_mode();
412+
m = (metric_mode_t)((m + 1) % 3);
413+
metric_set_mode(m);
414+
dirty = 1;
415+
}
404416
/* (3) FOCUSED-APP KEYS — terminal/files/notes/document */
405417
else if (s_terminal_id) {
406418
window_t *top = wm_topmost();

atomik_os/src/replica_flow.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ void replica_flow_open(void) {
440440
int x = dock_right_edge() + ATOMIK_GRID_L;
441441
int w = fabric_shelf_x() - x - ATOMIK_GRID_L;
442442
int h = 480;
443-
int y = ATOMIK_SAFE_TOP + 32 + ATOMIK_GRID_L;
443+
int y = ATOMIK_SAFE_TOP + ATOMIK_PULSE_BAR_H + ATOMIK_GRID_L;
444444
if (w < 700) w = 700; /* clamp on narrower boards */
445445
window_t *win = wm_open("Replica Flow",
446446
x, y, w, h,

atomik_os/src/status.c

Lines changed: 158 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
/* status.c — top status bar.
1+
/* status.c — Pulse Bar.
22
*
33
* Reads /proc/uptime for clock display (since this rootfs has no RTC),
4-
* /proc/stat for CPU usage, and surfaces the agent prediction. Renders
5-
* along the very top edge of the screen above the desktop. */
4+
* /proc/stat for CPU usage, and surfaces the agent prediction.
5+
*
6+
* v0.38-C: Pulse Bar 2.0 per ChatGPT 2026-05-10 directive — make the
7+
* top bar feel like a real status instrument, not a thin text strip.
8+
* - bar height 32 → ATOMIK_PULSE_BAR_H (40 px) for visual weight
9+
* - ATOMiK wordmark scaled 1 → 2 (the visual anchor on the left)
10+
* - DATA / MODE / PERSONALITY badges with colored locator dots
11+
* - Event-pulse mini-waveform driven by atomik_event_total deltas
12+
* - Mode is now controllable via 'M' (DEV → DEMO → INVESTOR → DEV)
13+
* - Right segment still: wallet · cpu · uptime · version */
614
#include "atomik_os.h"
715
#include <stdio.h>
816
#include <string.h>
@@ -19,6 +27,20 @@ static unsigned long s_last_total = 0;
1927
static unsigned long s_last_idle = 0;
2028
static int s_cpu_pct = 0;
2129

30+
/* === v0.38-C: event-pulse mini-waveform state ===
31+
*
32+
* Sample atomik_event_total() at fixed cadence; render the last N
33+
* deltas as a thin sparkline inside the Pulse Bar. Visible signal of
34+
* "is the system alive and doing work right now." Real data — the
35+
* bus is the same event ring every other surface consumes. */
36+
#define PULSE_HISTORY_N 32
37+
#define PULSE_SAMPLE_MS 150
38+
static uint16_t s_pulse_hist[PULSE_HISTORY_N];
39+
static uint8_t s_pulse_head;
40+
static uint8_t s_pulse_count;
41+
static unsigned long s_last_pulse_sample_ms;
42+
static unsigned long s_last_pulse_total;
43+
2244
static void update_cpu(void) {
2345
FILE *f = fopen("/proc/stat", "r");
2446
if (!f) return;
@@ -51,6 +73,93 @@ static void format_uptime(char *out, size_t cap) {
5173
snprintf(out, cap, "uptime %02d:%02d:%02d", h, m, s);
5274
}
5375

76+
/* v0.38-C: poll the event bus at PULSE_SAMPLE_MS and push the delta
77+
* into the sparkline ring. Called every frame; self-rate-limits. No
78+
* heavy work — one atomik_event_total() call + a ring store. */
79+
void status_tick(void) {
80+
unsigned long now = anim_now_ms();
81+
if (now - s_last_pulse_sample_ms < PULSE_SAMPLE_MS) return;
82+
s_last_pulse_sample_ms = now;
83+
unsigned long total = atomik_event_total();
84+
unsigned long delta = total - s_last_pulse_total;
85+
s_last_pulse_total = total;
86+
/* Clamp so a burst doesn't blow out the auto-normalization. */
87+
if (delta > 0xFFFF) delta = 0xFFFF;
88+
s_pulse_hist[s_pulse_head] = (uint16_t)delta;
89+
s_pulse_head = (s_pulse_head + 1) % PULSE_HISTORY_N;
90+
if (s_pulse_count < PULSE_HISTORY_N) s_pulse_count++;
91+
}
92+
93+
/* Thin Bresenham segment — same approach as fabric_history mini-waves. */
94+
static void pulse_line(int x0, int y0, int x1, int y1, pixel_t color) {
95+
int dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
96+
int dy = (y1 > y0) ? (y1 - y0) : (y0 - y1);
97+
int sx = (x0 < x1) ? 1 : -1;
98+
int sy = (y0 < y1) ? 1 : -1;
99+
int err = dx - dy;
100+
while (1) {
101+
draw_pixel(x0, y0, color);
102+
if (x0 == x1 && y0 == y1) break;
103+
int e2 = err * 2;
104+
if (e2 > -dy) { err -= dy; x0 += sx; }
105+
if (e2 < dx) { err += dx; y0 += sy; }
106+
}
107+
}
108+
109+
static int draw_event_pulse(int x, int y, int w, int h) {
110+
/* Track outline — dim 1-px hairline so the sparkline always sits
111+
* on a visible bed. */
112+
pixel_t bed = rgb(0x11, 0x18, 0x28);
113+
draw_rect(x, y, w, h, bed);
114+
draw_rect(x, y + h - 1, w, 1, ATOMIK_DOCK_BORDER);
115+
if (s_pulse_count < 2) return w;
116+
/* Auto-normalize: max-of-window so quiet periods don't compress. */
117+
uint16_t mx = 0;
118+
for (uint8_t i = 0; i < s_pulse_count; i++)
119+
if (s_pulse_hist[i] > mx) mx = s_pulse_hist[i];
120+
if (mx == 0) {
121+
/* Constant zero — draw a flat dim baseline. */
122+
draw_rect(x, y + h - 2, w, 1, ATOMIK_DOCK_BORDER);
123+
return w;
124+
}
125+
/* Walk samples in chronological order. */
126+
int prev_px = -1, prev_py = -1;
127+
for (uint8_t i = 0; i < s_pulse_count; i++) {
128+
int idx = (s_pulse_head - s_pulse_count + i + PULSE_HISTORY_N)
129+
% PULSE_HISTORY_N;
130+
uint16_t v = s_pulse_hist[idx];
131+
int px = x + (int)((long)i * (w - 1) / (PULSE_HISTORY_N - 1));
132+
int py = y + h - 2 -
133+
(int)((long)v * (h - 3) / mx);
134+
if (py < y + 1) py = y + 1;
135+
if (py > y + h - 2) py = y + h - 2;
136+
if (prev_px >= 0)
137+
pulse_line(prev_px, prev_py, px, py, ATOMIK_SEM_HARDWARE);
138+
else
139+
draw_pixel(px, py, ATOMIK_SEM_HARDWARE);
140+
prev_px = px;
141+
prev_py = py;
142+
}
143+
return w;
144+
}
145+
146+
static const char *mode_label(metric_mode_t m) {
147+
switch (m) {
148+
case METRIC_MODE_DEV: return "DEV";
149+
case METRIC_MODE_DEMO: return "DEMO";
150+
case METRIC_MODE_INVESTOR: return "INVESTOR";
151+
default: return "?";
152+
}
153+
}
154+
static pixel_t mode_color(metric_mode_t m) {
155+
switch (m) {
156+
case METRIC_MODE_DEV: return ATOMIK_SEM_WASTE; /* amber — relaxed truth */
157+
case METRIC_MODE_DEMO: return ATOMIK_SEM_HARDWARE; /* cyan — rehearsal */
158+
case METRIC_MODE_INVESTOR: return ATOMIK_SEM_SAVINGS; /* green — strictest */
159+
default: return ATOMIK_FG_DIM;
160+
}
161+
}
162+
54163
void status_draw(void) {
55164
update_cpu();
56165

@@ -66,9 +175,10 @@ void status_draw(void) {
66175
* which appeared bottom-biased on monitors with less than 32 px
67176
* of overscan (text 8 px from the bottom edge of a visibly-larger
68177
* bar). User feedback 2026-05-07. */
69-
const int bar_h = 32;
178+
const int bar_h = ATOMIK_PULSE_BAR_H; /* v0.38-C: 40 px */
70179
const int bar_y = ATOMIK_SAFE_TOP;
71180
const int ty = bar_y + (bar_h - text_height(1)) / 2;
181+
const int ty_brand = bar_y + (bar_h - text_height(2)) / 2;
72182

73183
/* Bar fills only the safe zone — wallpaper covers y=0..SAFE_TOP
74184
* (cropped/invisible on this monitor; visible as a thin strip on
@@ -98,8 +208,10 @@ void status_draw(void) {
98208
*/
99209
const char *brand = "ATOMiK";
100210
int cur_x = ATOMIK_GRID_L;
101-
draw_text(cur_x, ty, brand, 1, ATOMIK_SEM_HARDWARE);
102-
cur_x += text_width(brand, 1) + ATOMIK_GRID_M;
211+
/* v0.38-C: scale-2 brand — visual anchor on the left. Scale-2
212+
* text is 32 px tall, vertically centered inside the 40 px bar. */
213+
draw_text(cur_x, ty_brand, brand, 2, ATOMIK_SEM_HARDWARE);
214+
cur_x += text_width(brand, 2) + ATOMIK_GRID_L;
103215

104216
/* Vertical separator between sections — dim, 1 px wide, stops the
105217
* bar from feeling like one long undifferentiated text run. */
@@ -137,6 +249,28 @@ void status_draw(void) {
137249
cur_x += ATOMIK_GRID_M + 1;
138250
}
139251

252+
/* === MODE: <DEV/DEMO/INVESTOR> badge — v0.38-C ===
253+
*
254+
* Surfaces the metric_mode_t that gates which metric sources can
255+
* display. DEV (amber) shows everything including MOCK; DEMO
256+
* (cyan) hides pure MOCK; INVESTOR (green) shows only LIVE +
257+
* DERIVED + labeled SCENARIO. Cycle with 'M' key from the global
258+
* key router. */
259+
{
260+
metric_mode_t m = metric_mode();
261+
char mode_badge[32];
262+
snprintf(mode_badge, sizeof mode_badge, "MODE: %s", mode_label(m));
263+
pixel_t mcol = mode_color(m);
264+
int dot_y4 = bar_y + (bar_h - ATOMIK_GRID_M) / 2;
265+
draw_rect(cur_x, dot_y4, ATOMIK_GRID_M, ATOMIK_GRID_M, mcol);
266+
cur_x += ATOMIK_GRID_M + ATOMIK_GRID_S;
267+
draw_text(cur_x, ty, mode_badge, 1, mcol);
268+
cur_x += text_width(mode_badge, 1) + ATOMIK_GRID_M;
269+
draw_rect(cur_x, bar_y + ATOMIK_GRID_S,
270+
1, bar_h - ATOMIK_GRID_S * 2, ATOMIK_DOCK_BORDER);
271+
cur_x += ATOMIK_GRID_M + 1;
272+
}
273+
140274
/* === Active personality badge ===
141275
*
142276
* v0.34-B: surfaces the current Resource Fabric personality globally
@@ -217,6 +351,24 @@ void status_draw(void) {
217351
}
218352
}
219353

354+
/* === Event-pulse mini-waveform — v0.38-C ===
355+
*
356+
* Sparkline of atomik_event_total() deltas over the last ~5 s
357+
* (32 samples × 150 ms). Real data — the same event ring every
358+
* other surface consumes. Visual signal: "is the system alive
359+
* and producing events right now?". Cyan (HARDWARE) so it reads
360+
* as system telemetry, not application state. */
361+
{
362+
const char *pulse_label = "PULSE";
363+
draw_text(cur_x, ty, pulse_label, 1, ATOMIK_FG_DIM);
364+
cur_x += text_width(pulse_label, 1) + ATOMIK_GRID_S;
365+
int pulse_w = 96;
366+
int pulse_h = bar_h - ATOMIK_GRID_S * 2;
367+
int pulse_y = bar_y + ATOMIK_GRID_S;
368+
draw_event_pulse(cur_x, pulse_y, pulse_w, pulse_h);
369+
cur_x += pulse_w + ATOMIK_GRID_M;
370+
}
371+
220372
/* Separator before window strip + hints. */
221373
draw_rect(cur_x, bar_y + ATOMIK_GRID_S,
222374
1, bar_h - ATOMIK_GRID_S * 2, ATOMIK_DOCK_BORDER);

0 commit comments

Comments
 (0)