@modelstatus/cli 0.1.35 → 0.1.37

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.
@@ -1,187 +1,21 @@
1
- /* Donkey Kong thin Ink wrapper. h = React.createElement (NO JSX, per ui.js).
1
+ /* RETIRED. The old Ink-overlay Donkey Kong (a React component running a useTick
2
+ * frame loop, mounted as an overlay inside ScanView) has been replaced by the
3
+ * direct-ANSI, Ink-free game:
2
4
  *
3
- * Holds the game state + a synchronously-mutated inputRef (the stale-closure-safe
4
- * pattern from useSearch / scan.js refIdxRef), runs the frame loop via useTick
5
- * GATED so it NEVER ticks at idle, and composes a fixed-height block:
6
- * row 0: scan-progress HUD (live scan fields → SweepBar) }
7
- * row 1: game stats (♥ lives · score · level · best · message) } HUD_ROWS = 2
8
- * rows 2..2+BOARD_H-1: the board (one colored <Text> per row)
9
- * last row: in-game KeyBar
10
- * Total rows = BOARD_H + 3 == the terminal `height` (boardSize math), so the
11
- * surrounding window chrome never jumps and Ink keeps diffing.
5
+ * - src/tui/game/loop.js — 60Hz fixed-timestep loop (own raw mode + alt screen)
6
+ * - src/tui/game/term.js double-buffered diff renderer (never clears mid-play)
7
+ * - src/tui/game/input.js — raw-stdin held-key model
8
+ * - src/tui/game/dk-core.js— pure sub-cell fixed-point physics engine
12
9
  *
13
- * The component owns its own useInput (gated on `active` + `gameMode`) so it is
14
- * independently mountable + testable; when embedded in ScanView the wrapper
15
- * simply passes `active` through and relies on ui.setCapturing to stop the
16
- * global app keys leaking. The SCAN is never touched we only READ the hook
17
- * fields the design surfaces (scan.filesScanned / scan.candidates.length /
18
- * scan.phase). */
19
- import React from "react";
20
- import { Box, Text, useInput } from "ink";
21
- import { h, C, LIGHT, GLYPH, KeyBar, SweepBar, useTick, cell, fmtNum } from "../ui.js";
22
- import { GAME_GLYPH, GAME_COLORS, colorize } from "./dk-render.js";
23
- import { initGame, stepGame, nextLevel, FRAME_MS, boardSize, MIN_W, MIN_H } from "./dk-core.js";
24
-
25
- const BG_ON = !(process.env.MM_ASCII === "1" || process.env.TERM === "dumb") && process.env.NO_COLOR == null;
26
-
27
- const GAME_KEYS = [
28
- { k: "←→", label: "move" },
29
- { k: "↑↓", label: "climb" },
30
- { k: "spc", label: "jump" },
31
- { k: "p", label: "pause" },
32
- { k: "r", label: "restart" },
33
- { k: "q", label: "back to scan" },
34
- ];
35
-
36
- const freshInput = () => ({ left: false, right: false, up: false, down: false, jump: false });
37
-
38
- /**
39
- * DonkeyKong overlay.
40
- * props:
41
- * - width, height: the SAME numbers ScanView gets (drive boardSize)
42
- * - scan: the useStreamingScan return (read-only — filesScanned, candidates, phase)
43
- * - ui: shell helpers ({ showToast, setCapturing, setHandlesBack })
44
- * - onExit: called when the player quits the game (q / esc / backspace)
45
- * - active: input gate (mirrors ScanView's `active`)
46
- * - level: starting level (default 1)
10
+ * Reached via `mm play` (standalone) or the Scan-tab P key, which UNMOUNTS Ink,
11
+ * runs the loop, then REMOUNTS the TUI (see src/tui/views/scan.js launchGame +
12
+ * src/tui/app.js appController). This file is kept only as a tombstone — nothing
13
+ * imports it. It intentionally exports nothing and pulls in no dependencies.
14
+ *
15
+ * This file can be deleted outright; it is retained as a no-op so an accidental
16
+ * stale import fails loudly rather than resurrecting the old flicker-prone path.
47
17
  */
48
- export function DonkeyKong({ width = 78, height = 14, scan = {}, ui = {}, onExit = () => {}, active = true, level = 1 }) {
49
- const dims = { width, height };
50
- const { BOARD_W, BOARD_H } = boardSize(width, height);
51
- const playable = BOARD_W >= MIN_W && BOARD_H >= MIN_H;
52
-
53
- const [game, setGame] = React.useState(() => initGame({ ...dims, level }));
54
- const [paused, setPaused] = React.useState(false);
55
- const pausedRef = React.useRef(false);
56
- const inputRef = React.useRef(freshInput());
57
- const highRef = React.useRef(0);
58
-
59
- // Track the running session high score (kept in a ref so it survives restarts
60
- // within this mount; shown in the HUD).
61
- if (game && game.score > highRef.current) highRef.current = game.score;
62
-
63
- const scanLive = scan.phase ? scan.phase !== "done" : true;
64
- // The loop NEVER ticks at idle: only while (a) the overlay is up [it's mounted
65
- // = up], (b) not paused, (c) the game isn't on a terminal frozen frame, and
66
- // (d) the scan is still live (or just finishing). Per the design's idle rule.
67
- const playing = game && game.status === "playing";
68
- const ticking = playable && active && !paused && playing && (scanLive || game.frame < 1);
69
- const tick = useTick(FRAME_MS, ticking);
70
-
71
- // One advance per frame; read + clear the synchronous input ref each tick so a
72
- // keypress in the same tick is seen (and isn't applied twice).
73
- React.useEffect(() => {
74
- if (!ticking) return;
75
- setGame((g) => {
76
- const ng = stepGame(g, { input: inputRef.current });
77
- return ng;
78
- });
79
- inputRef.current = freshInput();
80
- // eslint-disable-next-line react-hooks/exhaustive-deps
81
- }, [tick]);
82
-
83
- // Auto level-up a beat after a win: hold the "you saved her" frame for ~1s,
84
- // then rebuild the next level carrying score + lives.
85
- React.useEffect(() => {
86
- if (game && game.status === "won") {
87
- const t = setTimeout(() => setGame((g) => (g.status === "won" ? nextLevel(g, dims) : g)), 1000);
88
- return () => clearTimeout(t);
89
- }
90
- return undefined;
91
- }, [game && game.status, game && game.level]); // eslint-disable-line react-hooks/exhaustive-deps
92
-
93
- function exit() {
94
- ui.setCapturing?.(false);
95
- ui.setHandlesBack?.(false);
96
- onExit();
97
- }
98
-
99
- // Single input owner for the overlay. q / esc / backspace exit; r restarts;
100
- // p toggles ONLY the game loop (the scan keeps running). Movement keys set the
101
- // synchronous intent flags consumed next tick. Ctrl-C is NOT swallowed.
102
- useInput(
103
- (input, key) => {
104
- if (!active) return;
105
- if (key.ctrl && input === "c") return; // let the global handler exit the app
106
- if (input === "q" || key.escape || key.backspace || key.delete) return exit();
107
- if (input === "r") { setGame(initGame({ ...dims, level: 1 })); inputRef.current = freshInput(); return; }
108
- if (input === "p") { pausedRef.current = !pausedRef.current; setPaused(pausedRef.current); return; }
109
- const ir = inputRef.current;
110
- if (key.leftArrow || input === "h") ir.left = true;
111
- else if (key.rightArrow || input === "l") ir.right = true;
112
- else if (key.upArrow || input === "k") ir.up = true;
113
- else if (key.downArrow || input === "j") ir.down = true;
114
- else if (input === " ") ir.jump = true;
115
- },
116
- { isActive: active },
117
- );
118
-
119
- // --- too-small guard: a single line, no loop, nothing animates -----------
120
- if (!playable) {
121
- return h(
122
- Box,
123
- { flexDirection: "column" },
124
- h(Text, { color: "#d97706" }, " terminal too small for the game — resize to ~30x12, q to go back"),
125
- );
126
- }
127
-
128
- // ---- HUD row 1: live scan progress ----
129
- const files = scan.filesScanned || 0;
130
- const models = (scan.candidates && scan.candidates.length) || 0;
131
- const dirs = scan.dirsSeen || 0;
132
- const barW = Math.max(10, Math.min(22, BOARD_W - 28));
133
- // SweepBar renders a <Box>, which can't nest inside a <Text> — so the live HUD
134
- // row is a flex <Box> with the bar as a sibling of the text spans.
135
- const hud1 = scanLive
136
- ? h(
137
- Box,
138
- { flexDirection: "row" },
139
- h(Text, { color: C.FG_FAINT }, " scanning "),
140
- h(SweepBar, { tick, width: barW }),
141
- h(Text, { color: C.FG_DIM }, ` files ${fmtNum(files)} · models ${fmtNum(models)} · dirs ${fmtNum(dirs)}`),
142
- )
143
- : h(
144
- Text,
145
- {},
146
- h(Text, { color: "#16a34a" }, ` ${GLYPH.check} scan complete`),
147
- h(Text, { color: C.FG_DIM }, ` · ${fmtNum(models)} models · q to view results`),
148
- );
149
-
150
- // ---- HUD row 2: game stats + transient message ----
151
- const livesStr = "♥".repeat(Math.max(0, game.lives)) || "—";
152
- const statusTag =
153
- game.status === "over" ? ` ${game.message} · r restart · q back`
154
- : game.status === "won" ? ` ${GLYPH.spark} ${game.message}`
155
- : game.message ? ` ${game.message}` : "";
156
- const hud2 = h(
157
- Text,
158
- {},
159
- h(Text, { color: LIGHT.red }, ` ${livesStr}`),
160
- h(Text, { color: C.FG_DIM }, " score "),
161
- h(Text, { color: C.ACCENT, bold: true }, fmtNum(game.score)),
162
- h(Text, { color: C.FG_DIM }, ` lvl ${game.level}` + (paused ? " ⏸ paused" : "")),
163
- h(Text, { color: C.FG_FAINT }, ` best ${fmtNum(Math.max(highRef.current, game.score))}`),
164
- h(Text, { color: game.status === "over" ? "#dc2626" : C.FG_STRONG }, statusTag),
165
- );
166
-
167
- // ---- board: one colored <Text> per row, padded to BOARD_W ----
168
- const useColor = BG_ON;
169
- const spanRows = colorize(game, { glyph: GAME_GLYPH, colors: GAME_COLORS });
170
- const boardNodes = spanRows.map((spans, y) =>
171
- h(
172
- Text,
173
- { key: "b" + y, wrap: "truncate" },
174
- ...(useColor
175
- ? spans.map((sp, i) => h(Text, { key: i, color: sp.color }, sp.text))
176
- : [h(Text, { key: 0, color: C.FG }, spans.map((sp) => sp.text).join("").slice(0, BOARD_W).padEnd(BOARD_W))]),
177
- ),
178
- );
179
- // Defensive height pin: render EXACTLY BOARD_H board rows.
180
- while (boardNodes.length < BOARD_H) boardNodes.push(h(Text, { key: "bp" + boardNodes.length, color: C.FG }, cell("", BOARD_W)));
181
-
182
- const keybar = h(KeyBar, { keys: GAME_KEYS, width });
183
-
184
- return h(Box, { flexDirection: "column" }, hud1, hud2, ...boardNodes.slice(0, BOARD_H), keybar);
185
- }
186
-
187
- export default DonkeyKong;
18
+ throw new Error(
19
+ "DkGame.js is retired the Ink overlay game was replaced by the direct-ANSI loop " +
20
+ "(src/tui/game/loop.js). Use `mm play` or the Scan-tab P key. Do not import DkGame.js.",
21
+ );