arbiter-ai 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/README.md +41 -0
- package/assets/jerom_16x16.png +0 -0
- package/dist/arbiter.d.ts +43 -0
- package/dist/arbiter.js +486 -0
- package/dist/context-analyzer.d.ts +15 -0
- package/dist/context-analyzer.js +603 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +165 -0
- package/dist/orchestrator.d.ts +31 -0
- package/dist/orchestrator.js +227 -0
- package/dist/router.d.ts +187 -0
- package/dist/router.js +1135 -0
- package/dist/router.test.d.ts +15 -0
- package/dist/router.test.js +95 -0
- package/dist/session-persistence.d.ts +9 -0
- package/dist/session-persistence.js +63 -0
- package/dist/session-persistence.test.d.ts +1 -0
- package/dist/session-persistence.test.js +165 -0
- package/dist/sound.d.ts +31 -0
- package/dist/sound.js +50 -0
- package/dist/state.d.ts +72 -0
- package/dist/state.js +107 -0
- package/dist/state.test.d.ts +1 -0
- package/dist/state.test.js +194 -0
- package/dist/test-headless.d.ts +1 -0
- package/dist/test-headless.js +155 -0
- package/dist/tui/index.d.ts +14 -0
- package/dist/tui/index.js +17 -0
- package/dist/tui/layout.d.ts +30 -0
- package/dist/tui/layout.js +200 -0
- package/dist/tui/render.d.ts +57 -0
- package/dist/tui/render.js +266 -0
- package/dist/tui/scene.d.ts +64 -0
- package/dist/tui/scene.js +366 -0
- package/dist/tui/screens/CharacterSelect-termkit.d.ts +18 -0
- package/dist/tui/screens/CharacterSelect-termkit.js +216 -0
- package/dist/tui/screens/ForestIntro-termkit.d.ts +15 -0
- package/dist/tui/screens/ForestIntro-termkit.js +856 -0
- package/dist/tui/screens/GitignoreCheck-termkit.d.ts +14 -0
- package/dist/tui/screens/GitignoreCheck-termkit.js +185 -0
- package/dist/tui/screens/TitleScreen-termkit.d.ts +14 -0
- package/dist/tui/screens/TitleScreen-termkit.js +132 -0
- package/dist/tui/screens/index.d.ts +9 -0
- package/dist/tui/screens/index.js +10 -0
- package/dist/tui/tileset.d.ts +97 -0
- package/dist/tui/tileset.js +237 -0
- package/dist/tui/tui-termkit.d.ts +34 -0
- package/dist/tui/tui-termkit.js +2602 -0
- package/dist/tui/types.d.ts +41 -0
- package/dist/tui/types.js +4 -0
- package/package.json +71 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tileset loading and rendering utilities for the Arbiter TUI
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for loading PNG tilesets, extracting individual tiles,
|
|
5
|
+
* compositing tiles together, and rendering them to ANSI terminal output.
|
|
6
|
+
*/
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import sharp from 'sharp';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Constants
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export const TILE_SIZE = 16;
|
|
14
|
+
export const TILES_PER_ROW = 10;
|
|
15
|
+
export const CHAR_HEIGHT = 8; // 16 pixels / 2 due to half-block rendering
|
|
16
|
+
// Get package root directory (works when installed globally or locally)
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..'); // From dist/tui/ to package root
|
|
20
|
+
const TILESET_PATH = path.join(PACKAGE_ROOT, 'assets/jerom_16x16.png');
|
|
21
|
+
// ANSI escape sequences
|
|
22
|
+
export const RESET = '\x1b[0m';
|
|
23
|
+
export const CLEAR_SCREEN = '\x1b[2J';
|
|
24
|
+
export const CURSOR_HOME = '\x1b[H';
|
|
25
|
+
export const HIDE_CURSOR = '\x1b[?25l';
|
|
26
|
+
export const SHOW_CURSOR = '\x1b[?25h';
|
|
27
|
+
// Key tile indices from the tileset
|
|
28
|
+
export const TILE = {
|
|
29
|
+
GRASS: 50,
|
|
30
|
+
GRASS_SPARSE: 51,
|
|
31
|
+
PINE_TREE: 57,
|
|
32
|
+
BARE_TREE: 58,
|
|
33
|
+
CAULDRON: 86,
|
|
34
|
+
CAMPFIRE: 87,
|
|
35
|
+
SMOKE: 90,
|
|
36
|
+
SPELLBOOK: 102,
|
|
37
|
+
HUMAN_1: 190,
|
|
38
|
+
HUMAN_2: 191,
|
|
39
|
+
HUMAN_3: 192,
|
|
40
|
+
HUMAN_4: 193,
|
|
41
|
+
HUMAN_5: 194,
|
|
42
|
+
HUMAN_6: 195,
|
|
43
|
+
HUMAN_7: 196,
|
|
44
|
+
HUMAN_8: 197,
|
|
45
|
+
ARBITER: 205,
|
|
46
|
+
DEMON_1: 220,
|
|
47
|
+
DEMON_2: 221,
|
|
48
|
+
DEMON_3: 222,
|
|
49
|
+
DEMON_4: 223,
|
|
50
|
+
DEMON_5: 224,
|
|
51
|
+
FOCUS: 270, // Focus overlay - corner brackets to highlight active speaker
|
|
52
|
+
CHAT_BUBBLE_QUARTERS: 267, // Quarter tiles - top-right is chat bubble indicator
|
|
53
|
+
ALERT_QUARTERS: 268, // Quarter tiles - top-left is exclamation/alert indicator
|
|
54
|
+
SCROLL: 124,
|
|
55
|
+
};
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Core Functions
|
|
58
|
+
// ============================================================================
|
|
59
|
+
/**
|
|
60
|
+
* Load the tileset PNG and return cached data
|
|
61
|
+
*/
|
|
62
|
+
export async function loadTileset() {
|
|
63
|
+
const image = sharp(TILESET_PATH);
|
|
64
|
+
const { data, info } = await image.raw().ensureAlpha().toBuffer({ resolveWithObject: true });
|
|
65
|
+
return { width: info.width, height: info.height, data };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get a single pixel from the tileset data buffer
|
|
69
|
+
*/
|
|
70
|
+
function getPixel(data, width, x, y) {
|
|
71
|
+
const idx = (y * width + x) * 4;
|
|
72
|
+
return { r: data[idx], g: data[idx + 1], b: data[idx + 2], a: data[idx + 3] };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Extract a 16x16 tile by index from the tileset
|
|
76
|
+
*/
|
|
77
|
+
export function extractTile(tileset, index) {
|
|
78
|
+
const tileX = (index % TILES_PER_ROW) * TILE_SIZE;
|
|
79
|
+
const tileY = Math.floor(index / TILES_PER_ROW) * TILE_SIZE;
|
|
80
|
+
const pixels = [];
|
|
81
|
+
for (let y = 0; y < TILE_SIZE; y++) {
|
|
82
|
+
const row = [];
|
|
83
|
+
for (let x = 0; x < TILE_SIZE; x++) {
|
|
84
|
+
row.push(getPixel(tileset.data, tileset.width, tileX + x, tileY + y));
|
|
85
|
+
}
|
|
86
|
+
pixels.push(row);
|
|
87
|
+
}
|
|
88
|
+
return pixels;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Composite foreground tile on background tile using alpha threshold
|
|
92
|
+
* Pixels with alpha below threshold use the background pixel
|
|
93
|
+
*/
|
|
94
|
+
export function compositeTiles(fg, bg, alphaThreshold) {
|
|
95
|
+
const size = fg.length;
|
|
96
|
+
const result = [];
|
|
97
|
+
for (let y = 0; y < size; y++) {
|
|
98
|
+
const row = [];
|
|
99
|
+
for (let x = 0; x < size; x++) {
|
|
100
|
+
const fgPx = fg[y][x];
|
|
101
|
+
const bgPx = bg[y]?.[x] || fgPx;
|
|
102
|
+
row.push(fgPx.a < alphaThreshold ? bgPx : fgPx);
|
|
103
|
+
}
|
|
104
|
+
result.push(row);
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Composite focus overlay on character tile
|
|
110
|
+
* The focus overlay has transparent center, only the corner brackets show
|
|
111
|
+
*/
|
|
112
|
+
export function compositeWithFocus(charPixels, focusPixels, alphaThreshold = 1) {
|
|
113
|
+
const size = charPixels.length;
|
|
114
|
+
const result = [];
|
|
115
|
+
for (let y = 0; y < size; y++) {
|
|
116
|
+
const row = [];
|
|
117
|
+
for (let x = 0; x < size; x++) {
|
|
118
|
+
const focusPx = focusPixels[y][x];
|
|
119
|
+
const charPx = charPixels[y][x];
|
|
120
|
+
// Focus overlay: if focus pixel is opaque, use it; otherwise use character
|
|
121
|
+
row.push(focusPx.a >= alphaThreshold ? focusPx : charPx);
|
|
122
|
+
}
|
|
123
|
+
result.push(row);
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Mirror a tile horizontally (flip left-right)
|
|
129
|
+
*/
|
|
130
|
+
export function mirrorTile(pixels) {
|
|
131
|
+
return pixels.map((row) => [...row].reverse());
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Extract an 8x8 quarter from a 16x16 tile
|
|
135
|
+
* @param pixels The full 16x16 tile pixels
|
|
136
|
+
* @param quarter Which quarter to extract
|
|
137
|
+
* @returns 8x8 pixel array
|
|
138
|
+
*/
|
|
139
|
+
export function extractQuarterTile(pixels, quarter) {
|
|
140
|
+
const halfSize = TILE_SIZE / 2; // 8
|
|
141
|
+
let startX = 0;
|
|
142
|
+
let startY = 0;
|
|
143
|
+
switch (quarter) {
|
|
144
|
+
case 'top-left':
|
|
145
|
+
startX = 0;
|
|
146
|
+
startY = 0;
|
|
147
|
+
break;
|
|
148
|
+
case 'top-right':
|
|
149
|
+
startX = halfSize;
|
|
150
|
+
startY = 0;
|
|
151
|
+
break;
|
|
152
|
+
case 'bottom-left':
|
|
153
|
+
startX = 0;
|
|
154
|
+
startY = halfSize;
|
|
155
|
+
break;
|
|
156
|
+
case 'bottom-right':
|
|
157
|
+
startX = halfSize;
|
|
158
|
+
startY = halfSize;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
const result = [];
|
|
162
|
+
for (let y = 0; y < halfSize; y++) {
|
|
163
|
+
const row = [];
|
|
164
|
+
for (let x = 0; x < halfSize; x++) {
|
|
165
|
+
row.push(pixels[startY + y][startX + x]);
|
|
166
|
+
}
|
|
167
|
+
result.push(row);
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Composite an 8x8 quarter tile onto a specific corner of a 16x16 tile
|
|
173
|
+
* @param base The full 16x16 tile pixels (will be cloned, not mutated)
|
|
174
|
+
* @param quarter The 8x8 quarter tile to overlay
|
|
175
|
+
* @param position Where to place the quarter tile
|
|
176
|
+
* @param alphaThreshold Pixels with alpha below this use the base pixel
|
|
177
|
+
* @returns New 16x16 pixel array with quarter composited
|
|
178
|
+
*/
|
|
179
|
+
export function compositeQuarterTile(base, quarter, position, alphaThreshold = 1) {
|
|
180
|
+
const halfSize = TILE_SIZE / 2; // 8
|
|
181
|
+
let startX = 0;
|
|
182
|
+
let startY = 0;
|
|
183
|
+
switch (position) {
|
|
184
|
+
case 'top-left':
|
|
185
|
+
startX = 0;
|
|
186
|
+
startY = 0;
|
|
187
|
+
break;
|
|
188
|
+
case 'top-right':
|
|
189
|
+
startX = halfSize;
|
|
190
|
+
startY = 0;
|
|
191
|
+
break;
|
|
192
|
+
case 'bottom-left':
|
|
193
|
+
startX = 0;
|
|
194
|
+
startY = halfSize;
|
|
195
|
+
break;
|
|
196
|
+
case 'bottom-right':
|
|
197
|
+
startX = halfSize;
|
|
198
|
+
startY = halfSize;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
// Clone the base tile
|
|
202
|
+
const result = base.map((row) => row.map((px) => ({ ...px })));
|
|
203
|
+
// Overlay the quarter
|
|
204
|
+
for (let y = 0; y < halfSize; y++) {
|
|
205
|
+
for (let x = 0; x < halfSize; x++) {
|
|
206
|
+
const quarterPx = quarter[y][x];
|
|
207
|
+
if (quarterPx.a >= alphaThreshold) {
|
|
208
|
+
result[startY + y][startX + x] = quarterPx;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Generate ANSI true color escape sequence
|
|
216
|
+
*/
|
|
217
|
+
function tc(r, g, b, bg) {
|
|
218
|
+
return bg ? `\x1b[48;2;${r};${g};${b}m` : `\x1b[38;2;${r};${g};${b}m`;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Render a tile to ANSI string array (16 chars wide x 8 rows)
|
|
222
|
+
* Uses half-block characters for 2:1 vertical compression
|
|
223
|
+
*/
|
|
224
|
+
export function renderTile(pixels) {
|
|
225
|
+
const lines = [];
|
|
226
|
+
for (let y = 0; y < TILE_SIZE; y += 2) {
|
|
227
|
+
let line = '';
|
|
228
|
+
for (let x = 0; x < TILE_SIZE; x++) {
|
|
229
|
+
const top = pixels[y][x];
|
|
230
|
+
const bot = pixels[y + 1]?.[x] || top;
|
|
231
|
+
line += `${tc(top.r, top.g, top.b, true) + tc(bot.r, bot.g, bot.b, false)}▄`;
|
|
232
|
+
}
|
|
233
|
+
line += RESET;
|
|
234
|
+
lines.push(line);
|
|
235
|
+
}
|
|
236
|
+
return lines;
|
|
237
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal-Kit based TUI using Strategy 5 (Minimal Redraws)
|
|
3
|
+
*
|
|
4
|
+
* This replaces the Ink-based TUI with a terminal-kit implementation
|
|
5
|
+
* that uses minimal redraws for flicker-free animation and input handling.
|
|
6
|
+
*/
|
|
7
|
+
import type { RouterCallbacks } from '../router.js';
|
|
8
|
+
import type { AppState } from '../state.js';
|
|
9
|
+
/**
|
|
10
|
+
* TUI interface - main entry point for the terminal UI
|
|
11
|
+
* Matches the interface expected by the Router
|
|
12
|
+
*/
|
|
13
|
+
export interface TUI {
|
|
14
|
+
/** Starts the TUI and takes over the terminal */
|
|
15
|
+
start(): void;
|
|
16
|
+
/** Stops the TUI and restores the terminal */
|
|
17
|
+
stop(): void;
|
|
18
|
+
/** Returns router callbacks for updating the display */
|
|
19
|
+
getRouterCallbacks(): RouterCallbacks;
|
|
20
|
+
/** Registers a callback for user input */
|
|
21
|
+
onInput(callback: (text: string) => void): void;
|
|
22
|
+
/** Registers a callback for when user confirms exit */
|
|
23
|
+
onExit(callback: () => void): void;
|
|
24
|
+
/** Registers a callback for when requirements selection is complete */
|
|
25
|
+
onRequirementsReady(callback: () => void): void;
|
|
26
|
+
/** Start the loading animation for arbiter or orchestrator */
|
|
27
|
+
startWaiting(waitingFor: 'arbiter' | 'orchestrator'): void;
|
|
28
|
+
/** Stop the loading animation */
|
|
29
|
+
stopWaiting(): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a TUI instance using terminal-kit with Strategy 5 (minimal redraws)
|
|
33
|
+
*/
|
|
34
|
+
export declare function createTUI(appState: AppState, selectedCharacter?: number): TUI;
|