create-rezi 0.1.0-alpha.2 → 0.1.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -10
- package/dist/index.js +31 -16
- package/dist/index.js.map +1 -1
- package/dist/scaffold.d.ts +2 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +35 -36
- package/dist/scaffold.js.map +1 -1
- package/package.json +7 -4
- package/templates/cli-tool/README.md +42 -0
- package/templates/{file-browser → cli-tool}/package.json +3 -2
- package/templates/cli-tool/src/main.ts +1037 -0
- package/templates/dashboard/README.md +30 -7
- package/templates/dashboard/package.json +3 -2
- package/templates/dashboard/src/main.ts +1675 -198
- package/templates/stress-test/README.md +81 -0
- package/templates/{streaming-viewer → stress-test}/package.json +3 -2
- package/templates/stress-test/src/main.ts +1615 -0
- package/dist/__tests__/scaffold.test.d.ts +0 -2
- package/dist/__tests__/scaffold.test.d.ts.map +0 -1
- package/dist/__tests__/scaffold.test.js +0 -29
- package/dist/__tests__/scaffold.test.js.map +0 -1
- package/templates/file-browser/README.md +0 -18
- package/templates/file-browser/src/main.ts +0 -258
- package/templates/form-app/README.md +0 -18
- package/templates/form-app/package.json +0 -23
- package/templates/form-app/src/main.ts +0 -222
- package/templates/streaming-viewer/README.md +0 -17
- package/templates/streaming-viewer/src/main.ts +0 -176
- package/templates/streaming-viewer/tsconfig.json +0 -14
- /package/templates/{file-browser → cli-tool}/tsconfig.json +0 -0
- /package/templates/{form-app → stress-test}/tsconfig.json +0 -0
|
@@ -0,0 +1,1615 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { devNull, tmpdir } from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { BadgeVariant, RichTextSpan, TextStyle, ThemeDefinition, VNode } from "@rezi-ui/core";
|
|
5
|
+
import {
|
|
6
|
+
createApp,
|
|
7
|
+
darkTheme,
|
|
8
|
+
dimmedTheme,
|
|
9
|
+
draculaTheme,
|
|
10
|
+
highContrastTheme,
|
|
11
|
+
lightTheme,
|
|
12
|
+
nordTheme,
|
|
13
|
+
rgb,
|
|
14
|
+
ui,
|
|
15
|
+
} from "@rezi-ui/core";
|
|
16
|
+
import { createNodeBackend } from "@rezi-ui/node";
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Types
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
type Phase = 1 | 2 | 3 | 4 | 5;
|
|
23
|
+
type ThemeName = "nord" | "dracula" | "dimmed" | "dark" | "light" | "high-contrast";
|
|
24
|
+
|
|
25
|
+
type Event = {
|
|
26
|
+
id: number;
|
|
27
|
+
at: string;
|
|
28
|
+
severity: "info" | "warn" | "critical";
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type ThemeSpec = {
|
|
33
|
+
label: string;
|
|
34
|
+
theme: ThemeDefinition;
|
|
35
|
+
badge: BadgeVariant;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type PhaseSpec = {
|
|
39
|
+
name: string;
|
|
40
|
+
hz: number;
|
|
41
|
+
durationMs: number;
|
|
42
|
+
intensity: number;
|
|
43
|
+
cpuBurnIters: number;
|
|
44
|
+
ioBlocks: number;
|
|
45
|
+
ballastMb: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type LaneSize = {
|
|
49
|
+
width: number;
|
|
50
|
+
height: number;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type SimModel = {
|
|
54
|
+
drawOpsPerTick: number;
|
|
55
|
+
colorChurnPct: number;
|
|
56
|
+
textChurnPct: number;
|
|
57
|
+
motionPct: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type State = {
|
|
61
|
+
phase: Phase;
|
|
62
|
+
phaseStartedAt: number;
|
|
63
|
+
phaseElapsedMs: number;
|
|
64
|
+
ticks: number;
|
|
65
|
+
paused: boolean;
|
|
66
|
+
helpOpen: boolean;
|
|
67
|
+
turbo: boolean;
|
|
68
|
+
writeFlood: boolean;
|
|
69
|
+
themeName: ThemeName;
|
|
70
|
+
startedAt: number;
|
|
71
|
+
nowMs: number;
|
|
72
|
+
totalUpdates: number;
|
|
73
|
+
nextEventId: number;
|
|
74
|
+
events: readonly Event[];
|
|
75
|
+
rssGoalNotified: boolean;
|
|
76
|
+
liveUpdateHz: number;
|
|
77
|
+
lastUpdateMs: number;
|
|
78
|
+
lastRenderMs: number;
|
|
79
|
+
lastEventLoopLagMs: number;
|
|
80
|
+
layoutRectCount: number;
|
|
81
|
+
backendEventPollP95Ms: number;
|
|
82
|
+
simDrawOpsPerTick: number;
|
|
83
|
+
simColorChurnPct: number;
|
|
84
|
+
simTextChurnPct: number;
|
|
85
|
+
simMotionPct: number;
|
|
86
|
+
lastRealCpuBurnMs: number;
|
|
87
|
+
lastRealIoWriteMBs: number;
|
|
88
|
+
lastRealBallastMB: number;
|
|
89
|
+
rssBytes: number;
|
|
90
|
+
heapUsedBytes: number;
|
|
91
|
+
processCpuPct: number;
|
|
92
|
+
throughputHistory: readonly number[];
|
|
93
|
+
processCpuHistory: readonly number[];
|
|
94
|
+
rssHistory: readonly number[];
|
|
95
|
+
lagHistory: readonly number[];
|
|
96
|
+
drawHistory: readonly number[];
|
|
97
|
+
ioHistory: readonly number[];
|
|
98
|
+
updateTimeHistory: readonly number[];
|
|
99
|
+
renderTimeHistory: readonly number[];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Constants
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
const PRODUCT_NAME = "__APP_NAME__";
|
|
107
|
+
const APP_NAME_PLACEHOLDER = "__APP" + "_NAME__";
|
|
108
|
+
const PRODUCT_DISPLAY_NAME =
|
|
109
|
+
PRODUCT_NAME === APP_NAME_PLACEHOLDER ? "Rezi Visual Benchmark" : PRODUCT_NAME;
|
|
110
|
+
|
|
111
|
+
const UI_FPS_CAP = 30;
|
|
112
|
+
const SPARKLINE_HISTORY_SIZE = 24;
|
|
113
|
+
const MAX_EVENTS = 12;
|
|
114
|
+
const MEMORY_SAMPLE_INTERVAL = 2;
|
|
115
|
+
const BACKEND_PERF_SAMPLE_INTERVAL_MS = 2_000;
|
|
116
|
+
const PANEL_PX = 1;
|
|
117
|
+
const PANEL_PY = 0;
|
|
118
|
+
const CADENCE_PULSE_FRAMES = Object.freeze([
|
|
119
|
+
"▁",
|
|
120
|
+
"▂",
|
|
121
|
+
"▃",
|
|
122
|
+
"▄",
|
|
123
|
+
"▅",
|
|
124
|
+
"▆",
|
|
125
|
+
"▇",
|
|
126
|
+
"█",
|
|
127
|
+
"▇",
|
|
128
|
+
"▆",
|
|
129
|
+
"▅",
|
|
130
|
+
"▄",
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
const LANE_MIN_WIDTH = 40;
|
|
134
|
+
const LANE_MAX_WIDTH = 92;
|
|
135
|
+
const LANE_MIN_HEIGHT = 14;
|
|
136
|
+
const LANE_MAX_HEIGHT = 28;
|
|
137
|
+
const LAYOUT_RESERVED_ROWS = 24;
|
|
138
|
+
|
|
139
|
+
const MEMORY_BALLAST_CHUNK_BYTES = 16 * 1024 * 1024;
|
|
140
|
+
const MEMORY_BALLAST_STEP_CHUNKS = 2;
|
|
141
|
+
|
|
142
|
+
const REAL_IO_BLOCK_BYTES = 64 * 1024;
|
|
143
|
+
const REAL_IO_BUFFER = Buffer.alloc(REAL_IO_BLOCK_BYTES, 0x5a);
|
|
144
|
+
|
|
145
|
+
const PHASE_SPECS: readonly PhaseSpec[] = Object.freeze([
|
|
146
|
+
{
|
|
147
|
+
name: "Boot",
|
|
148
|
+
hz: 2,
|
|
149
|
+
durationMs: 10_000,
|
|
150
|
+
intensity: 0.18,
|
|
151
|
+
cpuBurnIters: 20_000,
|
|
152
|
+
ioBlocks: 1,
|
|
153
|
+
ballastMb: 128,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "Build",
|
|
157
|
+
hz: 4,
|
|
158
|
+
durationMs: 12_000,
|
|
159
|
+
intensity: 0.34,
|
|
160
|
+
cpuBurnIters: 48_000,
|
|
161
|
+
ioBlocks: 2,
|
|
162
|
+
ballastMb: 256,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "Load",
|
|
166
|
+
hz: 8,
|
|
167
|
+
durationMs: 16_000,
|
|
168
|
+
intensity: 0.56,
|
|
169
|
+
cpuBurnIters: 88_000,
|
|
170
|
+
ioBlocks: 4,
|
|
171
|
+
ballastMb: 512,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "Surge",
|
|
175
|
+
hz: 9,
|
|
176
|
+
durationMs: 20_000,
|
|
177
|
+
intensity: 0.78,
|
|
178
|
+
cpuBurnIters: 108_000,
|
|
179
|
+
ioBlocks: 5,
|
|
180
|
+
ballastMb: 896,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "Overdrive",
|
|
184
|
+
hz: 14,
|
|
185
|
+
durationMs: 24_000,
|
|
186
|
+
intensity: 1.0,
|
|
187
|
+
cpuBurnIters: 170_000,
|
|
188
|
+
ioBlocks: 6,
|
|
189
|
+
ballastMb: 1280,
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
const FILE_ENTRIES = Object.freeze([
|
|
194
|
+
"services/api/router.ts",
|
|
195
|
+
"services/auth/token.ts",
|
|
196
|
+
"services/queue/worker.ts",
|
|
197
|
+
"ui/widgets/chart.ts",
|
|
198
|
+
"ui/widgets/terminal.ts",
|
|
199
|
+
"bench/scenes/matrix.ts",
|
|
200
|
+
"bench/scenes/geometry.ts",
|
|
201
|
+
"bench/scenes/text-lab.ts",
|
|
202
|
+
"runtime/metrics.ts",
|
|
203
|
+
"runtime/render-loop.ts",
|
|
204
|
+
"runtime/layout-engine.ts",
|
|
205
|
+
"runtime/event-loop.ts",
|
|
206
|
+
"storage/cache/index.ts",
|
|
207
|
+
"storage/cache/hotset.ts",
|
|
208
|
+
"storage/blob/writer.ts",
|
|
209
|
+
"storage/blob/compactor.ts",
|
|
210
|
+
]);
|
|
211
|
+
|
|
212
|
+
const COMMAND_LINES = Object.freeze([
|
|
213
|
+
"render --scene geometry --density high",
|
|
214
|
+
"stream --lane text-lab --follow",
|
|
215
|
+
"trace --lane matrix-rain --span 2s",
|
|
216
|
+
"bench --profile overdrive --capture",
|
|
217
|
+
"diff --render-ops --window 5s",
|
|
218
|
+
]);
|
|
219
|
+
|
|
220
|
+
const MATRIX_GLYPHS = Object.freeze("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#$%&@*".split(""));
|
|
221
|
+
const SHAPE_GLYPHS = Object.freeze([" ", "·", ":", "-", "=", "+", "*", "░", "▒", "▓", "█"]);
|
|
222
|
+
const BAR_PARTIALS = Object.freeze(["", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]);
|
|
223
|
+
|
|
224
|
+
const themeCatalog: Record<ThemeName, ThemeSpec> = {
|
|
225
|
+
nord: { label: "Nord", theme: nordTheme, badge: "info" },
|
|
226
|
+
dracula: { label: "Dracula", theme: draculaTheme, badge: "warning" },
|
|
227
|
+
dimmed: { label: "Dimmed", theme: dimmedTheme, badge: "default" },
|
|
228
|
+
dark: { label: "Dark", theme: darkTheme, badge: "default" },
|
|
229
|
+
light: { label: "Light", theme: lightTheme, badge: "success" },
|
|
230
|
+
"high-contrast": { label: "High Contrast", theme: highContrastTheme, badge: "error" },
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const themeOrder: readonly ThemeName[] = Object.freeze([
|
|
234
|
+
"nord",
|
|
235
|
+
"dracula",
|
|
236
|
+
"dimmed",
|
|
237
|
+
"dark",
|
|
238
|
+
"light",
|
|
239
|
+
"high-contrast",
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
type ShortcutSpec = Readonly<{ keys: string | readonly string[]; description: string }>;
|
|
243
|
+
|
|
244
|
+
const HELP_SHORTCUTS: readonly ShortcutSpec[] = Object.freeze([
|
|
245
|
+
{ keys: ["p", "space"], description: "Pause / resume" },
|
|
246
|
+
{ keys: ["+", "-"], description: "Phase up / down" },
|
|
247
|
+
{ keys: "r", description: "Reset benchmark" },
|
|
248
|
+
{ keys: "t", description: "Cycle theme" },
|
|
249
|
+
{ keys: "z", description: "Toggle turbo mode" },
|
|
250
|
+
{ keys: "w", description: "Toggle write flood" },
|
|
251
|
+
{ keys: "h", description: "Open help" },
|
|
252
|
+
{ keys: "escape", description: "Close help" },
|
|
253
|
+
{ keys: "q", description: "Quit" },
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Utilities
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
function clamp(value: number, min: number, max: number): number {
|
|
261
|
+
return Math.max(min, Math.min(max, value));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function round2(value: number): number {
|
|
265
|
+
return Math.round(value * 100) / 100;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function smoothFloat(current: number, target: number, gain: number): number {
|
|
269
|
+
return current + (target - current) * gain;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function fixedLabel(value: string, maxChars: number, width = maxChars): string {
|
|
273
|
+
const clipped =
|
|
274
|
+
value.length > maxChars
|
|
275
|
+
? maxChars <= 1
|
|
276
|
+
? value.slice(0, 1)
|
|
277
|
+
: `${value.slice(0, maxChars - 1)}\u2026`
|
|
278
|
+
: value;
|
|
279
|
+
return clipped.padEnd(width, " ");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function signedDelta(value: number, digits = 0): string {
|
|
283
|
+
const rounded = digits === 0 ? Math.round(value).toString() : value.toFixed(digits);
|
|
284
|
+
if (value > 0) return `+${rounded}`;
|
|
285
|
+
if (value < 0) return rounded;
|
|
286
|
+
return digits === 0 ? "0" : Number(value).toFixed(digits);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function animationFrame(frames: readonly string[], tick: number): string {
|
|
290
|
+
if (frames.length === 0) return "";
|
|
291
|
+
return frames[Math.abs(tick) % frames.length] ?? "";
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function shortcutLabel(keys: string | readonly string[]): string {
|
|
295
|
+
const parts = Array.isArray(keys) ? keys : [keys];
|
|
296
|
+
return parts.join(" + ");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function timeStamp(date = new Date()): string {
|
|
300
|
+
return date.toLocaleTimeString("en-US", { hour12: false });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function pushSeries(
|
|
304
|
+
history: readonly number[],
|
|
305
|
+
value: number,
|
|
306
|
+
maxSize = SPARKLINE_HISTORY_SIZE,
|
|
307
|
+
): readonly number[] {
|
|
308
|
+
return Object.freeze([...history, value].slice(-maxSize));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function repeatSeries(value: number, size = SPARKLINE_HISTORY_SIZE): readonly number[] {
|
|
312
|
+
return Object.freeze(Array.from({ length: size }, () => value));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function phaseSpec(phase: Phase): PhaseSpec {
|
|
316
|
+
const spec = PHASE_SPECS[phase - 1];
|
|
317
|
+
if (spec) return spec;
|
|
318
|
+
return {
|
|
319
|
+
name: "Boot",
|
|
320
|
+
hz: 2,
|
|
321
|
+
durationMs: 10_000,
|
|
322
|
+
intensity: 0.18,
|
|
323
|
+
cpuBurnIters: 20_000,
|
|
324
|
+
ioBlocks: 1,
|
|
325
|
+
ballastMb: 128,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function nextThemeName(current: ThemeName): ThemeName {
|
|
330
|
+
const index = themeOrder.indexOf(current);
|
|
331
|
+
const nextIndex = index < 0 ? 0 : (index + 1) % themeOrder.length;
|
|
332
|
+
return themeOrder[nextIndex] ?? themeOrder[0] ?? "nord";
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function formatBytes(bytes: number): string {
|
|
336
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
337
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
338
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
339
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function hash32(value: number): number {
|
|
343
|
+
let x = value >>> 0;
|
|
344
|
+
x ^= x >>> 16;
|
|
345
|
+
x = Math.imul(x, 0x7feb352d);
|
|
346
|
+
x ^= x >>> 15;
|
|
347
|
+
x = Math.imul(x, 0x846ca68b);
|
|
348
|
+
x ^= x >>> 16;
|
|
349
|
+
return x >>> 0;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function noise4(a: number, b: number, c: number, d = 0): number {
|
|
353
|
+
const seed =
|
|
354
|
+
(Math.imul(a + 1, 374761393) ^
|
|
355
|
+
Math.imul(b + 1, 668265263) ^
|
|
356
|
+
Math.imul(c + 1, 362437) ^
|
|
357
|
+
Math.imul(d + 1, 1274126177)) >>>
|
|
358
|
+
0;
|
|
359
|
+
return hash32(seed) / 0xffffffff;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function lerpChannel(start: number, end: number, t: number): number {
|
|
363
|
+
return Math.round(start + (end - start) * t);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function clampByte(value: number): number {
|
|
367
|
+
return clamp(Math.round(value), 0, 255);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function neonColor(t: number, hueShift = 0): ReturnType<typeof rgb> {
|
|
371
|
+
const angle = t * Math.PI * 2 + hueShift;
|
|
372
|
+
const r = 134 + 110 * Math.sin(angle);
|
|
373
|
+
const g = 134 + 110 * Math.sin(angle + 2.09);
|
|
374
|
+
const b = 134 + 110 * Math.sin(angle + 4.18);
|
|
375
|
+
return rgb(clampByte(r), clampByte(g), clampByte(b));
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function metricDelta(history: readonly number[]): number {
|
|
379
|
+
const last = history[history.length - 1] ?? 0;
|
|
380
|
+
const prev = history[history.length - 2] ?? last;
|
|
381
|
+
return last - prev;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function smoothBar(value: number, width: number): string {
|
|
385
|
+
const clamped = clamp(value, 0, 1);
|
|
386
|
+
const exact = clamped * width;
|
|
387
|
+
const full = Math.floor(exact);
|
|
388
|
+
const rem = exact - full;
|
|
389
|
+
const partialIndex = clamp(Math.round(rem * 8), 0, 7);
|
|
390
|
+
const partial = partialIndex > 0 && full < width ? (BAR_PARTIALS[partialIndex] ?? "") : "";
|
|
391
|
+
const consumed = full + (partial ? 1 : 0);
|
|
392
|
+
return `${"█".repeat(full)}${partial}${" ".repeat(Math.max(0, width - consumed))}`;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function eventVariant(severity: Event["severity"]): BadgeVariant {
|
|
396
|
+
if (severity === "critical") return "error";
|
|
397
|
+
if (severity === "warn") return "warning";
|
|
398
|
+
return "info";
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function eventIcon(severity: Event["severity"]): string {
|
|
402
|
+
if (severity === "critical") return "status.cross";
|
|
403
|
+
if (severity === "warn") return "status.warning";
|
|
404
|
+
return "status.info";
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function phaseBadgeVariant(phase: Phase, current: Phase): BadgeVariant {
|
|
408
|
+
if (phase === current) return "warning";
|
|
409
|
+
if (phase < current) return "success";
|
|
410
|
+
return "default";
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function pushCell(spans: RichTextSpan[], text: string, style?: TextStyle): void {
|
|
414
|
+
if (style) spans.push({ text, style });
|
|
415
|
+
else spans.push({ text });
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function statusBadgeVariant(cpuPct: number, lagMs: number): BadgeVariant {
|
|
419
|
+
if (cpuPct >= 90 || lagMs >= 20) return "error";
|
|
420
|
+
if (cpuPct >= 70 || lagMs >= 12) return "warning";
|
|
421
|
+
return "success";
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function terminalColumns(): number {
|
|
425
|
+
const cols = process.stdout.columns ?? 180;
|
|
426
|
+
return clamp(cols, 100, 420);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function terminalRows(): number {
|
|
430
|
+
const rows = process.stdout.rows ?? 48;
|
|
431
|
+
return clamp(rows, 30, 140);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function resolveLaneSize(): LaneSize {
|
|
435
|
+
const cols = terminalColumns();
|
|
436
|
+
const rows = terminalRows();
|
|
437
|
+
|
|
438
|
+
const width = clamp(Math.floor((cols - 8) / 3), LANE_MIN_WIDTH, LANE_MAX_WIDTH);
|
|
439
|
+
const reservedRows = clamp(
|
|
440
|
+
Math.floor(rows * 0.42),
|
|
441
|
+
LAYOUT_RESERVED_ROWS - 4,
|
|
442
|
+
LAYOUT_RESERVED_ROWS + 10,
|
|
443
|
+
);
|
|
444
|
+
const availableRows = Math.max(LANE_MIN_HEIGHT, rows - reservedRows);
|
|
445
|
+
const height = clamp(availableRows, LANE_MIN_HEIGHT, LANE_MAX_HEIGHT);
|
|
446
|
+
return { width, height };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function deriveSimModel(
|
|
450
|
+
tick: number,
|
|
451
|
+
intensity: number,
|
|
452
|
+
lane: LaneSize,
|
|
453
|
+
writeFlood: boolean,
|
|
454
|
+
turbo: boolean,
|
|
455
|
+
): SimModel {
|
|
456
|
+
const cells = lane.width * lane.height;
|
|
457
|
+
const geomPulse = 0.5 + 0.5 * Math.sin(tick / 9);
|
|
458
|
+
const textPulse = 0.5 + 0.5 * Math.cos(tick / 7);
|
|
459
|
+
const matrixPulse = 0.5 + 0.5 * Math.sin(tick / 5);
|
|
460
|
+
|
|
461
|
+
const geometryDensity = clamp(0.35 + intensity * 0.48 + (geomPulse - 0.5) * 0.14, 0.2, 0.96);
|
|
462
|
+
const textDensity = clamp(0.26 + intensity * 0.34 + (textPulse - 0.5) * 0.12, 0.15, 0.9);
|
|
463
|
+
const matrixDensity = clamp(0.3 + intensity * 0.5 + (matrixPulse - 0.5) * 0.18, 0.2, 0.98);
|
|
464
|
+
|
|
465
|
+
const deterministicOps = cells * (geometryDensity + textDensity + matrixDensity);
|
|
466
|
+
const drawOpsPerTick = Math.round(
|
|
467
|
+
deterministicOps * (writeFlood ? 1.14 : 1) * (turbo ? 1.08 : 1),
|
|
468
|
+
);
|
|
469
|
+
const colorChurnPct = round2(
|
|
470
|
+
clamp((geometryDensity * 0.48 + textDensity * 0.15 + matrixDensity * 0.52) * 100, 0, 100),
|
|
471
|
+
);
|
|
472
|
+
const textChurnPct = round2(
|
|
473
|
+
clamp((textDensity * 0.78 + matrixDensity * 0.14 + intensity * 0.08) * 100, 0, 100),
|
|
474
|
+
);
|
|
475
|
+
const motionPct = round2(
|
|
476
|
+
clamp((matrixDensity * 0.74 + geometryDensity * 0.2 + intensity * 0.1) * 100, 0, 100),
|
|
477
|
+
);
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
drawOpsPerTick,
|
|
481
|
+
colorChurnPct,
|
|
482
|
+
textChurnPct,
|
|
483
|
+
motionPct,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ---------------------------------------------------------------------------
|
|
488
|
+
// Visual lanes
|
|
489
|
+
// ---------------------------------------------------------------------------
|
|
490
|
+
|
|
491
|
+
function renderGeometryLane(tick: number, intensity: number, lane: LaneSize): readonly VNode[] {
|
|
492
|
+
const rows: VNode[] = [];
|
|
493
|
+
const width = lane.width;
|
|
494
|
+
const height = lane.height;
|
|
495
|
+
const t = tick * 0.045;
|
|
496
|
+
|
|
497
|
+
for (let y = 0; y < height; y++) {
|
|
498
|
+
const spans: RichTextSpan[] = [];
|
|
499
|
+
const ny = ((y / Math.max(1, height - 1)) * 2 - 1) * 1.2;
|
|
500
|
+
for (let x = 0; x < width; x++) {
|
|
501
|
+
const nx = ((x / Math.max(1, width - 1)) * 2 - 1) * 1.12;
|
|
502
|
+
const radius = Math.hypot(nx * 1.08, ny * 1.36);
|
|
503
|
+
const angle = Math.atan2(ny, nx);
|
|
504
|
+
const baseRing = 0.32 + 0.11 * Math.sin(t * 0.7 + angle * 1.6);
|
|
505
|
+
const ring = Math.exp(-(((radius - baseRing) * 6.8) ** 2));
|
|
506
|
+
const swirl = 0.5 + 0.5 * Math.sin(radius * 18 - t * 3.2 + Math.cos(angle * 4 + t) * 2.2);
|
|
507
|
+
const ridge = 0.5 + 0.5 * Math.sin(nx * 10.8 - ny * 7.9 + t * 2.1);
|
|
508
|
+
const spark = noise4(x, y, tick, 13);
|
|
509
|
+
const checker =
|
|
510
|
+
((Math.floor((nx + 1.2) * 6 + t * 0.8) + Math.floor((ny + 1.1) * 4 - t * 0.7)) & 1) === 0
|
|
511
|
+
? 0.16
|
|
512
|
+
: -0.08;
|
|
513
|
+
|
|
514
|
+
let signal = clamp(
|
|
515
|
+
0.22 +
|
|
516
|
+
ring * 0.44 +
|
|
517
|
+
swirl * 0.31 +
|
|
518
|
+
ridge * 0.24 +
|
|
519
|
+
checker +
|
|
520
|
+
spark * 0.16 +
|
|
521
|
+
intensity * 0.15,
|
|
522
|
+
0,
|
|
523
|
+
1,
|
|
524
|
+
);
|
|
525
|
+
const gridX = Math.max(4, Math.round(13 - intensity * 6));
|
|
526
|
+
const gridY = Math.max(3, Math.round(9 - intensity * 4));
|
|
527
|
+
if (
|
|
528
|
+
(x + Math.floor(tick * 0.7)) % gridX === 0 ||
|
|
529
|
+
(y + Math.floor(tick * 0.5)) % gridY === 0
|
|
530
|
+
) {
|
|
531
|
+
signal = Math.max(signal, 0.66 + 0.24 * spark);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
let glyph = SHAPE_GLYPHS[Math.round(signal * (SHAPE_GLYPHS.length - 1))] ?? " ";
|
|
535
|
+
if (glyph !== " " && signal > 0.9) glyph = spark > 0.5 ? "◆" : "●";
|
|
536
|
+
|
|
537
|
+
const color = neonColor(0.12 + signal * 0.92 + spark * 0.14, 0.35 + angle * 0.12);
|
|
538
|
+
if (glyph === " ") {
|
|
539
|
+
pushCell(spans, glyph);
|
|
540
|
+
} else if (signal > 0.95) {
|
|
541
|
+
const bg = rgb(
|
|
542
|
+
lerpChannel(16, 72, signal),
|
|
543
|
+
lerpChannel(12, 38, signal),
|
|
544
|
+
lerpChannel(26, 78, signal),
|
|
545
|
+
);
|
|
546
|
+
pushCell(spans, glyph, { fg: color, bg, bold: signal > 0.98 });
|
|
547
|
+
} else {
|
|
548
|
+
pushCell(spans, glyph, { fg: color });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
rows.push(ui.richText(spans));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return rows;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function renderTextLabLane(tick: number, intensity: number, lane: LaneSize): readonly VNode[] {
|
|
558
|
+
const rows: VNode[] = [];
|
|
559
|
+
const width = lane.width;
|
|
560
|
+
const height = lane.height;
|
|
561
|
+
const fileNameWidth = clamp(Math.floor(width * 0.48), 16, 54);
|
|
562
|
+
const barWidth = clamp(Math.floor(width * 0.24), 10, 24);
|
|
563
|
+
const cmd = COMMAND_LINES[Math.floor(tick / 14) % COMMAND_LINES.length] ?? "run benchmark";
|
|
564
|
+
const typePos = tick % (cmd.length + Math.max(10, Math.floor(width * 0.35)));
|
|
565
|
+
const typed = typePos <= cmd.length ? cmd.slice(0, typePos) : cmd;
|
|
566
|
+
const cursor = tick % 2 === 0 ? "▌" : " ";
|
|
567
|
+
|
|
568
|
+
rows.push(
|
|
569
|
+
ui.richText([
|
|
570
|
+
{ text: "$ ", style: { fg: rgb(120, 210, 255), bold: true } },
|
|
571
|
+
{ text: typed, style: { fg: rgb(195, 225, 255) } },
|
|
572
|
+
{ text: cursor, style: { fg: rgb(255, 216, 98), bold: true } },
|
|
573
|
+
]),
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
const readRate = Math.round(44 + intensity * 235 + 18 * Math.sin(tick / 8));
|
|
577
|
+
const writeRate = Math.round(16 + intensity * 176 + 22 * Math.cos(tick / 10));
|
|
578
|
+
const parseRate = Math.round(18 + intensity * 84 + 12 * Math.sin(tick / 11 + 0.6));
|
|
579
|
+
rows.push(
|
|
580
|
+
ui.richText([
|
|
581
|
+
{
|
|
582
|
+
text: `sim read ${readRate.toString().padStart(4, " ")} MB/s`,
|
|
583
|
+
style: { fg: rgb(150, 220, 255) },
|
|
584
|
+
},
|
|
585
|
+
{ text: " | " },
|
|
586
|
+
{
|
|
587
|
+
text: `sim write ${writeRate.toString().padStart(4, " ")} MB/s`,
|
|
588
|
+
style: { fg: rgb(255, 188, 110) },
|
|
589
|
+
},
|
|
590
|
+
{ text: " | " },
|
|
591
|
+
{
|
|
592
|
+
text: `parse ${parseRate.toString().padStart(3, " ")} op/s`,
|
|
593
|
+
style: { fg: rgb(165, 206, 255) },
|
|
594
|
+
},
|
|
595
|
+
]),
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
const queueDepth = clamp(0.22 + intensity * 0.68 + 0.12 * Math.sin(tick / 7), 0, 1);
|
|
599
|
+
rows.push(
|
|
600
|
+
ui.richText([
|
|
601
|
+
{ text: "queue ", style: { fg: rgb(148, 178, 212) } },
|
|
602
|
+
{
|
|
603
|
+
text: smoothBar(queueDepth, clamp(Math.floor(width * 0.25), 10, 24)),
|
|
604
|
+
style: { fg: rgb(98, 168, 230) },
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
text: ` ${(queueDepth * 100).toFixed(0).padStart(3, " ")}%`,
|
|
608
|
+
style: { fg: rgb(120, 196, 255) },
|
|
609
|
+
},
|
|
610
|
+
]),
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
const rowsForFiles = Math.max(3, height - 5);
|
|
614
|
+
for (let i = 0; i < rowsForFiles; i++) {
|
|
615
|
+
const idx = (i + Math.floor(tick / 2)) % FILE_ENTRIES.length;
|
|
616
|
+
const name = FILE_ENTRIES[idx] ?? "runtime/task.ts";
|
|
617
|
+
const pulse = 0.5 + 0.5 * Math.sin((tick + i * 2.7) / 6.5);
|
|
618
|
+
const load = clamp(noise4(i, tick, idx, 71) * 0.62 + pulse * 0.28 + intensity * 0.18, 0, 1);
|
|
619
|
+
const op = load > 0.82 ? "WRITE" : load > 0.62 ? "READ " : load > 0.38 ? "PARSE" : "IDLE ";
|
|
620
|
+
const opStyle: TextStyle =
|
|
621
|
+
op === "WRITE"
|
|
622
|
+
? { fg: rgb(255, 132, 132), bold: true }
|
|
623
|
+
: op === "READ "
|
|
624
|
+
? { fg: rgb(255, 214, 120), bold: true }
|
|
625
|
+
: op === "PARSE"
|
|
626
|
+
? { fg: rgb(130, 215, 255) }
|
|
627
|
+
: { fg: rgb(134, 152, 174), dim: true };
|
|
628
|
+
const pct = Math.round(load * 100);
|
|
629
|
+
const bar = smoothBar(load, barWidth);
|
|
630
|
+
const heatGlyph = SHAPE_GLYPHS[Math.round(load * (SHAPE_GLYPHS.length - 1))] ?? ".";
|
|
631
|
+
rows.push(
|
|
632
|
+
ui.richText([
|
|
633
|
+
{ text: fixedLabel(name, fileNameWidth, fileNameWidth), style: { fg: rgb(192, 204, 224) } },
|
|
634
|
+
{ text: " " },
|
|
635
|
+
{ text: op, style: opStyle },
|
|
636
|
+
{ text: " " },
|
|
637
|
+
{
|
|
638
|
+
text: fixedLabel(`${pct.toString().padStart(3, " ")}%`, 4, 4),
|
|
639
|
+
style: { fg: rgb(110, 196, 255) },
|
|
640
|
+
},
|
|
641
|
+
{ text: " " },
|
|
642
|
+
{ text: bar, style: { fg: rgb(98, 170, 225) } },
|
|
643
|
+
{ text: " " },
|
|
644
|
+
{ text: `${heatGlyph}${heatGlyph}`, style: { fg: rgb(168, 216, 255), dim: load < 0.42 } },
|
|
645
|
+
]),
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return rows.slice(0, height);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function renderMatrixLane(tick: number, intensity: number, lane: LaneSize): readonly VNode[] {
|
|
653
|
+
const rows: VNode[] = [];
|
|
654
|
+
const width = lane.width;
|
|
655
|
+
const height = lane.height;
|
|
656
|
+
const tailLen = Math.max(4, Math.round(6 + intensity * 9));
|
|
657
|
+
|
|
658
|
+
for (let y = 0; y < height; y++) {
|
|
659
|
+
const spans: RichTextSpan[] = [];
|
|
660
|
+
for (let x = 0; x < width; x++) {
|
|
661
|
+
const columnSeed = noise4(x, 0, 0, 17);
|
|
662
|
+
const speed = 0.45 + columnSeed * 1.25 + intensity * 0.65;
|
|
663
|
+
const phaseOffset = Math.floor(columnSeed * 17) + ((x * 9) % (height + 14));
|
|
664
|
+
const head = (tick * speed + phaseOffset) % (height + 14);
|
|
665
|
+
const distance = head - y;
|
|
666
|
+
let glyph = " ";
|
|
667
|
+
let style: TextStyle | undefined;
|
|
668
|
+
|
|
669
|
+
if (distance >= 0 && distance < tailLen) {
|
|
670
|
+
const g =
|
|
671
|
+
MATRIX_GLYPHS[
|
|
672
|
+
(x * 13 + y * 7 + tick + Math.floor(columnSeed * 23)) % MATRIX_GLYPHS.length
|
|
673
|
+
] ?? "#";
|
|
674
|
+
glyph = g;
|
|
675
|
+
if (distance < 1.1) {
|
|
676
|
+
style = { fg: rgb(232, 255, 236), bold: true };
|
|
677
|
+
} else if (distance < 3.8) {
|
|
678
|
+
style = { fg: rgb(166, 255, 174), bold: intensity > 0.85 };
|
|
679
|
+
} else {
|
|
680
|
+
const fade = 1 - distance / tailLen;
|
|
681
|
+
const green = lerpChannel(74, 210, fade);
|
|
682
|
+
const blue = lerpChannel(20, 110, fade * 0.65 + columnSeed * 0.35);
|
|
683
|
+
style = { fg: rgb(24, green, blue), dim: fade < 0.25 };
|
|
684
|
+
}
|
|
685
|
+
} else {
|
|
686
|
+
const spark = noise4(x, y, tick, 109);
|
|
687
|
+
if (spark > 0.994 - intensity * 0.03) {
|
|
688
|
+
glyph = spark > 0.994 ? "*" : ".";
|
|
689
|
+
style = { fg: rgb(42, 96, 58), dim: true };
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
pushCell(spans, glyph, style);
|
|
694
|
+
}
|
|
695
|
+
rows.push(ui.richText(spans));
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return rows;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// ---------------------------------------------------------------------------
|
|
702
|
+
// Real stress engines
|
|
703
|
+
// ---------------------------------------------------------------------------
|
|
704
|
+
|
|
705
|
+
let _lastUpdateDurationMs = 0;
|
|
706
|
+
let _lastCpuUsage = process.cpuUsage();
|
|
707
|
+
let _lastCpuSampleAtMs = Date.now();
|
|
708
|
+
let _lastRenderMs = 0;
|
|
709
|
+
let _lastLayoutRectCount = 0;
|
|
710
|
+
let _lastBackendEventPollP95Ms = 0;
|
|
711
|
+
let _lastViewMs = 0;
|
|
712
|
+
|
|
713
|
+
let _backendPerfInFlight = false;
|
|
714
|
+
let _lastBackendPerfSampleMs = 0;
|
|
715
|
+
let _backend: ReturnType<typeof createNodeBackend> | null = null;
|
|
716
|
+
|
|
717
|
+
let _memoryBallast: Buffer[] = [];
|
|
718
|
+
let _memoryBallastBytes = 0;
|
|
719
|
+
let _cpuBurnAccumulator = 0x13572468;
|
|
720
|
+
|
|
721
|
+
let _realIoFd: number | null = null;
|
|
722
|
+
let _realIoSinkLabel = "unavailable";
|
|
723
|
+
let _realIoSinkIsNullDevice = false;
|
|
724
|
+
|
|
725
|
+
function closeRealIoSink(): void {
|
|
726
|
+
if (_realIoFd === null) return;
|
|
727
|
+
try {
|
|
728
|
+
fs.closeSync(_realIoFd);
|
|
729
|
+
} catch {
|
|
730
|
+
// ignore cleanup error
|
|
731
|
+
}
|
|
732
|
+
_realIoFd = null;
|
|
733
|
+
_realIoSinkLabel = "unavailable";
|
|
734
|
+
_realIoSinkIsNullDevice = false;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function openRealIoSink(): void {
|
|
738
|
+
closeRealIoSink();
|
|
739
|
+
const fallbackNull = process.platform === "win32" ? "NUL" : "/dev/null";
|
|
740
|
+
const fileCandidates = [
|
|
741
|
+
path.join(tmpdir(), "rezi-visual-benchmark-sink.bin"),
|
|
742
|
+
path.join(process.cwd(), ".rezi-visual-benchmark-sink.bin"),
|
|
743
|
+
];
|
|
744
|
+
const candidates = [
|
|
745
|
+
{ target: devNull, nullDevice: true },
|
|
746
|
+
{ target: fallbackNull, nullDevice: true },
|
|
747
|
+
...fileCandidates.map((target) => ({ target, nullDevice: false })),
|
|
748
|
+
];
|
|
749
|
+
|
|
750
|
+
for (const candidate of candidates) {
|
|
751
|
+
try {
|
|
752
|
+
_realIoFd = fs.openSync(candidate.target, "w");
|
|
753
|
+
_realIoSinkLabel = candidate.target;
|
|
754
|
+
_realIoSinkIsNullDevice = candidate.nullDevice;
|
|
755
|
+
return;
|
|
756
|
+
} catch {
|
|
757
|
+
// try next candidate
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
_realIoFd = null;
|
|
762
|
+
_realIoSinkLabel = "unavailable";
|
|
763
|
+
_realIoSinkIsNullDevice = false;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
openRealIoSink();
|
|
767
|
+
|
|
768
|
+
function clearMemoryBallast(): void {
|
|
769
|
+
_memoryBallast = [];
|
|
770
|
+
_memoryBallastBytes = 0;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function adjustMemoryBallast(targetBytes: number): number {
|
|
774
|
+
const cappedTarget = Math.max(0, targetBytes);
|
|
775
|
+
const stepBytes = MEMORY_BALLAST_CHUNK_BYTES * MEMORY_BALLAST_STEP_CHUNKS;
|
|
776
|
+
|
|
777
|
+
if (_memoryBallastBytes < cappedTarget) {
|
|
778
|
+
let toAdd = Math.min(cappedTarget - _memoryBallastBytes, stepBytes);
|
|
779
|
+
while (toAdd > 0) {
|
|
780
|
+
const chunkBytes = Math.min(MEMORY_BALLAST_CHUNK_BYTES, toAdd);
|
|
781
|
+
const chunk = Buffer.alloc(chunkBytes, (_memoryBallast.length * 17) & 0xff);
|
|
782
|
+
_memoryBallast.push(chunk);
|
|
783
|
+
_memoryBallastBytes += chunkBytes;
|
|
784
|
+
toAdd -= chunkBytes;
|
|
785
|
+
}
|
|
786
|
+
} else if (_memoryBallastBytes > cappedTarget) {
|
|
787
|
+
let toRemove = Math.min(_memoryBallastBytes - cappedTarget, stepBytes);
|
|
788
|
+
while (toRemove > 0 && _memoryBallast.length > 0) {
|
|
789
|
+
const chunk = _memoryBallast.pop();
|
|
790
|
+
if (!chunk) break;
|
|
791
|
+
_memoryBallastBytes -= chunk.length;
|
|
792
|
+
toRemove -= chunk.length;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return _memoryBallastBytes;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function runRealCpuBurn(iterations: number, tick: number): number {
|
|
800
|
+
const count = Math.max(0, Math.floor(iterations));
|
|
801
|
+
const start = performance.now();
|
|
802
|
+
let acc = _cpuBurnAccumulator | 0;
|
|
803
|
+
for (let i = 0; i < count; i++) {
|
|
804
|
+
acc = Math.imul(acc ^ (tick + i), 1664525) + 1013904223;
|
|
805
|
+
acc ^= acc >>> 16;
|
|
806
|
+
}
|
|
807
|
+
_cpuBurnAccumulator = acc;
|
|
808
|
+
return round2(performance.now() - start);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
function runRealIoWrite(blocks: number): number {
|
|
812
|
+
if (_realIoFd === null) openRealIoSink();
|
|
813
|
+
if (_realIoFd === null) return 0;
|
|
814
|
+
|
|
815
|
+
const blockCount = Math.max(0, Math.floor(blocks));
|
|
816
|
+
const position = _realIoSinkIsNullDevice ? null : 0;
|
|
817
|
+
let written = 0;
|
|
818
|
+
|
|
819
|
+
for (let i = 0; i < blockCount; i++) {
|
|
820
|
+
if (_realIoFd === null) break;
|
|
821
|
+
try {
|
|
822
|
+
written += fs.writeSync(_realIoFd, REAL_IO_BUFFER, 0, REAL_IO_BUFFER.length, position);
|
|
823
|
+
} catch {
|
|
824
|
+
closeRealIoSink();
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return written;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// ---------------------------------------------------------------------------
|
|
833
|
+
// Simulation
|
|
834
|
+
// ---------------------------------------------------------------------------
|
|
835
|
+
|
|
836
|
+
let _nextIntervalMs = 1000 / phaseSpec(1).hz;
|
|
837
|
+
|
|
838
|
+
function simulateTick(state: State, nowMs: number): State {
|
|
839
|
+
if (state.paused) return state;
|
|
840
|
+
|
|
841
|
+
const tick = state.ticks + 1;
|
|
842
|
+
const tickDeltaMs = Math.max(1, nowMs - state.nowMs);
|
|
843
|
+
const instantHz = 1000 / tickDeltaMs;
|
|
844
|
+
const liveUpdateHz = round2(
|
|
845
|
+
smoothFloat(state.liveUpdateHz <= 0 ? instantHz : state.liveUpdateHz, instantHz, 0.35),
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
const prevSpec = phaseSpec(state.phase);
|
|
849
|
+
const elapsed = nowMs - state.phaseStartedAt;
|
|
850
|
+
|
|
851
|
+
let phase = state.phase;
|
|
852
|
+
let phaseStartedAt = state.phaseStartedAt;
|
|
853
|
+
if (elapsed >= prevSpec.durationMs && state.phase < 5) {
|
|
854
|
+
phase = (state.phase + 1) as Phase;
|
|
855
|
+
phaseStartedAt = nowMs;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const spec = phaseSpec(phase);
|
|
859
|
+
_nextIntervalMs = 1000 / spec.hz;
|
|
860
|
+
|
|
861
|
+
const targetIntervalMs = Math.max(1, _nextIntervalMs);
|
|
862
|
+
const lagMs = round2(clamp(tickDeltaMs - targetIntervalMs, 0, 200));
|
|
863
|
+
|
|
864
|
+
const turboMul = state.turbo ? 1.45 : 1;
|
|
865
|
+
const floodMul = state.writeFlood ? 1.8 : 1;
|
|
866
|
+
const intensity = clamp(spec.intensity * turboMul, 0, 1.8);
|
|
867
|
+
const lane = resolveLaneSize();
|
|
868
|
+
const simModel = deriveSimModel(tick, intensity, lane, state.writeFlood, state.turbo);
|
|
869
|
+
const simDrawOpsPerTick = simModel.drawOpsPerTick;
|
|
870
|
+
const simColorChurnPct = simModel.colorChurnPct;
|
|
871
|
+
const simTextChurnPct = simModel.textChurnPct;
|
|
872
|
+
const simMotionPct = simModel.motionPct;
|
|
873
|
+
|
|
874
|
+
const burnIters = spec.cpuBurnIters * turboMul * (state.writeFlood ? 1.2 : 1);
|
|
875
|
+
const lastRealCpuBurnMs = runRealCpuBurn(burnIters, tick);
|
|
876
|
+
|
|
877
|
+
const ioBlocks = spec.ioBlocks * turboMul * floodMul;
|
|
878
|
+
const ioBytes = runRealIoWrite(ioBlocks);
|
|
879
|
+
const lastRealIoWriteMBs = round2((ioBytes / (1024 * 1024)) * instantHz);
|
|
880
|
+
|
|
881
|
+
const ballastTargetMb = spec.ballastMb + (state.turbo ? 256 : 0);
|
|
882
|
+
const ballastBytes = adjustMemoryBallast(ballastTargetMb * 1024 * 1024);
|
|
883
|
+
const lastRealBallastMB = round2(ballastBytes / (1024 * 1024));
|
|
884
|
+
|
|
885
|
+
let rssBytes = state.rssBytes;
|
|
886
|
+
let heapUsedBytes = state.heapUsedBytes;
|
|
887
|
+
let processCpuPct = state.processCpuPct;
|
|
888
|
+
if (tick % MEMORY_SAMPLE_INTERVAL === 0) {
|
|
889
|
+
const mem = process.memoryUsage();
|
|
890
|
+
rssBytes = mem.rss;
|
|
891
|
+
heapUsedBytes = mem.heapUsed;
|
|
892
|
+
|
|
893
|
+
const usageNow = process.cpuUsage();
|
|
894
|
+
const cpuUsageUs =
|
|
895
|
+
usageNow.user - _lastCpuUsage.user + (usageNow.system - _lastCpuUsage.system);
|
|
896
|
+
const elapsedCpuMs = Math.max(1, nowMs - _lastCpuSampleAtMs);
|
|
897
|
+
processCpuPct = round2(clamp((cpuUsageUs / 1000 / elapsedCpuMs) * 100, 0, 999));
|
|
898
|
+
_lastCpuUsage = usageNow;
|
|
899
|
+
_lastCpuSampleAtMs = nowMs;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (
|
|
903
|
+
!_backendPerfInFlight &&
|
|
904
|
+
nowMs - _lastBackendPerfSampleMs >= BACKEND_PERF_SAMPLE_INTERVAL_MS &&
|
|
905
|
+
_backend !== null
|
|
906
|
+
) {
|
|
907
|
+
_backendPerfInFlight = true;
|
|
908
|
+
_lastBackendPerfSampleMs = nowMs;
|
|
909
|
+
void _backend.perf
|
|
910
|
+
.perfSnapshot()
|
|
911
|
+
.then((snapshot) => {
|
|
912
|
+
const phaseEventPoll = snapshot.phases.event_poll;
|
|
913
|
+
if (phaseEventPoll !== undefined) {
|
|
914
|
+
_lastBackendEventPollP95Ms = round2(phaseEventPoll.p95);
|
|
915
|
+
}
|
|
916
|
+
})
|
|
917
|
+
.catch(() => {})
|
|
918
|
+
.finally(() => {
|
|
919
|
+
_backendPerfInFlight = false;
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
let nextEventId = state.nextEventId;
|
|
924
|
+
const generated: Event[] = [];
|
|
925
|
+
const addEvent = (severity: Event["severity"], message: string): void => {
|
|
926
|
+
if (generated.length >= MAX_EVENTS) return;
|
|
927
|
+
generated.push({ id: nextEventId, at: timeStamp(), severity, message });
|
|
928
|
+
nextEventId += 1;
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
if (phase !== state.phase) {
|
|
932
|
+
addEvent(
|
|
933
|
+
"info",
|
|
934
|
+
`Phase ${phase} ${spec.name}: ${spec.hz} Hz, intensity ${(spec.intensity * 100).toFixed(0)}%.`,
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (tick % Math.max(8, Math.round(34 / Math.max(1, spec.hz))) === 0) {
|
|
939
|
+
const severity: Event["severity"] = processCpuPct > 86 || lagMs > 18 ? "warn" : "info";
|
|
940
|
+
addEvent(
|
|
941
|
+
severity,
|
|
942
|
+
`sim-draw ${simDrawOpsPerTick.toLocaleString()}/tick (${lane.width}x${lane.height}x3) · CPU ${processCpuPct.toFixed(1)}% · RSS ${formatBytes(rssBytes)} · lag ${lagMs.toFixed(2)} ms.`,
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
if (lagMs > 24) {
|
|
947
|
+
addEvent("critical", `Event-loop lag spike ${lagMs.toFixed(2)} ms.`);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
let rssGoalNotified = state.rssGoalNotified;
|
|
951
|
+
if (!rssGoalNotified && rssBytes >= 1024 * 1024 * 1024) {
|
|
952
|
+
rssGoalNotified = true;
|
|
953
|
+
addEvent("info", `RSS crossed 1 GB (${formatBytes(rssBytes)}).`);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return {
|
|
957
|
+
...state,
|
|
958
|
+
phase,
|
|
959
|
+
phaseStartedAt,
|
|
960
|
+
phaseElapsedMs: nowMs - phaseStartedAt,
|
|
961
|
+
ticks: tick,
|
|
962
|
+
nowMs,
|
|
963
|
+
totalUpdates: state.totalUpdates + 1,
|
|
964
|
+
nextEventId,
|
|
965
|
+
events: Object.freeze([...generated, ...state.events].slice(0, MAX_EVENTS)),
|
|
966
|
+
rssGoalNotified,
|
|
967
|
+
liveUpdateHz,
|
|
968
|
+
lastRenderMs: _lastRenderMs,
|
|
969
|
+
lastEventLoopLagMs: lagMs,
|
|
970
|
+
layoutRectCount: _lastLayoutRectCount,
|
|
971
|
+
backendEventPollP95Ms: _lastBackendEventPollP95Ms,
|
|
972
|
+
simDrawOpsPerTick,
|
|
973
|
+
simColorChurnPct,
|
|
974
|
+
simTextChurnPct,
|
|
975
|
+
simMotionPct,
|
|
976
|
+
lastRealCpuBurnMs,
|
|
977
|
+
lastRealIoWriteMBs,
|
|
978
|
+
lastRealBallastMB,
|
|
979
|
+
rssBytes,
|
|
980
|
+
heapUsedBytes,
|
|
981
|
+
processCpuPct,
|
|
982
|
+
throughputHistory: pushSeries(state.throughputHistory, liveUpdateHz),
|
|
983
|
+
processCpuHistory: pushSeries(state.processCpuHistory, processCpuPct),
|
|
984
|
+
rssHistory: pushSeries(state.rssHistory, round2(rssBytes / (1024 * 1024))),
|
|
985
|
+
lagHistory: pushSeries(state.lagHistory, lagMs),
|
|
986
|
+
drawHistory: pushSeries(state.drawHistory, simDrawOpsPerTick),
|
|
987
|
+
ioHistory: pushSeries(state.ioHistory, lastRealIoWriteMBs),
|
|
988
|
+
updateTimeHistory: pushSeries(state.updateTimeHistory, _lastUpdateDurationMs),
|
|
989
|
+
renderTimeHistory: pushSeries(state.renderTimeHistory, _lastRenderMs),
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// ---------------------------------------------------------------------------
|
|
994
|
+
// App setup
|
|
995
|
+
// ---------------------------------------------------------------------------
|
|
996
|
+
|
|
997
|
+
const initialNowMs = Date.now();
|
|
998
|
+
const backend = createNodeBackend({
|
|
999
|
+
fpsCap: UI_FPS_CAP,
|
|
1000
|
+
executionMode: "worker",
|
|
1001
|
+
});
|
|
1002
|
+
_backend = backend;
|
|
1003
|
+
|
|
1004
|
+
const app = createApp<State>({
|
|
1005
|
+
backend,
|
|
1006
|
+
config: {
|
|
1007
|
+
fpsCap: UI_FPS_CAP,
|
|
1008
|
+
internal_onRender: (metrics) => {
|
|
1009
|
+
_lastRenderMs = round2(metrics.renderTime);
|
|
1010
|
+
},
|
|
1011
|
+
internal_onLayout: (snapshot) => {
|
|
1012
|
+
_lastLayoutRectCount = snapshot.idRects.size;
|
|
1013
|
+
},
|
|
1014
|
+
},
|
|
1015
|
+
theme: themeCatalog.nord.theme,
|
|
1016
|
+
initialState: {
|
|
1017
|
+
phase: 1,
|
|
1018
|
+
phaseStartedAt: initialNowMs,
|
|
1019
|
+
phaseElapsedMs: 0,
|
|
1020
|
+
ticks: 0,
|
|
1021
|
+
paused: false,
|
|
1022
|
+
helpOpen: false,
|
|
1023
|
+
turbo: false,
|
|
1024
|
+
writeFlood: false,
|
|
1025
|
+
themeName: "nord",
|
|
1026
|
+
startedAt: initialNowMs,
|
|
1027
|
+
nowMs: initialNowMs,
|
|
1028
|
+
totalUpdates: 0,
|
|
1029
|
+
nextEventId: 1,
|
|
1030
|
+
events: Object.freeze([]),
|
|
1031
|
+
rssGoalNotified: false,
|
|
1032
|
+
liveUpdateHz: phaseSpec(1).hz,
|
|
1033
|
+
lastUpdateMs: 0,
|
|
1034
|
+
lastRenderMs: 0,
|
|
1035
|
+
lastEventLoopLagMs: 0,
|
|
1036
|
+
layoutRectCount: 0,
|
|
1037
|
+
backendEventPollP95Ms: 0,
|
|
1038
|
+
simDrawOpsPerTick: 0,
|
|
1039
|
+
simColorChurnPct: 0,
|
|
1040
|
+
simTextChurnPct: 0,
|
|
1041
|
+
simMotionPct: 0,
|
|
1042
|
+
lastRealCpuBurnMs: 0,
|
|
1043
|
+
lastRealIoWriteMBs: 0,
|
|
1044
|
+
lastRealBallastMB: 0,
|
|
1045
|
+
rssBytes: 0,
|
|
1046
|
+
heapUsedBytes: 0,
|
|
1047
|
+
processCpuPct: 0,
|
|
1048
|
+
throughputHistory: repeatSeries(phaseSpec(1).hz),
|
|
1049
|
+
processCpuHistory: repeatSeries(0),
|
|
1050
|
+
rssHistory: repeatSeries(0),
|
|
1051
|
+
lagHistory: repeatSeries(0),
|
|
1052
|
+
drawHistory: repeatSeries(0),
|
|
1053
|
+
ioHistory: repeatSeries(0),
|
|
1054
|
+
updateTimeHistory: repeatSeries(0),
|
|
1055
|
+
renderTimeHistory: repeatSeries(0),
|
|
1056
|
+
},
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
// ---------------------------------------------------------------------------
|
|
1060
|
+
// Actions
|
|
1061
|
+
// ---------------------------------------------------------------------------
|
|
1062
|
+
|
|
1063
|
+
function togglePauseAction(): void {
|
|
1064
|
+
app.update((s) => ({ ...s, paused: !s.paused }));
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
function toggleTurboAction(): void {
|
|
1068
|
+
app.update((s) => ({ ...s, turbo: !s.turbo }));
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function toggleWriteFloodAction(): void {
|
|
1072
|
+
app.update((s) => ({ ...s, writeFlood: !s.writeFlood }));
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function cycleThemeAction(): void {
|
|
1076
|
+
let nextTheme: ThemeName = "nord";
|
|
1077
|
+
app.update((s) => {
|
|
1078
|
+
nextTheme = nextThemeName(s.themeName);
|
|
1079
|
+
return { ...s, themeName: nextTheme };
|
|
1080
|
+
});
|
|
1081
|
+
app.setTheme(themeCatalog[nextTheme]?.theme ?? themeCatalog.nord.theme);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function openHelpAction(): void {
|
|
1085
|
+
app.update((s) => ({ ...s, helpOpen: true }));
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
function closeHelpAction(): void {
|
|
1089
|
+
app.update((s) => ({ ...s, helpOpen: false }));
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
function advancePhaseAction(): void {
|
|
1093
|
+
app.update((s) => {
|
|
1094
|
+
if (s.phase >= 5) return s;
|
|
1095
|
+
const next = (s.phase + 1) as Phase;
|
|
1096
|
+
_nextIntervalMs = 1000 / phaseSpec(next).hz;
|
|
1097
|
+
return { ...s, phase: next, phaseStartedAt: Date.now(), phaseElapsedMs: 0 };
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function retreatPhaseAction(): void {
|
|
1102
|
+
app.update((s) => {
|
|
1103
|
+
if (s.phase <= 1) return s;
|
|
1104
|
+
const next = (s.phase - 1) as Phase;
|
|
1105
|
+
_nextIntervalMs = 1000 / phaseSpec(next).hz;
|
|
1106
|
+
return { ...s, phase: next, phaseStartedAt: Date.now(), phaseElapsedMs: 0 };
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function resetAction(): void {
|
|
1111
|
+
const now = Date.now();
|
|
1112
|
+
_nextIntervalMs = 1000 / phaseSpec(1).hz;
|
|
1113
|
+
_lastCpuUsage = process.cpuUsage();
|
|
1114
|
+
_lastCpuSampleAtMs = now;
|
|
1115
|
+
_lastRenderMs = 0;
|
|
1116
|
+
_lastLayoutRectCount = 0;
|
|
1117
|
+
_lastBackendEventPollP95Ms = 0;
|
|
1118
|
+
_backendPerfInFlight = false;
|
|
1119
|
+
_lastBackendPerfSampleMs = 0;
|
|
1120
|
+
clearMemoryBallast();
|
|
1121
|
+
if (_realIoFd === null) openRealIoSink();
|
|
1122
|
+
|
|
1123
|
+
app.update((s) => ({
|
|
1124
|
+
...s,
|
|
1125
|
+
phase: 1,
|
|
1126
|
+
phaseStartedAt: now,
|
|
1127
|
+
phaseElapsedMs: 0,
|
|
1128
|
+
ticks: 0,
|
|
1129
|
+
paused: false,
|
|
1130
|
+
turbo: false,
|
|
1131
|
+
writeFlood: false,
|
|
1132
|
+
startedAt: now,
|
|
1133
|
+
nowMs: now,
|
|
1134
|
+
totalUpdates: 0,
|
|
1135
|
+
nextEventId: 1,
|
|
1136
|
+
events: Object.freeze([]),
|
|
1137
|
+
rssGoalNotified: false,
|
|
1138
|
+
liveUpdateHz: phaseSpec(1).hz,
|
|
1139
|
+
lastUpdateMs: 0,
|
|
1140
|
+
lastRenderMs: 0,
|
|
1141
|
+
lastEventLoopLagMs: 0,
|
|
1142
|
+
layoutRectCount: 0,
|
|
1143
|
+
backendEventPollP95Ms: 0,
|
|
1144
|
+
simDrawOpsPerTick: 0,
|
|
1145
|
+
simColorChurnPct: 0,
|
|
1146
|
+
simTextChurnPct: 0,
|
|
1147
|
+
simMotionPct: 0,
|
|
1148
|
+
lastRealCpuBurnMs: 0,
|
|
1149
|
+
lastRealIoWriteMBs: 0,
|
|
1150
|
+
lastRealBallastMB: 0,
|
|
1151
|
+
rssBytes: 0,
|
|
1152
|
+
heapUsedBytes: 0,
|
|
1153
|
+
processCpuPct: 0,
|
|
1154
|
+
throughputHistory: repeatSeries(phaseSpec(1).hz),
|
|
1155
|
+
processCpuHistory: repeatSeries(0),
|
|
1156
|
+
rssHistory: repeatSeries(0),
|
|
1157
|
+
lagHistory: repeatSeries(0),
|
|
1158
|
+
drawHistory: repeatSeries(0),
|
|
1159
|
+
ioHistory: repeatSeries(0),
|
|
1160
|
+
updateTimeHistory: repeatSeries(0),
|
|
1161
|
+
renderTimeHistory: repeatSeries(0),
|
|
1162
|
+
}));
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function stopAction(): void {
|
|
1166
|
+
stopTelemetryLoop();
|
|
1167
|
+
clearMemoryBallast();
|
|
1168
|
+
closeRealIoSink();
|
|
1169
|
+
void app.stop();
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// ---------------------------------------------------------------------------
|
|
1173
|
+
// View
|
|
1174
|
+
// ---------------------------------------------------------------------------
|
|
1175
|
+
|
|
1176
|
+
app.view((state) => {
|
|
1177
|
+
const viewStartMs = performance.now();
|
|
1178
|
+
|
|
1179
|
+
const spec = phaseSpec(state.phase);
|
|
1180
|
+
const themeSpec = themeCatalog[state.themeName] ?? themeCatalog.nord;
|
|
1181
|
+
const palette = themeSpec.theme.colors;
|
|
1182
|
+
|
|
1183
|
+
const rootStyle: TextStyle = { bg: palette.bg.base, fg: palette.fg.primary };
|
|
1184
|
+
const panelStyle: TextStyle = { bg: palette.bg.elevated, fg: palette.fg.primary };
|
|
1185
|
+
const metaStyle: TextStyle = { fg: palette.fg.secondary, dim: true };
|
|
1186
|
+
const quietStyle: TextStyle = { fg: palette.fg.muted, dim: true };
|
|
1187
|
+
const accentStyle: TextStyle = { fg: palette.accent.primary, bold: true };
|
|
1188
|
+
|
|
1189
|
+
const phaseProgress = clamp(state.phaseElapsedMs / Math.max(1, spec.durationMs), 0, 1);
|
|
1190
|
+
const cadencePulse = animationFrame(CADENCE_PULSE_FRAMES, state.ticks);
|
|
1191
|
+
|
|
1192
|
+
const throughputDelta = metricDelta(state.throughputHistory);
|
|
1193
|
+
const cpuDelta = metricDelta(state.processCpuHistory);
|
|
1194
|
+
const rssDelta = metricDelta(state.rssHistory);
|
|
1195
|
+
const lagDelta = metricDelta(state.lagHistory);
|
|
1196
|
+
const ioDelta = metricDelta(state.ioHistory);
|
|
1197
|
+
|
|
1198
|
+
const statusVariant = statusBadgeVariant(state.processCpuPct, state.lastEventLoopLagMs);
|
|
1199
|
+
const statusLabel =
|
|
1200
|
+
statusVariant === "error" ? "HOT" : statusVariant === "warning" ? "RISING" : "STABLE";
|
|
1201
|
+
|
|
1202
|
+
const intensity = clamp(spec.intensity * (state.turbo ? 1.45 : 1), 0, 1.8);
|
|
1203
|
+
const lane = resolveLaneSize();
|
|
1204
|
+
const laneCellCount = lane.width * lane.height * 3;
|
|
1205
|
+
const drawSparkMax = Math.max(6000, lane.width * lane.height * 5);
|
|
1206
|
+
const drawLastPerTick = state.drawHistory[state.drawHistory.length - 1] ?? 0;
|
|
1207
|
+
const drawPrevPerTick = state.drawHistory[state.drawHistory.length - 2] ?? drawLastPerTick;
|
|
1208
|
+
const hzLast = state.throughputHistory[state.throughputHistory.length - 1] ?? state.liveUpdateHz;
|
|
1209
|
+
const hzPrev = state.throughputHistory[state.throughputHistory.length - 2] ?? hzLast;
|
|
1210
|
+
const drawOpsPerSec = round2(drawLastPerTick * hzLast);
|
|
1211
|
+
const drawDeltaPerSec = drawOpsPerSec - drawPrevPerTick * hzPrev;
|
|
1212
|
+
|
|
1213
|
+
const realIoAvailable = _realIoFd !== null;
|
|
1214
|
+
const realIoSinkTypeLabel = !realIoAvailable
|
|
1215
|
+
? "no sink"
|
|
1216
|
+
: _realIoSinkIsNullDevice
|
|
1217
|
+
? "null sink"
|
|
1218
|
+
: "file sink";
|
|
1219
|
+
const realIoSinkLabel = realIoAvailable ? _realIoSinkLabel : "unavailable";
|
|
1220
|
+
|
|
1221
|
+
const rssGoalHit = state.rssBytes >= 1024 * 1024 * 1024;
|
|
1222
|
+
|
|
1223
|
+
const headerStrip = ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1224
|
+
ui.text(PRODUCT_DISPLAY_NAME, { variant: "heading" }),
|
|
1225
|
+
ui.text("|", { style: quietStyle }),
|
|
1226
|
+
ui.text("DEMO benchmark", { style: metaStyle }),
|
|
1227
|
+
ui.text("|", { style: quietStyle }),
|
|
1228
|
+
state.paused
|
|
1229
|
+
? ui.text("paused", { style: { fg: palette.warning, bold: true } })
|
|
1230
|
+
: ui.richText([
|
|
1231
|
+
{ text: cadencePulse, style: { fg: palette.accent.primary, bold: true } },
|
|
1232
|
+
{ text: " streaming", style: accentStyle },
|
|
1233
|
+
]),
|
|
1234
|
+
ui.spacer({ flex: 1 }),
|
|
1235
|
+
ui.text(`phase ${state.phase}/${PHASE_SPECS.length} ${spec.name}`, { style: accentStyle }),
|
|
1236
|
+
ui.text(`· ${spec.hz} Hz`, { style: metaStyle }),
|
|
1237
|
+
ui.text(`· ${(intensity * 100).toFixed(0)}%`, { style: metaStyle }),
|
|
1238
|
+
]);
|
|
1239
|
+
|
|
1240
|
+
const phaseStrip = ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1241
|
+
...([1, 2, 3, 4, 5] as const).map((phase) =>
|
|
1242
|
+
ui.badge(
|
|
1243
|
+
`${phase === state.phase ? "LIVE " : ""}${phase} ${fixedLabel(phaseSpec(phase).name, 9, 9)}`,
|
|
1244
|
+
{
|
|
1245
|
+
variant: phaseBadgeVariant(phase, state.phase),
|
|
1246
|
+
},
|
|
1247
|
+
),
|
|
1248
|
+
),
|
|
1249
|
+
ui.spacer({ flex: 1 }),
|
|
1250
|
+
ui.text(`turbo ${state.turbo ? "on" : "off"}`, {
|
|
1251
|
+
style: state.turbo ? accentStyle : metaStyle,
|
|
1252
|
+
}),
|
|
1253
|
+
ui.text(`write-flood ${state.writeFlood ? "on" : "off"}`, {
|
|
1254
|
+
style: state.writeFlood ? accentStyle : metaStyle,
|
|
1255
|
+
}),
|
|
1256
|
+
]);
|
|
1257
|
+
|
|
1258
|
+
const progressStrip = ui.row({ gap: 1, items: "center" }, [
|
|
1259
|
+
ui.text("ramp", { style: metaStyle }),
|
|
1260
|
+
ui.progress(state.phase === 5 ? 1 : phaseProgress, {
|
|
1261
|
+
variant: "blocks",
|
|
1262
|
+
width: 30,
|
|
1263
|
+
style: { fg: palette.accent.primary },
|
|
1264
|
+
}),
|
|
1265
|
+
ui.text(
|
|
1266
|
+
state.phase === 5
|
|
1267
|
+
? "final phase"
|
|
1268
|
+
: `${Math.round(phaseProgress * 100)}% -> phase ${state.phase + 1}`,
|
|
1269
|
+
{ style: metaStyle },
|
|
1270
|
+
),
|
|
1271
|
+
]);
|
|
1272
|
+
|
|
1273
|
+
const statusStrip = ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1274
|
+
ui.badge(statusLabel, { variant: statusVariant }),
|
|
1275
|
+
ui.text(`CPU(proc) ${state.processCpuPct.toFixed(1)}%`, { style: metaStyle }),
|
|
1276
|
+
ui.text(`lag ${state.lastEventLoopLagMs.toFixed(2)} ms`, { style: metaStyle }),
|
|
1277
|
+
ui.text(`sim-draw ${Math.round(drawOpsPerSec).toLocaleString()}/s`, { style: metaStyle }),
|
|
1278
|
+
ui.text(`RSS ${formatBytes(state.rssBytes)}`, { style: metaStyle }),
|
|
1279
|
+
ui.text(`sink ${realIoSinkTypeLabel}`, { style: metaStyle }),
|
|
1280
|
+
ui.spacer({ flex: 1 }),
|
|
1281
|
+
ui.text("keys: p +/- z w t h q", { style: quietStyle }),
|
|
1282
|
+
]);
|
|
1283
|
+
|
|
1284
|
+
const diagnosticsText = ui.box(
|
|
1285
|
+
{
|
|
1286
|
+
border: "rounded",
|
|
1287
|
+
px: PANEL_PX,
|
|
1288
|
+
py: PANEL_PY,
|
|
1289
|
+
style: panelStyle,
|
|
1290
|
+
title: "DIAGNOSTICS / BENCHMARKS",
|
|
1291
|
+
},
|
|
1292
|
+
[
|
|
1293
|
+
ui.column({ gap: 0 }, [
|
|
1294
|
+
ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1295
|
+
ui.badge("SIM", { variant: "warning" }),
|
|
1296
|
+
ui.text("deterministic visual model (draw/color/text/motion scorecard)", {
|
|
1297
|
+
style: quietStyle,
|
|
1298
|
+
}),
|
|
1299
|
+
ui.badge("REAL", { variant: "success" }),
|
|
1300
|
+
ui.text("process CPU/RSS + update/render timings + sink I/O", { style: quietStyle }),
|
|
1301
|
+
]),
|
|
1302
|
+
ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1303
|
+
ui.text(`tick ${state.liveUpdateHz.toFixed(1)} Hz (${signedDelta(throughputDelta, 1)})`, {
|
|
1304
|
+
style: metaStyle,
|
|
1305
|
+
}),
|
|
1306
|
+
ui.text(
|
|
1307
|
+
`sim-draw ${Math.round(drawOpsPerSec).toLocaleString()}/s (${signedDelta(drawDeltaPerSec, 0)})`,
|
|
1308
|
+
{ style: metaStyle },
|
|
1309
|
+
),
|
|
1310
|
+
ui.text(`cpu(proc) ${state.processCpuPct.toFixed(1)}% (${signedDelta(cpuDelta, 1)})`, {
|
|
1311
|
+
style: metaStyle,
|
|
1312
|
+
}),
|
|
1313
|
+
ui.text(
|
|
1314
|
+
`rss ${(state.rssBytes / (1024 * 1024)).toFixed(0)} MB (${signedDelta(rssDelta, 1)})`,
|
|
1315
|
+
{ style: metaStyle },
|
|
1316
|
+
),
|
|
1317
|
+
]),
|
|
1318
|
+
ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1319
|
+
ui.text(
|
|
1320
|
+
`loop-lag ${state.lastEventLoopLagMs.toFixed(2)} ms (${signedDelta(lagDelta, 2)})`,
|
|
1321
|
+
{ style: metaStyle },
|
|
1322
|
+
),
|
|
1323
|
+
ui.text(`io ${state.lastRealIoWriteMBs.toFixed(1)} MB/s (${signedDelta(ioDelta, 1)})`, {
|
|
1324
|
+
style: metaStyle,
|
|
1325
|
+
}),
|
|
1326
|
+
ui.text(`color ${state.simColorChurnPct.toFixed(0)}%`, { style: metaStyle }),
|
|
1327
|
+
ui.text(`text ${state.simTextChurnPct.toFixed(0)}%`, { style: metaStyle }),
|
|
1328
|
+
ui.text(`motion ${state.simMotionPct.toFixed(0)}%`, { style: metaStyle }),
|
|
1329
|
+
]),
|
|
1330
|
+
ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1331
|
+
ui.text("cpu(proc)", { style: quietStyle }),
|
|
1332
|
+
ui.sparkline(state.processCpuHistory, { width: 16, min: 0, max: 100 }),
|
|
1333
|
+
ui.text("rss", { style: quietStyle }),
|
|
1334
|
+
ui.sparkline(state.rssHistory, { width: 16, min: 0, max: 1400 }),
|
|
1335
|
+
ui.text("lag", { style: quietStyle }),
|
|
1336
|
+
ui.sparkline(state.lagHistory, { width: 16, min: 0, max: 40 }),
|
|
1337
|
+
ui.text("sim-draw", { style: quietStyle }),
|
|
1338
|
+
ui.sparkline(state.drawHistory, { width: 16, min: 0, max: drawSparkMax }),
|
|
1339
|
+
]),
|
|
1340
|
+
ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1341
|
+
ui.text(
|
|
1342
|
+
`model deterministic lane ${lane.width}x${lane.height} (cells ${laneCellCount})`,
|
|
1343
|
+
{ style: quietStyle },
|
|
1344
|
+
),
|
|
1345
|
+
ui.text(`update ${state.lastUpdateMs.toFixed(2)} ms`, { style: quietStyle }),
|
|
1346
|
+
ui.text(`view ${_lastViewMs.toFixed(2)} ms`, { style: quietStyle }),
|
|
1347
|
+
ui.text(`render ${state.lastRenderMs.toFixed(2)} ms`, { style: quietStyle }),
|
|
1348
|
+
ui.text(`event_poll p95 ${state.backendEventPollP95Ms.toFixed(2)} ms`, {
|
|
1349
|
+
style: quietStyle,
|
|
1350
|
+
}),
|
|
1351
|
+
ui.text(`sink ${realIoSinkLabel}`, {
|
|
1352
|
+
style: quietStyle,
|
|
1353
|
+
textOverflow: "ellipsis",
|
|
1354
|
+
maxWidth: 44,
|
|
1355
|
+
}),
|
|
1356
|
+
ui.text(rssGoalHit ? "RSS >= 1GB reached" : "RSS >= 1GB pending", {
|
|
1357
|
+
style: rssGoalHit ? { fg: palette.error, bold: true } : quietStyle,
|
|
1358
|
+
}),
|
|
1359
|
+
]),
|
|
1360
|
+
]),
|
|
1361
|
+
],
|
|
1362
|
+
);
|
|
1363
|
+
|
|
1364
|
+
const demoPanel = ui.box(
|
|
1365
|
+
{
|
|
1366
|
+
border: "rounded",
|
|
1367
|
+
px: PANEL_PX,
|
|
1368
|
+
py: PANEL_PY,
|
|
1369
|
+
flex: 4,
|
|
1370
|
+
style: panelStyle,
|
|
1371
|
+
title: "DEMO",
|
|
1372
|
+
},
|
|
1373
|
+
[
|
|
1374
|
+
ui.row({ gap: 1, items: "stretch" }, [
|
|
1375
|
+
ui.box(
|
|
1376
|
+
{
|
|
1377
|
+
border: "rounded",
|
|
1378
|
+
px: PANEL_PX,
|
|
1379
|
+
py: PANEL_PY,
|
|
1380
|
+
flex: 1,
|
|
1381
|
+
style: panelStyle,
|
|
1382
|
+
title: "Shapes / Geometry Lane",
|
|
1383
|
+
},
|
|
1384
|
+
[
|
|
1385
|
+
ui.column({ gap: 0 }, [
|
|
1386
|
+
ui.text("Rectangles, circles, grids, color waves", { style: metaStyle }),
|
|
1387
|
+
...renderGeometryLane(state.ticks, intensity, lane),
|
|
1388
|
+
]),
|
|
1389
|
+
],
|
|
1390
|
+
),
|
|
1391
|
+
ui.box(
|
|
1392
|
+
{
|
|
1393
|
+
border: "rounded",
|
|
1394
|
+
px: PANEL_PX,
|
|
1395
|
+
py: PANEL_PY,
|
|
1396
|
+
flex: 1,
|
|
1397
|
+
style: panelStyle,
|
|
1398
|
+
title: "Text / File Activity Lane",
|
|
1399
|
+
},
|
|
1400
|
+
[
|
|
1401
|
+
ui.column({ gap: 0 }, [
|
|
1402
|
+
ui.text("Typing, file churn, stream-style updates", { style: metaStyle }),
|
|
1403
|
+
...renderTextLabLane(state.ticks, intensity, lane),
|
|
1404
|
+
]),
|
|
1405
|
+
],
|
|
1406
|
+
),
|
|
1407
|
+
ui.box(
|
|
1408
|
+
{
|
|
1409
|
+
border: "rounded",
|
|
1410
|
+
px: PANEL_PX,
|
|
1411
|
+
py: PANEL_PY,
|
|
1412
|
+
flex: 1,
|
|
1413
|
+
style: panelStyle,
|
|
1414
|
+
title: "Matrix Lane",
|
|
1415
|
+
},
|
|
1416
|
+
[
|
|
1417
|
+
ui.column({ gap: 0 }, [
|
|
1418
|
+
ui.text("Matrix-style rain with varying tails", { style: metaStyle }),
|
|
1419
|
+
...renderMatrixLane(state.ticks, intensity, lane),
|
|
1420
|
+
]),
|
|
1421
|
+
],
|
|
1422
|
+
),
|
|
1423
|
+
]),
|
|
1424
|
+
],
|
|
1425
|
+
);
|
|
1426
|
+
|
|
1427
|
+
const eventsStrip = ui.box(
|
|
1428
|
+
{ border: "rounded", px: PANEL_PX, py: PANEL_PY, style: panelStyle, title: "EVENTS" },
|
|
1429
|
+
[
|
|
1430
|
+
ui.column({ gap: 0 }, [
|
|
1431
|
+
...Array.from({ length: 6 }, (_, i) => {
|
|
1432
|
+
const ev = state.events[i];
|
|
1433
|
+
if (!ev) return ui.text(" ", { key: `ev-empty-${i}`, style: quietStyle });
|
|
1434
|
+
return ui.row({ key: `ev-${ev.id}`, gap: 1, items: "center" }, [
|
|
1435
|
+
ui.icon(eventIcon(ev.severity)),
|
|
1436
|
+
ui.badge(fixedLabel(ev.severity.toUpperCase(), 8, 8), {
|
|
1437
|
+
variant: eventVariant(ev.severity),
|
|
1438
|
+
}),
|
|
1439
|
+
ui.text(`[${ev.at}] ${ev.message}`, {
|
|
1440
|
+
style: metaStyle,
|
|
1441
|
+
textOverflow: "ellipsis",
|
|
1442
|
+
maxWidth: 108,
|
|
1443
|
+
}),
|
|
1444
|
+
]);
|
|
1445
|
+
}),
|
|
1446
|
+
]),
|
|
1447
|
+
],
|
|
1448
|
+
);
|
|
1449
|
+
|
|
1450
|
+
const footer = ui.row({ gap: 1, items: "center", wrap: true }, [
|
|
1451
|
+
ui.text("keys", { style: quietStyle }),
|
|
1452
|
+
ui.text("p/space pause", { style: quietStyle }),
|
|
1453
|
+
ui.text("·", { style: quietStyle }),
|
|
1454
|
+
ui.text("+/- phase", { style: quietStyle }),
|
|
1455
|
+
ui.text("·", { style: quietStyle }),
|
|
1456
|
+
ui.text("z turbo", { style: quietStyle }),
|
|
1457
|
+
ui.text("·", { style: quietStyle }),
|
|
1458
|
+
ui.text("w write-flood", { style: quietStyle }),
|
|
1459
|
+
ui.text("·", { style: quietStyle }),
|
|
1460
|
+
ui.text("t theme", { style: quietStyle }),
|
|
1461
|
+
ui.text("·", { style: quietStyle }),
|
|
1462
|
+
ui.text("h help", { style: quietStyle }),
|
|
1463
|
+
ui.text("·", { style: quietStyle }),
|
|
1464
|
+
ui.text("q quit", { style: quietStyle }),
|
|
1465
|
+
ui.spacer({ flex: 1 }),
|
|
1466
|
+
ui.text(`updates ${state.totalUpdates}`, { style: quietStyle }),
|
|
1467
|
+
]);
|
|
1468
|
+
|
|
1469
|
+
const content = ui.column({ flex: 1, p: 1, gap: 1, items: "stretch", style: rootStyle }, [
|
|
1470
|
+
headerStrip,
|
|
1471
|
+
phaseStrip,
|
|
1472
|
+
progressStrip,
|
|
1473
|
+
statusStrip,
|
|
1474
|
+
diagnosticsText,
|
|
1475
|
+
demoPanel,
|
|
1476
|
+
eventsStrip,
|
|
1477
|
+
footer,
|
|
1478
|
+
]);
|
|
1479
|
+
|
|
1480
|
+
_lastViewMs = performance.now() - viewStartMs;
|
|
1481
|
+
|
|
1482
|
+
return ui.layers([
|
|
1483
|
+
content,
|
|
1484
|
+
state.helpOpen
|
|
1485
|
+
? ui.modal({
|
|
1486
|
+
id: "help-modal",
|
|
1487
|
+
title: `${PRODUCT_DISPLAY_NAME} Controls`,
|
|
1488
|
+
width: 66,
|
|
1489
|
+
frameStyle: {
|
|
1490
|
+
background: palette.bg.elevated,
|
|
1491
|
+
foreground: palette.fg.primary,
|
|
1492
|
+
border: palette.border.default,
|
|
1493
|
+
},
|
|
1494
|
+
backdrop: "dim",
|
|
1495
|
+
initialFocus: "help-close",
|
|
1496
|
+
returnFocusTo: "toggle-pause",
|
|
1497
|
+
content: ui.column({ gap: 1 }, [
|
|
1498
|
+
ui.text("Keyboard Controls", { style: accentStyle }),
|
|
1499
|
+
ui.divider({ char: "·" }),
|
|
1500
|
+
...HELP_SHORTCUTS.map((shortcut, i) =>
|
|
1501
|
+
ui.row({ key: `shortcut-${i}`, gap: 1, items: "center" }, [
|
|
1502
|
+
ui.kbd(shortcutLabel(shortcut.keys)),
|
|
1503
|
+
ui.text(shortcut.description, { style: metaStyle }),
|
|
1504
|
+
]),
|
|
1505
|
+
),
|
|
1506
|
+
]),
|
|
1507
|
+
actions: [
|
|
1508
|
+
ui.button({ id: "help-close", label: "Close (Esc)", onPress: closeHelpAction }),
|
|
1509
|
+
],
|
|
1510
|
+
onClose: closeHelpAction,
|
|
1511
|
+
})
|
|
1512
|
+
: null,
|
|
1513
|
+
]);
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
// ---------------------------------------------------------------------------
|
|
1517
|
+
// Telemetry loop
|
|
1518
|
+
// ---------------------------------------------------------------------------
|
|
1519
|
+
|
|
1520
|
+
let telemetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
1521
|
+
let telemetryRunning = false;
|
|
1522
|
+
let telemetryNextAt = 0;
|
|
1523
|
+
|
|
1524
|
+
function clearTelemetryTimer(): void {
|
|
1525
|
+
if (telemetryTimer === null) return;
|
|
1526
|
+
clearTimeout(telemetryTimer);
|
|
1527
|
+
telemetryTimer = null;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function scheduleTelemetryTick(nowMs = Date.now()): void {
|
|
1531
|
+
if (!telemetryRunning) return;
|
|
1532
|
+
const intervalMs = _nextIntervalMs;
|
|
1533
|
+
if (telemetryNextAt <= 0) telemetryNextAt = nowMs + intervalMs;
|
|
1534
|
+
const delayMs = Math.max(0, telemetryNextAt - nowMs);
|
|
1535
|
+
telemetryTimer = setTimeout(() => runTelemetryTick(intervalMs), delayMs);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
function runTelemetryTick(intervalMs: number): void {
|
|
1539
|
+
if (!telemetryRunning) return;
|
|
1540
|
+
const nowMs = Date.now();
|
|
1541
|
+
if (telemetryNextAt <= 0) telemetryNextAt = nowMs;
|
|
1542
|
+
|
|
1543
|
+
const maxDrift = intervalMs * 2;
|
|
1544
|
+
if (nowMs - telemetryNextAt > maxDrift) telemetryNextAt = nowMs;
|
|
1545
|
+
telemetryNextAt += intervalMs;
|
|
1546
|
+
|
|
1547
|
+
app.update((state) => {
|
|
1548
|
+
const updateStart = performance.now();
|
|
1549
|
+
const nextState = simulateTick(state, nowMs);
|
|
1550
|
+
_lastUpdateDurationMs = round2(performance.now() - updateStart);
|
|
1551
|
+
if (nextState === state) return state;
|
|
1552
|
+
return {
|
|
1553
|
+
...nextState,
|
|
1554
|
+
updateTimeHistory: pushSeries(nextState.updateTimeHistory, _lastUpdateDurationMs),
|
|
1555
|
+
lastUpdateMs: _lastUpdateDurationMs,
|
|
1556
|
+
};
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
scheduleTelemetryTick(nowMs);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function startTelemetryLoop(): void {
|
|
1563
|
+
if (telemetryRunning) return;
|
|
1564
|
+
telemetryRunning = true;
|
|
1565
|
+
telemetryNextAt = 0;
|
|
1566
|
+
scheduleTelemetryTick();
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
function stopTelemetryLoop(): void {
|
|
1570
|
+
telemetryRunning = false;
|
|
1571
|
+
telemetryNextAt = 0;
|
|
1572
|
+
clearTelemetryTimer();
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
// ---------------------------------------------------------------------------
|
|
1576
|
+
// Events + keys
|
|
1577
|
+
// ---------------------------------------------------------------------------
|
|
1578
|
+
|
|
1579
|
+
app.onEvent((ev) => {
|
|
1580
|
+
if (ev.kind === "engine") {
|
|
1581
|
+
const raw = ev.event;
|
|
1582
|
+
if (raw.kind === "resize") startTelemetryLoop();
|
|
1583
|
+
}
|
|
1584
|
+
if (ev.kind === "fatal") stopTelemetryLoop();
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
app.keys({
|
|
1588
|
+
q: stopAction,
|
|
1589
|
+
"ctrl+c": stopAction,
|
|
1590
|
+
p: togglePauseAction,
|
|
1591
|
+
space: togglePauseAction,
|
|
1592
|
+
"+": advancePhaseAction,
|
|
1593
|
+
"=": advancePhaseAction,
|
|
1594
|
+
"-": retreatPhaseAction,
|
|
1595
|
+
r: resetAction,
|
|
1596
|
+
t: cycleThemeAction,
|
|
1597
|
+
T: cycleThemeAction,
|
|
1598
|
+
"shift+t": cycleThemeAction,
|
|
1599
|
+
z: toggleTurboAction,
|
|
1600
|
+
w: toggleWriteFloodAction,
|
|
1601
|
+
h: openHelpAction,
|
|
1602
|
+
escape: closeHelpAction,
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
// ---------------------------------------------------------------------------
|
|
1606
|
+
// Start
|
|
1607
|
+
// ---------------------------------------------------------------------------
|
|
1608
|
+
|
|
1609
|
+
try {
|
|
1610
|
+
await app.start();
|
|
1611
|
+
} finally {
|
|
1612
|
+
stopTelemetryLoop();
|
|
1613
|
+
clearMemoryBallast();
|
|
1614
|
+
closeRealIoSink();
|
|
1615
|
+
}
|