@agentdeck/bridge 0.2.3 → 1.0.0
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/assets/fm-helper/agentdeck-fm-helper +0 -0
- package/dist/apme/collector.d.ts +4 -0
- package/dist/apme/collector.d.ts.map +1 -1
- package/dist/apme/collector.js +1 -0
- package/dist/apme/collector.js.map +1 -1
- package/dist/apme/dashboard-html.d.ts.map +1 -1
- package/dist/apme/dashboard-html.js +30 -0
- package/dist/apme/dashboard-html.js.map +1 -1
- package/dist/apme/http.d.ts.map +1 -1
- package/dist/apme/http.js +9 -0
- package/dist/apme/http.js.map +1 -1
- package/dist/apme/index.d.ts.map +1 -1
- package/dist/apme/index.js +14 -3
- package/dist/apme/index.js.map +1 -1
- package/dist/apme/judge-detect.d.ts +26 -0
- package/dist/apme/judge-detect.d.ts.map +1 -0
- package/dist/apme/judge-detect.js +69 -0
- package/dist/apme/judge-detect.js.map +1 -0
- package/dist/apme/runner.d.ts +7 -0
- package/dist/apme/runner.d.ts.map +1 -1
- package/dist/apme/runner.js +188 -33
- package/dist/apme/runner.js.map +1 -1
- package/dist/apme/settings.d.ts +10 -8
- package/dist/apme/settings.d.ts.map +1 -1
- package/dist/apme/settings.js +11 -16
- package/dist/apme/settings.js.map +1 -1
- package/dist/awaiting-overlay.d.ts +17 -4
- package/dist/awaiting-overlay.d.ts.map +1 -1
- package/dist/awaiting-overlay.js +39 -8
- package/dist/awaiting-overlay.js.map +1 -1
- package/dist/bridge-core.d.ts +10 -0
- package/dist/bridge-core.d.ts.map +1 -1
- package/dist/bridge-core.js +50 -0
- package/dist/bridge-core.js.map +1 -1
- package/dist/claude-permission-rules.d.ts +55 -0
- package/dist/claude-permission-rules.d.ts.map +1 -0
- package/dist/claude-permission-rules.js +197 -0
- package/dist/claude-permission-rules.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -1
- package/dist/cli.js.map +1 -1
- package/dist/d200h/hid-protocol.d.ts +62 -0
- package/dist/d200h/hid-protocol.d.ts.map +1 -0
- package/dist/d200h/hid-protocol.js +124 -0
- package/dist/d200h/hid-protocol.js.map +1 -0
- package/dist/d200h/image-renderer.d.ts +24 -0
- package/dist/d200h/image-renderer.d.ts.map +1 -0
- package/dist/d200h/image-renderer.js +321 -0
- package/dist/d200h/image-renderer.js.map +1 -0
- package/dist/daemon-server.d.ts.map +1 -1
- package/dist/daemon-server.js +374 -26
- package/dist/daemon-server.js.map +1 -1
- package/dist/daemon.js +1 -1
- package/dist/esp32-serial.d.ts +30 -0
- package/dist/esp32-serial.d.ts.map +1 -1
- package/dist/esp32-serial.js +148 -11
- package/dist/esp32-serial.js.map +1 -1
- package/dist/fallback-task-timeline.d.ts.map +1 -1
- package/dist/fallback-task-timeline.js +9 -2
- package/dist/fallback-task-timeline.js.map +1 -1
- package/dist/hook-server.d.ts.map +1 -1
- package/dist/hook-server.js +6 -5
- package/dist/hook-server.js.map +1 -1
- package/dist/idotmatrix/idotmatrix-daemon-sync.js +2 -2
- package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +3 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +6 -2
- package/dist/logger.js.map +1 -1
- package/dist/model-catalog.d.ts +1 -1
- package/dist/model-catalog.d.ts.map +1 -1
- package/dist/model-catalog.js +2 -4
- package/dist/model-catalog.js.map +1 -1
- package/dist/modules/d200h-module.d.ts +65 -0
- package/dist/modules/d200h-module.d.ts.map +1 -0
- package/dist/modules/d200h-module.js +495 -0
- package/dist/modules/d200h-module.js.map +1 -0
- package/dist/modules/d200h-renderer.d.ts +7 -0
- package/dist/modules/d200h-renderer.d.ts.map +1 -0
- package/dist/modules/d200h-renderer.js +321 -0
- package/dist/modules/d200h-renderer.js.map +1 -0
- package/dist/modules/trmnl-module.d.ts +38 -0
- package/dist/modules/trmnl-module.d.ts.map +1 -0
- package/dist/modules/trmnl-module.js +111 -0
- package/dist/modules/trmnl-module.js.map +1 -0
- package/dist/observed-steering.d.ts +93 -0
- package/dist/observed-steering.d.ts.map +1 -0
- package/dist/observed-steering.js +247 -0
- package/dist/observed-steering.js.map +1 -0
- package/dist/opencode-steering.d.ts +36 -0
- package/dist/opencode-steering.d.ts.map +1 -0
- package/dist/opencode-steering.js +84 -0
- package/dist/opencode-steering.js.map +1 -0
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +48 -10
- package/dist/output-parser.js.map +1 -1
- package/dist/passive-observer.d.ts +6 -0
- package/dist/passive-observer.d.ts.map +1 -1
- package/dist/passive-observer.js +31 -1
- package/dist/passive-observer.js.map +1 -1
- package/dist/permission-resolver.d.ts +13 -5
- package/dist/permission-resolver.d.ts.map +1 -1
- package/dist/permission-resolver.js +18 -9
- package/dist/permission-resolver.js.map +1 -1
- package/dist/pixoo/micro-glyphs.d.ts +10 -41
- package/dist/pixoo/micro-glyphs.d.ts.map +1 -1
- package/dist/pixoo/micro-glyphs.js +130 -263
- package/dist/pixoo/micro-glyphs.js.map +1 -1
- package/dist/pixoo/official-dot-glyphs.generated.d.ts +8 -0
- package/dist/pixoo/official-dot-glyphs.generated.d.ts.map +1 -0
- package/dist/pixoo/official-dot-glyphs.generated.js +247 -0
- package/dist/pixoo/official-dot-glyphs.generated.js.map +1 -0
- package/dist/pixoo/pixoo-bridge.d.ts +14 -6
- package/dist/pixoo/pixoo-bridge.d.ts.map +1 -1
- package/dist/pixoo/pixoo-bridge.js +63 -39
- package/dist/pixoo/pixoo-bridge.js.map +1 -1
- package/dist/pixoo/pixoo-renderer.d.ts +21 -2
- package/dist/pixoo/pixoo-renderer.d.ts.map +1 -1
- package/dist/pixoo/pixoo-renderer.js +401 -132
- package/dist/pixoo/pixoo-renderer.js.map +1 -1
- package/dist/pixoo/pixoo-sprites.d.ts +24 -0
- package/dist/pixoo/pixoo-sprites.d.ts.map +1 -1
- package/dist/pixoo/pixoo-sprites.js +108 -0
- package/dist/pixoo/pixoo-sprites.js.map +1 -1
- package/dist/review-runner.d.ts +113 -0
- package/dist/review-runner.d.ts.map +1 -0
- package/dist/review-runner.js +428 -0
- package/dist/review-runner.js.map +1 -0
- package/dist/session-aggregator.d.ts +8 -0
- package/dist/session-aggregator.d.ts.map +1 -1
- package/dist/session-aggregator.js.map +1 -1
- package/dist/session-focus-relay.d.ts.map +1 -1
- package/dist/session-focus-relay.js +7 -1
- package/dist/session-focus-relay.js.map +1 -1
- package/dist/session-registry.d.ts +3 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +3 -0
- package/dist/session-registry.js.map +1 -1
- package/dist/timeline-store.d.ts +49 -0
- package/dist/timeline-store.d.ts.map +1 -1
- package/dist/timeline-store.js +175 -7
- package/dist/timeline-store.js.map +1 -1
- package/dist/trmnl/byos-server.d.ts +39 -0
- package/dist/trmnl/byos-server.d.ts.map +1 -0
- package/dist/trmnl/byos-server.js +301 -0
- package/dist/trmnl/byos-server.js.map +1 -0
- package/dist/trmnl/frame-cache.d.ts +37 -0
- package/dist/trmnl/frame-cache.d.ts.map +1 -0
- package/dist/trmnl/frame-cache.js +157 -0
- package/dist/trmnl/frame-cache.js.map +1 -0
- package/dist/trmnl/image-renderer.d.ts +27 -0
- package/dist/trmnl/image-renderer.d.ts.map +1 -0
- package/dist/trmnl/image-renderer.js +235 -0
- package/dist/trmnl/image-renderer.js.map +1 -0
- package/dist/trmnl/trmnl-settings.d.ts +96 -0
- package/dist/trmnl/trmnl-settings.d.ts.map +1 -0
- package/dist/trmnl/trmnl-settings.js +177 -0
- package/dist/trmnl/trmnl-settings.js.map +1 -0
- package/dist/trmnl/trmnl-telemetry.d.ts +47 -0
- package/dist/trmnl/trmnl-telemetry.d.ts.map +1 -0
- package/dist/trmnl/trmnl-telemetry.js +56 -0
- package/dist/trmnl/trmnl-telemetry.js.map +1 -0
- package/dist/tui/dashboard.d.ts.map +1 -1
- package/dist/tui/dashboard.js +14 -4
- package/dist/tui/dashboard.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js +63 -13
- package/dist/tui/renderer.js.map +1 -1
- package/dist/tui/terrarium.d.ts.map +1 -1
- package/dist/tui/terrarium.js +7 -2
- package/dist/tui/terrarium.js.map +1 -1
- package/dist/usage-event.d.ts.map +1 -1
- package/dist/usage-event.js +25 -2
- package/dist/usage-event.js.map +1 -1
- package/dist/version-check.d.ts.map +1 -1
- package/dist/version-check.js +1 -17
- package/dist/version-check.js.map +1 -1
- package/dist/ws-server.d.ts +0 -7
- package/dist/ws-server.d.ts.map +1 -1
- package/dist/ws-server.js +5 -20
- package/dist/ws-server.js.map +1 -1
- package/package.json +4 -4
|
@@ -19,11 +19,13 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { State } from '../types.js';
|
|
21
21
|
import { PASSIVE_OFFLINE_LABEL } from '@agentdeck/shared';
|
|
22
|
-
import { hasOpenClawSession } from '@agentdeck/shared';
|
|
22
|
+
import { hasOpenClawSession, foldCodexSessionsForDisplay } from '@agentdeck/shared';
|
|
23
|
+
import { layoutOctopuses, layoutCloudCreatures, layoutOpenCodeCreatures, layoutAntigravityCreatures, } from '@agentdeck/shared';
|
|
23
24
|
import { drawTextCentered } from './pixoo-font.js';
|
|
24
|
-
import { COLORS, setPixel, blendPixel, glowPixel, lerpColor,
|
|
25
|
+
import { COLORS, setPixel, blendPixel, glowPixel, lerpColor, drawOfficialDotGlyph, drawTetra, drawText, getOctopusPaletteForSession, getJellyfishPaletteForSession, getOpenCodePaletteForSession, getAntigravityPaletteForSession, } from './pixoo-sprites.js';
|
|
25
26
|
import { blitWithCamera, quantizeCameraPixels, updateDirector, WORLD_SIZE, ACTIVE_SIZE, } from './pixoo-camera.js';
|
|
26
|
-
import { MICRO_SIZE,
|
|
27
|
+
import { MICRO_SIZE, paintTimeboxBeacon, } from './micro-glyphs.js';
|
|
28
|
+
import { OFFICIAL_DOT_GLYPHS, OFFICIAL_DOT_GLYPH_SIZE, } from './official-dot-glyphs.generated.js';
|
|
27
29
|
const W = WORLD_SIZE;
|
|
28
30
|
const ACTIVE_OFFSET = (WORLD_SIZE - ACTIVE_SIZE) / 2; // 16
|
|
29
31
|
// Track last render time for accurate dt calculation
|
|
@@ -48,25 +50,58 @@ const JELLYFISH_AGENTS = new Set(['codex-cli', 'codex-app']);
|
|
|
48
50
|
const OPENCODE_AGENTS = new Set(['opencode']);
|
|
49
51
|
/** Agent types drawn as the Antigravity peak/arc mark. */
|
|
50
52
|
const ANTIGRAVITY_AGENTS = new Set(['antigravity']);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
function stateYForType(state, creatureType) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
53
|
+
function clamp(value, min, max) {
|
|
54
|
+
return Math.min(max, Math.max(min, value));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Vertical stratification per creature type, applied on top of the band's base
|
|
58
|
+
* Y. Each type keeps its own water layer so a mixed Claude+Codex+OpenCode tank
|
|
59
|
+
* stays readable: clouds ride near the surface while working and sink to answer,
|
|
60
|
+
* octopuses hold mid-water, and everything settles to the sand when idle.
|
|
61
|
+
*
|
|
62
|
+
* Mirror of `stateY(_:kind:baseY:)` in apple/AgentDeck/Daemon/Modules/PixooRenderer.swift.
|
|
63
|
+
*/
|
|
64
|
+
function stateYForType(state, creatureType, baseY) {
|
|
65
|
+
switch (creatureType) {
|
|
66
|
+
case 'octopus':
|
|
67
|
+
if (state === 'processing')
|
|
68
|
+
return clamp(baseY, 0.40, 0.54);
|
|
69
|
+
if (state === 'awaiting')
|
|
70
|
+
return clamp(baseY - 0.04, 0.35, 0.48);
|
|
71
|
+
return 0.80;
|
|
72
|
+
case 'jellyfish':
|
|
73
|
+
if (state === 'processing')
|
|
74
|
+
return clamp(baseY, 0.16, 0.28);
|
|
75
|
+
if (state === 'awaiting')
|
|
76
|
+
return clamp(baseY + 0.26, 0.52, 0.64);
|
|
77
|
+
return clamp(baseY + 0.40, 0.80, 0.82);
|
|
78
|
+
case 'opencode':
|
|
79
|
+
if (state === 'processing')
|
|
80
|
+
return clamp(baseY - 0.02, 0.20, 0.34);
|
|
81
|
+
if (state === 'awaiting')
|
|
82
|
+
return clamp(baseY + 0.22, 0.50, 0.62);
|
|
83
|
+
return clamp(baseY + 0.36, 0.79, 0.81);
|
|
84
|
+
case 'antigravity':
|
|
85
|
+
if (state === 'processing')
|
|
86
|
+
return clamp(baseY - 0.04, 0.16, 0.30);
|
|
87
|
+
if (state === 'awaiting')
|
|
88
|
+
return clamp(baseY + 0.22, 0.46, 0.54);
|
|
89
|
+
return clamp(baseY + 0.34, 0.56, 0.64);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function slotsForType(creatureType, count) {
|
|
93
|
+
switch (creatureType) {
|
|
94
|
+
case 'octopus': return layoutOctopuses(count);
|
|
95
|
+
case 'jellyfish': return layoutCloudCreatures(count);
|
|
96
|
+
case 'opencode': return layoutOpenCodeCreatures(count);
|
|
97
|
+
case 'antigravity': return layoutAntigravityCreatures(count);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const FALLBACK_SLOT = { x: 0.38, y: 0.42, scale: 1.0 };
|
|
101
|
+
function slotAt(slots, index) {
|
|
102
|
+
if (slots.length === 0)
|
|
103
|
+
return FALLBACK_SLOT;
|
|
104
|
+
return slots[Math.min(index, slots.length - 1)];
|
|
70
105
|
}
|
|
71
106
|
/** Check if agent type gets a creature. */
|
|
72
107
|
function isCreatureAgent(agentType) {
|
|
@@ -82,11 +117,15 @@ function creatureTypeFor(agentType) {
|
|
|
82
117
|
return 'octopus';
|
|
83
118
|
}
|
|
84
119
|
function syncCreatures(sessions, stateEvent) {
|
|
85
|
-
// Determine which sessions are alive creature agents (octopus or jellyfish)
|
|
120
|
+
// Determine which sessions are alive creature agents (octopus or jellyfish).
|
|
121
|
+
// Codex rows fold by project first: each Claude Code rescue/stop-gate spawns a
|
|
122
|
+
// fresh codex thread, so without folding one workspace lights up 4-5 cloud
|
|
123
|
+
// marks at once. Octopus / opencode are NOT folded — running several of those
|
|
124
|
+
// against one project is a deliberate user pattern.
|
|
86
125
|
const aliveCoding = [];
|
|
87
126
|
if (sessions) {
|
|
88
|
-
for (const s of sessions) {
|
|
89
|
-
if (s.
|
|
127
|
+
for (const s of foldCodexSessionsForDisplay(sessions.filter(s => s.alive))) {
|
|
128
|
+
if (s.agentType && isCreatureAgent(s.agentType)) {
|
|
90
129
|
aliveCoding.push({ id: s.id, agentType: s.agentType, state: s.state ?? 'idle' });
|
|
91
130
|
}
|
|
92
131
|
}
|
|
@@ -106,22 +145,40 @@ function syncCreatures(sessions, stateEvent) {
|
|
|
106
145
|
creatureInstances.delete(id);
|
|
107
146
|
}
|
|
108
147
|
}
|
|
148
|
+
// Each agent type owns an X band and wraps to a second/third row as it fills,
|
|
149
|
+
// so a mixed tank clusters by agent instead of interleaving along one line.
|
|
150
|
+
const typeCounts = new Map();
|
|
151
|
+
for (const s of aliveCoding) {
|
|
152
|
+
const t = creatureTypeFor(s.agentType);
|
|
153
|
+
typeCounts.set(t, (typeCounts.get(t) ?? 0) + 1);
|
|
154
|
+
}
|
|
155
|
+
const slotsByType = new Map();
|
|
156
|
+
for (const [type, count] of typeCounts)
|
|
157
|
+
slotsByType.set(type, slotsForType(type, count));
|
|
158
|
+
const typeIndices = new Map();
|
|
159
|
+
// Band base Y per session, kept so the `_primary` refresh below can re-derive
|
|
160
|
+
// its stratified Y from the band instead of compounding onto an already
|
|
161
|
+
// stratified worldY.
|
|
162
|
+
const baseYById = new Map();
|
|
109
163
|
// Add/update creatures
|
|
110
164
|
for (let i = 0; i < aliveCoding.length; i++) {
|
|
111
165
|
const s = aliveCoding[i];
|
|
112
166
|
const existing = creatureInstances.get(s.id);
|
|
113
167
|
const sessionState = mapSessionState(s.state);
|
|
114
168
|
const creatureType = creatureTypeFor(s.agentType);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
169
|
+
const slotIndex = typeIndices.get(creatureType) ?? 0;
|
|
170
|
+
typeIndices.set(creatureType, slotIndex + 1);
|
|
171
|
+
const slot = slotAt(slotsByType.get(creatureType) ?? [], slotIndex);
|
|
172
|
+
const x = slot.x;
|
|
173
|
+
baseYById.set(s.id, slot.y);
|
|
174
|
+
const worldY = stateYForType(sessionState, creatureType, slot.y);
|
|
119
175
|
if (existing) {
|
|
120
176
|
existing.state = sessionState;
|
|
121
177
|
existing.agentType = s.agentType;
|
|
122
178
|
existing.creatureType = creatureType;
|
|
123
179
|
existing.worldX = x; // Update X dynamically to maintain even spacing
|
|
124
|
-
existing.worldY =
|
|
180
|
+
existing.worldY = worldY;
|
|
181
|
+
existing.sizeScale = slot.scale;
|
|
125
182
|
}
|
|
126
183
|
else {
|
|
127
184
|
creatureInstances.set(s.id, {
|
|
@@ -130,22 +187,28 @@ function syncCreatures(sessions, stateEvent) {
|
|
|
130
187
|
creatureType,
|
|
131
188
|
state: sessionState,
|
|
132
189
|
worldX: x,
|
|
133
|
-
worldY
|
|
190
|
+
worldY,
|
|
134
191
|
phaseOffset: i * 5,
|
|
192
|
+
sizeScale: slot.scale,
|
|
135
193
|
});
|
|
136
194
|
}
|
|
137
195
|
}
|
|
138
|
-
//
|
|
139
|
-
//
|
|
196
|
+
// Refresh the synthetic `_primary` creature from stateEvent — it *is* the
|
|
197
|
+
// stateEvent, so this keeps it precise before the first sessions_list lands.
|
|
198
|
+
//
|
|
199
|
+
// Real sessions are deliberately left alone. stateEvent.sessionId can name a
|
|
200
|
+
// row that folding has already collapsed into a representative, and stamping
|
|
201
|
+
// that member's state onto the representative would downgrade a group the fold
|
|
202
|
+
// resolved to `processing` down to the one idle sibling the event came from.
|
|
203
|
+
// The sessions list is daemon-pushed and already carries per-session state.
|
|
140
204
|
const aType = stateEvent?.agentType;
|
|
141
205
|
const isCreature = isCreatureAgent(aType ?? '');
|
|
142
|
-
if (stateEvent && isCreature
|
|
143
|
-
const
|
|
144
|
-
const primary = creatureInstances.get(primaryId);
|
|
206
|
+
if (stateEvent && isCreature) {
|
|
207
|
+
const primary = creatureInstances.get('_primary');
|
|
145
208
|
if (primary) {
|
|
146
209
|
const st = simplifiedState(stateEvent.state ?? State.IDLE);
|
|
147
210
|
primary.state = st;
|
|
148
|
-
primary.worldY = stateYForType(st, primary.creatureType);
|
|
211
|
+
primary.worldY = stateYForType(st, primary.creatureType, baseYById.get('_primary') ?? primary.worldY);
|
|
149
212
|
}
|
|
150
213
|
}
|
|
151
214
|
}
|
|
@@ -474,7 +537,7 @@ function simplifiedState(state) {
|
|
|
474
537
|
}
|
|
475
538
|
// ===== Usage HUD Helpers =====
|
|
476
539
|
/** Gauge bar color based on usage percentage. */
|
|
477
|
-
function gaugeColor(pct, animFrame) {
|
|
540
|
+
function gaugeColor(pct, animFrame, brand) {
|
|
478
541
|
if (pct >= 90) {
|
|
479
542
|
// Red with pulse
|
|
480
543
|
const pulse = (Math.sin(animFrame * 0.2) + 1) * 0.3;
|
|
@@ -482,9 +545,7 @@ function gaugeColor(pct, animFrame) {
|
|
|
482
545
|
}
|
|
483
546
|
if (pct >= 70)
|
|
484
547
|
return COLORS.stateAwaiting; // amber
|
|
485
|
-
|
|
486
|
-
return [0x00, 0xC8, 0xB4]; // teal
|
|
487
|
-
return COLORS.stateProcessing; // blue
|
|
548
|
+
return brand;
|
|
488
549
|
}
|
|
489
550
|
/** Pixoo HUD reset time: "1h23", "4d6", "59m". */
|
|
490
551
|
export function formatResetDetailed(resetsAt) {
|
|
@@ -508,78 +569,155 @@ export function formatResetDetailed(resetsAt) {
|
|
|
508
569
|
return `${hours}h`;
|
|
509
570
|
return `${mins}m`;
|
|
510
571
|
}
|
|
511
|
-
/** Draw Usage HUD in screen space (bottom
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
572
|
+
/** Draw Usage HUD in screen space (bottom, zoom-independent).
|
|
573
|
+
* Each provider uses the same seven-pixel band with its official creature
|
|
574
|
+
* silhouette, full-height usage fill, percentage and reset countdown. When both are present Claude occupies
|
|
575
|
+
* rows 50-56 and Codex rows 57-63; a lone provider stays on rows 57-63.
|
|
576
|
+
* Primary/5h is the left zone and secondary/7d is the right zone.
|
|
577
|
+
* When Codex only reports 7d, its subscription date occupies the otherwise
|
|
578
|
+
* empty primary zone.
|
|
515
579
|
*/
|
|
516
580
|
function drawUsageHUD(buf, usageEvent, animFrame) {
|
|
517
|
-
|
|
518
|
-
// an old "12%" frozen on-screen looks authoritative to the user even though
|
|
519
|
-
// the CLI daemon isn't live to produce a fresh fetch. Matches the collapse
|
|
520
|
-
// behavior on every other surface (macOS dashboard, plugin, Android, D200H).
|
|
521
|
-
if (!usageEvent || usageEvent.fiveHourPercent == null)
|
|
522
|
-
return;
|
|
523
|
-
if (usageEvent.usageStale === true)
|
|
581
|
+
if (!usageEvent)
|
|
524
582
|
return;
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
583
|
+
const providers = [];
|
|
584
|
+
if (usageEvent.usageStale !== true && usageEvent.fiveHourPercent != null) {
|
|
585
|
+
providers.push({
|
|
586
|
+
glyph: 'claudeCode', brand: [255, 112, 76],
|
|
587
|
+
primary: { percent: usageEvent.fiveHourPercent, resetsAt: usageEvent.fiveHourResetsAt },
|
|
588
|
+
secondary: usageEvent.sevenDayPercent == null ? undefined : {
|
|
589
|
+
percent: usageEvent.sevenDayPercent, resetsAt: usageEvent.sevenDayResetsAt,
|
|
590
|
+
},
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
// A window counts as renderable only when it is both fresh AND carries a
|
|
594
|
+
// number. "Not stale but no percent" is a malformed frame, not a zero — the
|
|
595
|
+
// gauge must stay absent rather than draw an authoritative-looking 0%.
|
|
596
|
+
// Mirror of `freshCodexWindow` in apple/AgentDeck/Daemon/Modules/PixooRenderer.swift.
|
|
597
|
+
const freshCodexWindow = (w) => w && w.stale !== true && w.usedPercent != null
|
|
598
|
+
? { percent: w.usedPercent, resetsAt: w.resetsAt }
|
|
599
|
+
: undefined;
|
|
600
|
+
const codexPrimaryWindow = freshCodexWindow(usageEvent.codexRateLimits?.primary);
|
|
601
|
+
const codexSecondaryWindow = freshCodexWindow(usageEvent.codexRateLimits?.secondary);
|
|
602
|
+
if (codexPrimaryWindow || codexSecondaryWindow) {
|
|
603
|
+
providers.push({
|
|
604
|
+
glyph: 'codex', brand: [126, 116, 255],
|
|
605
|
+
primary: codexPrimaryWindow,
|
|
606
|
+
secondary: codexSecondaryWindow,
|
|
607
|
+
subscriptionUntil: usageEvent.codexSubscriptionActiveUntil,
|
|
608
|
+
});
|
|
534
609
|
}
|
|
610
|
+
if (providers.length === 0)
|
|
611
|
+
return;
|
|
535
612
|
const timeColor = [0x60, 0x70, 0x80];
|
|
536
|
-
|
|
537
|
-
function
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
613
|
+
const firstY = providers.length > 1 ? 50 : 57;
|
|
614
|
+
function drawCreatureMarker(provider, rowY) {
|
|
615
|
+
const mask = OFFICIAL_DOT_GLYPHS[provider.glyph];
|
|
616
|
+
const sourceSize = OFFICIAL_DOT_GLYPH_SIZE;
|
|
617
|
+
// Sample the canonical square canvas instead of cropping its occupied
|
|
618
|
+
// bounds. Claude's source silhouette is intentionally wider than it is
|
|
619
|
+
// tall; cropping and stretching those bounds to 7x7 turns the robot into
|
|
620
|
+
// a square. The 9px slot leaves one physical LED on both sides.
|
|
621
|
+
const markerSize = 7;
|
|
622
|
+
const markerX = 1;
|
|
623
|
+
for (let dy = 0; dy < 7; dy++) {
|
|
624
|
+
const sy = Math.min(sourceSize - 1, Math.floor((dy + 0.5) * sourceSize / markerSize));
|
|
625
|
+
for (let dx = 0; dx < markerSize; dx++) {
|
|
626
|
+
const sx = Math.min(sourceSize - 1, Math.floor((dx + 0.5) * sourceSize / markerSize));
|
|
627
|
+
const alpha = mask[sy * sourceSize + sx];
|
|
628
|
+
if (alpha <= 16)
|
|
629
|
+
continue;
|
|
630
|
+
const intensity = alpha >= 96 ? 1 : 0.56;
|
|
631
|
+
setPixel(buf, markerX + dx, rowY + dy, lerpColor(COLORS.black, provider.brand, intensity));
|
|
545
632
|
}
|
|
546
633
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
634
|
+
}
|
|
635
|
+
function fittedReset(resetsAt, pctText, zoneWidth) {
|
|
636
|
+
const detailed = formatResetDetailed(resetsAt);
|
|
637
|
+
if (!detailed)
|
|
638
|
+
return '';
|
|
639
|
+
const maxChars = Math.floor(zoneWidth / 4);
|
|
640
|
+
if (pctText.length + detailed.length <= maxChars)
|
|
641
|
+
return detailed;
|
|
642
|
+
const unit = detailed.match(/^(\d+)[dhm]/);
|
|
643
|
+
return unit ? `${unit[1]}${unit[0].at(-1)}` : detailed.slice(0, Math.max(0, maxChars - pctText.length));
|
|
644
|
+
}
|
|
645
|
+
function subscriptionDate(until) {
|
|
646
|
+
const value = until?.trim();
|
|
647
|
+
if (!value)
|
|
648
|
+
return '';
|
|
649
|
+
const iso = value.match(/^\d{4}-(\d{1,2})-(\d{1,2})/);
|
|
650
|
+
if (iso)
|
|
651
|
+
return `${Number(iso[1])}/${Number(iso[2])}`;
|
|
652
|
+
const compact = value.match(/^~?(\d{1,2})\/(\d{1,2})$/);
|
|
653
|
+
return compact ? `${Number(compact[1])}/${Number(compact[2])}` : '';
|
|
654
|
+
}
|
|
655
|
+
function drawCenteredLabel(text, leftX, rightX, rowY) {
|
|
656
|
+
if (!text)
|
|
657
|
+
return;
|
|
658
|
+
const textWidth = text.length * 4 - 1;
|
|
659
|
+
const startX = leftX + Math.max(0, Math.floor((rightX - leftX - textWidth) / 2));
|
|
660
|
+
drawText(buf, text, startX + textWidth, rowY + 1, timeColor);
|
|
661
|
+
}
|
|
662
|
+
function renderWindow(window, leftX, rightX, rowY, brand) {
|
|
663
|
+
const pct = Math.max(0, Math.min(100, window.percent));
|
|
664
|
+
const color = gaugeColor(pct, animFrame, brand);
|
|
665
|
+
const zoneWidth = rightX - leftX;
|
|
666
|
+
const fillWidth = Math.round(zoneWidth * pct / 100);
|
|
667
|
+
// TC001-style fill: the used portion is a dim full-height color field, so
|
|
668
|
+
// the gauge reads at a glance while bright text remains crisp above it.
|
|
669
|
+
for (let y = rowY; y < rowY + 7; y++) {
|
|
670
|
+
for (let x = leftX; x < leftX + fillWidth; x++)
|
|
671
|
+
blendPixel(buf, x, y, color, 0.24);
|
|
672
|
+
}
|
|
673
|
+
const pctText = `${Math.round(pct)}%`;
|
|
674
|
+
const resetText = fittedReset(window.resetsAt, pctText, zoneWidth);
|
|
675
|
+
if (resetText) {
|
|
676
|
+
drawText(buf, resetText, rightX, rowY + 1, timeColor);
|
|
677
|
+
drawText(buf, pctText, rightX - resetText.length * 4, rowY + 1, color);
|
|
552
678
|
}
|
|
553
679
|
else {
|
|
554
|
-
drawText(buf, pctText, rightX,
|
|
680
|
+
drawText(buf, pctText, rightX, rowY + 1, color);
|
|
555
681
|
}
|
|
556
682
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
//
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
683
|
+
providers.forEach((provider, index) => {
|
|
684
|
+
const rowY = firstY + index * 7;
|
|
685
|
+
for (let y = rowY; y < rowY + 7; y++) {
|
|
686
|
+
for (let x = 0; x < 64; x++) {
|
|
687
|
+
blendPixel(buf, x, y, COLORS.black, 0.70);
|
|
688
|
+
blendPixel(buf, x, y, provider.brand, 0.08);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// A slightly denser identity dock lets the official mark join the same
|
|
692
|
+
// color band without placing quota fill behind the silhouette itself.
|
|
693
|
+
for (let y = rowY; y < rowY + 7; y++) {
|
|
694
|
+
for (let x = 0; x < 9; x++)
|
|
695
|
+
blendPixel(buf, x, y, provider.brand, 0.12);
|
|
696
|
+
}
|
|
697
|
+
drawCreatureMarker(provider, rowY);
|
|
698
|
+
if (provider.primary && provider.secondary) {
|
|
699
|
+
for (let y = rowY + 1; y < rowY + 6; y++)
|
|
700
|
+
blendPixel(buf, 36, y, provider.brand, 0.28);
|
|
701
|
+
renderWindow(provider.primary, 9, 36, rowY, provider.brand);
|
|
702
|
+
renderWindow(provider.secondary, 37, 64, rowY, provider.brand);
|
|
703
|
+
}
|
|
704
|
+
else if (!provider.primary && provider.secondary && subscriptionDate(provider.subscriptionUntil)) {
|
|
705
|
+
for (let y = rowY + 1; y < rowY + 6; y++)
|
|
706
|
+
blendPixel(buf, 36, y, provider.brand, 0.28);
|
|
707
|
+
drawCenteredLabel(subscriptionDate(provider.subscriptionUntil), 9, 36, rowY);
|
|
708
|
+
renderWindow(provider.secondary, 37, 64, rowY, provider.brand);
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
const only = provider.primary ?? provider.secondary;
|
|
712
|
+
if (only)
|
|
713
|
+
renderWindow(only, 9, 64, rowY, provider.brand);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
576
716
|
}
|
|
577
717
|
/**
|
|
578
|
-
* Render the
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
* a bold per-pixel glyph (octopus/jellyfish/opencode/crayfish) on a dark
|
|
582
|
-
* status-color field. The device fetches `size=11` for a pixel-perfect 1:1 frame.
|
|
718
|
+
* Render the Timebox Mini's native 11×11 Agent Beacon, then nearest-scale into
|
|
719
|
+
* the requested output. A stable 9×9 official mark carries identity while the
|
|
720
|
+
* one-pixel perimeter rail alone carries state and motion.
|
|
583
721
|
*/
|
|
584
722
|
function renderMicroFrame(outputBuf, size, animFrame, stateEvent, sessions, usagePct) {
|
|
585
723
|
// Presence-driven SSOT: the crayfish renders iff the daemon emitted an
|
|
@@ -597,28 +735,19 @@ function renderMicroFrame(outputBuf, size, animFrame, stateEvent, sessions, usag
|
|
|
597
735
|
: dominant?.state === 'awaiting' ? 'awaiting'
|
|
598
736
|
: (dominant?.state === 'processing' || (!dominant && routing)) ? 'processing'
|
|
599
737
|
: 'idle';
|
|
600
|
-
// Build the native 11×11 frame: dark status field + bold creature glyph.
|
|
601
738
|
const base = new Uint8Array(MICRO_SIZE * MICRO_SIZE * 3);
|
|
602
|
-
|
|
603
|
-
for (let i = 0; i < MICRO_SIZE * MICRO_SIZE; i++) {
|
|
604
|
-
base[i * 3] = bg[0];
|
|
605
|
-
base[i * 3 + 1] = bg[1];
|
|
606
|
-
base[i * 3 + 2] = bg[2];
|
|
607
|
-
}
|
|
608
|
-
const glyphState = dominant?.state === 'processing' ? 'working'
|
|
609
|
-
: dominant?.state === 'awaiting' ? 'asking'
|
|
610
|
-
: 'idle';
|
|
739
|
+
let creature = null;
|
|
611
740
|
if (dominant) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
: dominant.creatureType === '
|
|
615
|
-
: '
|
|
616
|
-
|
|
741
|
+
creature =
|
|
742
|
+
dominant.agentType === 'antigravity' ? 'antigravity'
|
|
743
|
+
: dominant.creatureType === 'jellyfish' ? 'jellyfish'
|
|
744
|
+
: dominant.creatureType === 'opencode' ? 'opencode'
|
|
745
|
+
: 'octopus';
|
|
617
746
|
}
|
|
618
747
|
else if (hasGateway) {
|
|
619
|
-
|
|
748
|
+
creature = 'crayfish';
|
|
620
749
|
}
|
|
621
|
-
|
|
750
|
+
paintTimeboxBeacon(base, creature, aggregate, animFrame);
|
|
622
751
|
// Scale the 11×11 base into the size×size output (1:1 when size === 11).
|
|
623
752
|
for (let y = 0; y < size; y++) {
|
|
624
753
|
const sy = Math.min(MICRO_SIZE - 1, Math.floor((y * MICRO_SIZE) / size));
|
|
@@ -632,12 +761,133 @@ function renderMicroFrame(outputBuf, size, animFrame, stateEvent, sessions, usag
|
|
|
632
761
|
}
|
|
633
762
|
}
|
|
634
763
|
}
|
|
764
|
+
/** Native 32×32 iDotMatrix identity stage. The panel gets saturated official
|
|
765
|
+
* marks on a quiet field plus four fixed telemetry rails; no 64px scene is
|
|
766
|
+
* downsampled, so cutouts remain physical pixels. */
|
|
767
|
+
function renderCompact32Frame(outputBuf, animFrame, stateEvent, sessions, usageEvent) {
|
|
768
|
+
const set = (x, y, color) => setPixel(outputBuf, x, y, color);
|
|
769
|
+
const state = String(stateEvent?.state ?? State.IDLE);
|
|
770
|
+
for (let y = 0; y < 28; y++) {
|
|
771
|
+
const t = y / 27;
|
|
772
|
+
const color = [2 + Math.round(5 * t), 7 + Math.round(10 * t), 18 + Math.round(18 * t)];
|
|
773
|
+
for (let x = 0; x < 32; x++)
|
|
774
|
+
set(x, y, color);
|
|
775
|
+
}
|
|
776
|
+
const surface = state.startsWith('awaiting') ? [255, 190, 45]
|
|
777
|
+
: state === 'processing' ? [50, 225, 255] : [38, 210, 145];
|
|
778
|
+
for (let x = 0; x < 32; x++) {
|
|
779
|
+
if ((x + Math.floor(animFrame / 4)) % (state === 'processing' ? 3 : 6) === 0)
|
|
780
|
+
set(x, 2, surface);
|
|
781
|
+
}
|
|
782
|
+
const glyphFor = (kind) => kind === 'jellyfish' ? 'codex' : kind === 'opencode' ? 'openCode'
|
|
783
|
+
: kind === 'antigravity' ? 'antigravity' : 'claudeCode';
|
|
784
|
+
const priority = (s) => s === 'awaiting' ? 0 : s === 'processing' ? 1 : 2;
|
|
785
|
+
const marks = [...creatureInstances.values()].map((c) => ({ glyph: glyphFor(c.creatureType), state: c.state }));
|
|
786
|
+
if (hasOpenClawSession(sessions ?? [])) {
|
|
787
|
+
const routing = sessions?.some((s) => s.agentType === 'openclaw' && s.state === 'processing') ?? false;
|
|
788
|
+
marks.push({ glyph: 'openClaw', state: routing ? 'processing' : 'idle' });
|
|
789
|
+
}
|
|
790
|
+
marks.sort((a, b) => priority(a.state) - priority(b.state));
|
|
791
|
+
marks.splice(3);
|
|
792
|
+
const slots = marks.length === 1 ? [{ x: 16, y: 14, size: 18 }]
|
|
793
|
+
: marks.length === 2 ? [{ x: 9, y: 14, size: 13 }, { x: 23, y: 14, size: 13 }]
|
|
794
|
+
: [{ x: 6, y: 14, size: 10 }, { x: 16, y: 14, size: 10 }, { x: 26, y: 14, size: 10 }];
|
|
795
|
+
const antigravityBands = [
|
|
796
|
+
[92, 214, 77], [245, 203, 36], [255, 132, 16], [255, 82, 65],
|
|
797
|
+
[183, 92, 182], [102, 111, 225], [36, 126, 255],
|
|
798
|
+
];
|
|
799
|
+
const baseColor = (glyph, sx) => {
|
|
800
|
+
if (glyph === 'claudeCode')
|
|
801
|
+
return [255, 112, 76];
|
|
802
|
+
if (glyph === 'codex')
|
|
803
|
+
return [126, 116, 255];
|
|
804
|
+
if (glyph === 'openCode')
|
|
805
|
+
return [255, 246, 248];
|
|
806
|
+
if (glyph === 'openClaw')
|
|
807
|
+
return [255, 67, 84];
|
|
808
|
+
return antigravityBands[Math.min(antigravityBands.length - 1, Math.floor(sx * antigravityBands.length / OFFICIAL_DOT_GLYPH_SIZE))];
|
|
809
|
+
};
|
|
810
|
+
marks.forEach((mark, index) => {
|
|
811
|
+
const slot = slots[index];
|
|
812
|
+
const mask = OFFICIAL_DOT_GLYPHS[mark.glyph];
|
|
813
|
+
const bob = mark.state === 'processing' ? Math.round(Math.sin((animFrame + index * 5) * 0.28)) : 0;
|
|
814
|
+
const x0 = slot.x - Math.floor(slot.size / 2);
|
|
815
|
+
const y0 = slot.y - Math.floor(slot.size / 2) + bob;
|
|
816
|
+
for (let dy = 0; dy < slot.size; dy++) {
|
|
817
|
+
const sy = Math.min(OFFICIAL_DOT_GLYPH_SIZE - 1, Math.floor(dy * OFFICIAL_DOT_GLYPH_SIZE / slot.size));
|
|
818
|
+
for (let dx = 0; dx < slot.size; dx++) {
|
|
819
|
+
const sx = Math.min(OFFICIAL_DOT_GLYPH_SIZE - 1, Math.floor(dx * OFFICIAL_DOT_GLYPH_SIZE / slot.size));
|
|
820
|
+
const alpha = mask[sy * OFFICIAL_DOT_GLYPH_SIZE + sx] / 255;
|
|
821
|
+
if (alpha <= 0.04)
|
|
822
|
+
continue;
|
|
823
|
+
const color = baseColor(mark.glyph, sx);
|
|
824
|
+
blendPixel(outputBuf, x0 + dx + 1, y0 + dy + 1, [0, 0, 0], alpha * 0.55);
|
|
825
|
+
if (alpha > 0.42) {
|
|
826
|
+
blendPixel(outputBuf, x0 + dx - 1, y0 + dy, color, 0.055);
|
|
827
|
+
blendPixel(outputBuf, x0 + dx + 1, y0 + dy, color, 0.055);
|
|
828
|
+
}
|
|
829
|
+
const coverage = Math.min(1, Math.pow(alpha, 0.72) * 1.12);
|
|
830
|
+
const light = 1.08 - dy / Math.max(1, slot.size - 1) * 0.12;
|
|
831
|
+
const lit = [
|
|
832
|
+
Math.min(255, Math.round(color[0] * light)),
|
|
833
|
+
Math.min(255, Math.round(color[1] * light)),
|
|
834
|
+
Math.min(255, Math.round(color[2] * light)),
|
|
835
|
+
];
|
|
836
|
+
blendPixel(outputBuf, x0 + dx, y0 + dy, lit, coverage);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (mark.glyph === 'openClaw') {
|
|
840
|
+
set(x0 + Math.round(9.05 / 24 * slot.size), y0 + Math.round(7.63 / 24 * slot.size), [0, 229, 204]);
|
|
841
|
+
set(x0 + Math.round(15.38 / 24 * slot.size), y0 + Math.round(7.63 / 24 * slot.size), [0, 229, 204]);
|
|
842
|
+
}
|
|
843
|
+
if (mark.state === 'processing') {
|
|
844
|
+
for (let spark = 0; spark < 3; spark++) {
|
|
845
|
+
const angle = animFrame * 0.24 + spark * Math.PI * 2 / 3;
|
|
846
|
+
const radius = slot.size / 2 + 1;
|
|
847
|
+
set(Math.round(slot.x + Math.cos(angle) * radius), Math.round(slot.y + bob + Math.sin(angle) * radius), [110, 235, 255]);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
else if (mark.state === 'awaiting') {
|
|
851
|
+
set(Math.min(31, x0 + slot.size), Math.max(2, y0), [255, 190, 45]);
|
|
852
|
+
set(Math.min(31, x0 + slot.size), Math.max(2, y0 + 1), [255, 190, 45]);
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
if (marks.length === 0) {
|
|
856
|
+
for (const [x, y] of [[14, 12], [15, 11], [16, 12], [17, 11], [18, 12], [15, 14], [16, 15], [17, 14]]) {
|
|
857
|
+
set(x, y, [76, 206, 220]);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
const primary = usageEvent?.codexRateLimits?.primary?.stale === true
|
|
861
|
+
? undefined : usageEvent?.codexRateLimits?.primary?.usedPercent;
|
|
862
|
+
const secondary = usageEvent?.codexRateLimits?.secondary?.stale === true
|
|
863
|
+
? undefined : usageEvent?.codexRateLimits?.secondary?.usedPercent;
|
|
864
|
+
const telemetry = [
|
|
865
|
+
[usageEvent?.fiveHourPercent, [42, 220, 154]],
|
|
866
|
+
[usageEvent?.sevenDayPercent, [54, 154, 255]],
|
|
867
|
+
[primary, [185, 86, 255]],
|
|
868
|
+
[secondary, [104, 116, 255]],
|
|
869
|
+
];
|
|
870
|
+
telemetry.forEach(([raw, brand], row) => {
|
|
871
|
+
const y = 28 + row;
|
|
872
|
+
for (let x = 0; x < 32; x++)
|
|
873
|
+
set(x, y, [5, 8, 14]);
|
|
874
|
+
if (raw == null)
|
|
875
|
+
return;
|
|
876
|
+
const pct = Math.max(0, Math.min(100, raw));
|
|
877
|
+
const color = pct >= 90 ? [255, 58, 72] : pct >= 70 ? [255, 183, 38] : brand;
|
|
878
|
+
set(0, y, brand);
|
|
879
|
+
set(1, y, brand);
|
|
880
|
+
const width = Math.round(pct / 100 * 29);
|
|
881
|
+
for (let x = 3; x < 3 + width; x++)
|
|
882
|
+
set(x, y, color);
|
|
883
|
+
});
|
|
884
|
+
}
|
|
635
885
|
/**
|
|
636
886
|
* Render a complete frame with camera system.
|
|
637
887
|
* Returns RGB buffer.
|
|
638
888
|
*
|
|
639
|
-
* `layout='micro'` renders
|
|
640
|
-
*
|
|
889
|
+
* `layout='micro'` renders the Timebox Mini Agent Beacon;
|
|
890
|
+
* `'standard'` is the full terrarium.
|
|
641
891
|
*/
|
|
642
892
|
export function renderFrame(stateEvent, usageEvent, sessions, timeOverrideMs, size = 64, layout = 'standard') {
|
|
643
893
|
const worldBuf = new Uint8Array(W * W * 3);
|
|
@@ -649,6 +899,11 @@ export function renderFrame(stateEvent, usageEvent, sessions, timeOverrideMs, si
|
|
|
649
899
|
renderMicroFrame(outputBuf, size, animFrame, stateEvent, sessions, usageEvent?.fiveHourPercent ?? 0);
|
|
650
900
|
return outputBuf;
|
|
651
901
|
}
|
|
902
|
+
if (size === 32) {
|
|
903
|
+
syncCreatures(sessions, stateEvent);
|
|
904
|
+
renderCompact32Frame(outputBuf, animFrame, stateEvent, sessions, usageEvent);
|
|
905
|
+
return outputBuf;
|
|
906
|
+
}
|
|
652
907
|
const state = stateEvent?.state ?? State.IDLE;
|
|
653
908
|
const usagePct = usageEvent?.fiveHourPercent ?? 0;
|
|
654
909
|
const surfaceY = SURFACE_Y;
|
|
@@ -757,23 +1012,16 @@ export function renderFrame(stateEvent, usageEvent, sessions, timeOverrideMs, si
|
|
|
757
1012
|
const spriteState = c.state === 'processing' ? 'working'
|
|
758
1013
|
: c.state === 'awaiting' ? 'asking'
|
|
759
1014
|
: 'idle'; // IDLE → idle (limbs move, body color preserved)
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
766
|
-
else if (c.creatureType === 'antigravity') {
|
|
767
|
-
drawAntigravity(outputBuf, c.worldX, c.worldY, spriteState, animFrame + c.phaseOffset, camera, getAntigravityPaletteForSession(sessionToneIndex));
|
|
768
|
-
}
|
|
769
|
-
else {
|
|
770
|
-
drawOctopus(outputBuf, c.worldX, c.worldY, spriteState, animFrame + c.phaseOffset, camera, getOctopusPaletteForSession(sessionToneIndex));
|
|
771
|
-
}
|
|
1015
|
+
const glyph = c.creatureType === 'jellyfish' ? 'codex'
|
|
1016
|
+
: c.creatureType === 'opencode' ? 'openCode'
|
|
1017
|
+
: c.creatureType === 'antigravity' ? 'antigravity'
|
|
1018
|
+
: 'claudeCode';
|
|
1019
|
+
drawOfficialDotGlyph(outputBuf, glyph, c.worldX, c.worldY, spriteState, animFrame + c.phaseOffset, camera, sessionToneIndex, c.sizeScale);
|
|
772
1020
|
}
|
|
773
1021
|
// Crayfish — always drawn when gateway available; IDLE = sitting (subtle breathing only)
|
|
774
1022
|
if (hasGateway) {
|
|
775
1023
|
const gatewayHasError = stateEvent?.gatewayHasError ?? false;
|
|
776
|
-
|
|
1024
|
+
drawOfficialDotGlyph(outputBuf, 'openClaw', cfX, cfY, crayfishRouting ? 'working' : 'idle', animFrame, camera, 0, 1.0, gatewayHasError);
|
|
777
1025
|
}
|
|
778
1026
|
// ========================================
|
|
779
1027
|
// Phase 4: Screen-space overlays
|
|
@@ -820,6 +1068,27 @@ export function renderDisconnectedFrame() {
|
|
|
820
1068
|
return buf;
|
|
821
1069
|
}
|
|
822
1070
|
// ===== Preview API (re-export camera controls) =====
|
|
1071
|
+
/**
|
|
1072
|
+
* Introspection seam: the creature set `renderFrame` would draw for this input,
|
|
1073
|
+
* without rasterizing anything. Counting marks back out of a 64×64 buffer is
|
|
1074
|
+
* far too fragile to assert on, so tests and the preview CLI read the resolved
|
|
1075
|
+
* world placement from here instead.
|
|
1076
|
+
*
|
|
1077
|
+
* This runs the same `syncCreatures` the renderer runs, so it mutates the module's
|
|
1078
|
+
* creature state exactly as a real frame would.
|
|
1079
|
+
*/
|
|
1080
|
+
export function getCreatureLayoutSnapshot(sessions, stateEvent) {
|
|
1081
|
+
syncCreatures(sessions, stateEvent);
|
|
1082
|
+
return [...creatureInstances.values()].map(c => ({
|
|
1083
|
+
sessionId: c.sessionId,
|
|
1084
|
+
agentType: c.agentType,
|
|
1085
|
+
creatureType: c.creatureType,
|
|
1086
|
+
state: c.state,
|
|
1087
|
+
worldX: c.worldX,
|
|
1088
|
+
worldY: c.worldY,
|
|
1089
|
+
sizeScale: c.sizeScale,
|
|
1090
|
+
}));
|
|
1091
|
+
}
|
|
823
1092
|
export { setZone, setOverride, resetDirector } from './pixoo-camera.js';
|
|
824
1093
|
export { ZONES } from './pixoo-camera.js';
|
|
825
1094
|
//# sourceMappingURL=pixoo-renderer.js.map
|