@modelstatus/cli 0.1.38 → 0.1.40
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/package.json +1 -1
- package/src/tui/game/dk-core.js +19 -30
- package/src/tui/game/dk-render.js +33 -10
- package/src/tui/game/loop.js +42 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelstatus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"description": "Track which AI models you use, where, and never get surprised by a retirement. Free offline model-health for any repo (mm status), browser sign-in for cloud inventory + alerts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm",
|
package/src/tui/game/dk-core.js
CHANGED
|
@@ -96,10 +96,6 @@ export function cooldownForLevel(level) {
|
|
|
96
96
|
export function barrelSpeedForLevel(level) {
|
|
97
97
|
return Math.min(170, BARREL_ROLL + (level - 1) * 12);
|
|
98
98
|
}
|
|
99
|
-
/** Probability a rolling barrel takes a ladder down per eligibility check. */
|
|
100
|
-
export function ladderChanceForLevel(level) {
|
|
101
|
-
return Math.min(0.6, 0.18 + (level - 1) * 0.1);
|
|
102
|
-
}
|
|
103
99
|
/** By construction the jump apex (~3 cells) clears a 1-cell barrel + player at
|
|
104
100
|
* every level — exposed so a test can assert the invariant numerically. */
|
|
105
101
|
export function jumpClearsBarrel(/* level */) {
|
|
@@ -340,7 +336,7 @@ function stepDying(s, _dt) {
|
|
|
340
336
|
p.vy = Math.min(p.vy + GRAVITY, TERMINAL_VY);
|
|
341
337
|
p.py = clampFx(p.py + p.vy, 0, toFx(s.BOARD_H - 1));
|
|
342
338
|
syncCell(p);
|
|
343
|
-
advanceBarrels(s
|
|
339
|
+
advanceBarrels(s); // juice: hazards stay live during the death beat
|
|
344
340
|
s.statusTimer -= 1;
|
|
345
341
|
if (s.statusTimer <= 0) {
|
|
346
342
|
if (s.lives > 0) {
|
|
@@ -542,7 +538,7 @@ function stepPlaying(s, input, dt) {
|
|
|
542
538
|
}
|
|
543
539
|
|
|
544
540
|
// 6/7. BARRELS -------------------------------------------------------------
|
|
545
|
-
advanceBarrels(s
|
|
541
|
+
advanceBarrels(s);
|
|
546
542
|
|
|
547
543
|
// 8. COLLISIONS + SCORING --------------------------------------------------
|
|
548
544
|
const pCol = cell(p.px), pRow = cell(p.py);
|
|
@@ -601,10 +597,11 @@ function stepPlaying(s, input, dt) {
|
|
|
601
597
|
return finalize(s, s.rngSeed);
|
|
602
598
|
}
|
|
603
599
|
|
|
604
|
-
/** Advance every barrel one tick (roll / fall
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
600
|
+
/** Advance every barrel one tick (roll along a girder / fall to the one below) in
|
|
601
|
+
* fx. Barrels NEVER take ladders — random mid-board drops were unreadable/unfair;
|
|
602
|
+
* a barrel rolls to the wall, bounces, and falls, a predictable zig-zag cascade. */
|
|
603
|
+
function advanceBarrels(s) {
|
|
604
|
+
const { platforms, BOARD_W, BOARD_H } = s;
|
|
608
605
|
const maxPx = toFx(BOARD_W - 1);
|
|
609
606
|
const surviving = [];
|
|
610
607
|
for (const b of s.barrels) {
|
|
@@ -627,33 +624,25 @@ function advanceBarrels(s, rnd) {
|
|
|
627
624
|
b.py = nextPy;
|
|
628
625
|
}
|
|
629
626
|
} else {
|
|
630
|
-
// On a girder:
|
|
631
|
-
|
|
632
|
-
|
|
627
|
+
// On a girder: roll along it (never down a ladder). Reaching a wall bounces
|
|
628
|
+
// the barrel and drops it to the girder below.
|
|
629
|
+
const nextPx = b.px + b.vx;
|
|
630
|
+
const nc = cell(nextPx);
|
|
631
|
+
if (nextPx < 0 || nextPx > maxPx) {
|
|
632
|
+
b.vx = -b.vx; // bounce off the wall and fall to the girder below
|
|
633
633
|
b.falling = true;
|
|
634
|
-
b.onLadder = true;
|
|
635
|
-
b.px = toFx(lad.col);
|
|
636
634
|
b.vy = GRAVITY;
|
|
637
635
|
b.py = b.py + b.vy;
|
|
638
636
|
} else {
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
b.
|
|
637
|
+
const giHere = girderIndexAt(platforms, c, cell(b.py) + 1);
|
|
638
|
+
if (giHere >= 0 && nc >= 0 && nc < BOARD_W && platforms[giHere].slopeOffsets[nc] !== undefined) {
|
|
639
|
+
b.px = nextPx;
|
|
640
|
+
b.py = toFx(platforms[giHere].slopeOffsets[nc] - 1); // follow slope
|
|
641
|
+
} else {
|
|
642
|
+
b.px = nextPx; // rolled off the end → fall
|
|
643
643
|
b.falling = true;
|
|
644
644
|
b.vy = GRAVITY;
|
|
645
645
|
b.py = b.py + b.vy;
|
|
646
|
-
} else {
|
|
647
|
-
const giHere = girderIndexAt(platforms, c, cell(b.py) + 1);
|
|
648
|
-
if (giHere >= 0 && nc >= 0 && nc < BOARD_W && platforms[giHere].slopeOffsets[nc] !== undefined) {
|
|
649
|
-
b.px = nextPx;
|
|
650
|
-
b.py = toFx(platforms[giHere].slopeOffsets[nc] - 1); // follow slope
|
|
651
|
-
} else {
|
|
652
|
-
b.px = nextPx; // rolled off the end → fall
|
|
653
|
-
b.falling = true;
|
|
654
|
-
b.vy = GRAVITY;
|
|
655
|
-
b.py = b.py + b.vy;
|
|
656
|
-
}
|
|
657
646
|
}
|
|
658
647
|
}
|
|
659
648
|
}
|
|
@@ -29,16 +29,38 @@ const G_ASCII = {
|
|
|
29
29
|
};
|
|
30
30
|
export const GAME_GLYPH = ASCII ? G_ASCII : G_UNICODE;
|
|
31
31
|
|
|
32
|
+
// Animation frames cycled by state.frame / entity fields so the board feels alive:
|
|
33
|
+
// barrels spin, the player bobs while walking/climbing + opens up airborne, the
|
|
34
|
+
// princess pulses a heart. ASCII variants stay pure-ASCII for the MM_ASCII path.
|
|
35
|
+
const PLAYER_FRAMES = ASCII ? ["P", "p"] : ["☻", "☺"];
|
|
36
|
+
const BARREL_FRAMES = ASCII ? ["o", "O", "0"] : ["◐", "◓", "◑", "◒"];
|
|
37
|
+
const PRINCESS_FRAMES = ASCII ? ["V", "v"] : ["♥", "♡"];
|
|
38
|
+
export const GAME_FRAMES = {
|
|
39
|
+
player: PLAYER_FRAMES, barrel: BARREL_FRAMES, princess: PRINCESS_FRAMES,
|
|
40
|
+
dk: [GAME_GLYPH.dk], girder: [GAME_GLYPH.girder], ladder: [GAME_GLYPH.ladder],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Animated player glyph from motion state + the global frame counter. */
|
|
44
|
+
function playerFrame(state) {
|
|
45
|
+
const p = state.jumpman || state.player;
|
|
46
|
+
const f = state.frame || 0;
|
|
47
|
+
if (!p) return PLAYER_FRAMES[0];
|
|
48
|
+
if (p.onLadder) return PLAYER_FRAMES[(f >> 2) & 1]; // climb bob
|
|
49
|
+
if (!p.onGround) return PLAYER_FRAMES[1]; // airborne
|
|
50
|
+
if (Math.abs(p.vx || 0) > 30) return PLAYER_FRAMES[(f >> 2) & 1]; // walk bob
|
|
51
|
+
return PLAYER_FRAMES[0]; // standing
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
// Entity color keys (resolved against the C palette by the wrapper; kept here as
|
|
33
55
|
// literal hex so the renderer stays dependency-free + the colorize output is
|
|
34
56
|
// directly usable by tests without importing the design system).
|
|
35
57
|
export const GAME_COLORS = {
|
|
36
|
-
jumpman: "#
|
|
37
|
-
barrel: "#
|
|
38
|
-
girder: "#
|
|
39
|
-
ladder: "#
|
|
40
|
-
dk: "#
|
|
41
|
-
princess: "#
|
|
58
|
+
jumpman: "#ef4444", // Mario red — pops off the board
|
|
59
|
+
barrel: "#f97316", // bright orange
|
|
60
|
+
girder: "#e0698c", // classic DK rose girders
|
|
61
|
+
ladder: "#38bdf8", // sky-blue ladders (cool contrast vs the warm board)
|
|
62
|
+
dk: "#c2630a", // gorilla amber-brown
|
|
63
|
+
princess: "#f472b6", // pink
|
|
42
64
|
empty: "#5b6673", // C.FG_FAINT
|
|
43
65
|
};
|
|
44
66
|
|
|
@@ -67,11 +89,12 @@ function buildGrid(state, glyph) {
|
|
|
67
89
|
for (const l of ladders) {
|
|
68
90
|
for (let y = l.yTop; y <= l.yBottom; y++) { put(l.col, y, glyph.ladder, "ladder"); put(l.col + 1, y, glyph.ladder, "ladder"); }
|
|
69
91
|
}
|
|
70
|
-
// goal + actors
|
|
71
|
-
|
|
92
|
+
// goal + actors (animated: princess pulse, barrel spin, player bob)
|
|
93
|
+
const f = state.frame || 0;
|
|
94
|
+
put(princess.x, princess.y, PRINCESS_FRAMES[(f >> 4) & 1], "princess");
|
|
72
95
|
put(donkeyKong.x, donkeyKong.y, glyph.dk, "dk");
|
|
73
|
-
for (const b of barrels) put(b.x, b.y,
|
|
74
|
-
put(jumpman.x, jumpman.y,
|
|
96
|
+
for (const b of barrels) put(b.x, b.y, BARREL_FRAMES[(b.spin >> 1) % BARREL_FRAMES.length], "barrel");
|
|
97
|
+
put(jumpman.x, jumpman.y, playerFrame(state), "jumpman");
|
|
75
98
|
return grid;
|
|
76
99
|
}
|
|
77
100
|
|
package/src/tui/game/loop.js
CHANGED
|
@@ -51,8 +51,14 @@ const COL = {
|
|
|
51
51
|
green: packHex("#16a34a"),
|
|
52
52
|
amber: packHex("#d97706"),
|
|
53
53
|
violet: packHex("#a78bfa"),
|
|
54
|
+
pink: packHex("#f472b6"),
|
|
55
|
+
gold: packHex("#fbbf24"),
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
// Win-celebration sparkles + a color-cycle palette (the flare on a level clear).
|
|
59
|
+
const WIN_SPARK = ASCII ? ["*", "+", ".", "'", "o"] : ["✦", "✧", "✺", "·", "♥"];
|
|
60
|
+
const WIN_PAL = [COL.pink, COL.gold, COL.accent, COL.green, COL.red, COL.violet];
|
|
61
|
+
|
|
56
62
|
const fmtNum = (n) => (n == null ? "0" : Number(n).toLocaleString("en-US"));
|
|
57
63
|
|
|
58
64
|
/**
|
|
@@ -213,7 +219,9 @@ export function runGame({ width, height, level = 1, scanStore = null, onExit, _i
|
|
|
213
219
|
bb.blit(cells, 0, HUD_ROWS, cells.width, cells.height);
|
|
214
220
|
// Respawn blink: hide the player glyph on alternate frames during invuln.
|
|
215
221
|
maybeBlinkPlayer(bb, game, curW);
|
|
216
|
-
|
|
222
|
+
// Level-clear gets the full celebration; everything else the plain banner.
|
|
223
|
+
if (game.status === "levelclear") drawWinFlare(bb, game, curW, curH);
|
|
224
|
+
else drawBanner(bb, game, curW, curH);
|
|
217
225
|
drawKeybar(bb, curW, HUD_ROWS + curH);
|
|
218
226
|
const s = bb.render();
|
|
219
227
|
if (s) term.write(s);
|
|
@@ -323,6 +331,36 @@ function drawBanner(bb, game, w, boardH) {
|
|
|
323
331
|
}
|
|
324
332
|
}
|
|
325
333
|
|
|
334
|
+
/** Win celebration: a drifting confetti field + a color-cycling heart banner +
|
|
335
|
+
* the bonus tally. Drawn over the board during the levelclear window (the loop is
|
|
336
|
+
* actively ticking, so animating here is fine — it's not idle). All deterministic
|
|
337
|
+
* off game.frame (no RNG), so it's stable + cheap (the diff renderer only emits
|
|
338
|
+
* the cells that changed). */
|
|
339
|
+
function drawWinFlare(bb, game, w, boardH) {
|
|
340
|
+
const f = game.frame || 0;
|
|
341
|
+
// Confetti: a deterministic, gently-falling sparkle field across the board.
|
|
342
|
+
const count = Math.min(48, Math.max(16, Math.floor((w * boardH) / 10)));
|
|
343
|
+
for (let i = 0; i < count; i++) {
|
|
344
|
+
const col = (i * 17 + (i % 5)) % w;
|
|
345
|
+
const row = (i * 7 + (f >> 1)) % boardH; // drifts downward each frame
|
|
346
|
+
const ch = WIN_SPARK[(i + (f >> 2)) % WIN_SPARK.length];
|
|
347
|
+
const c = WIN_PAL[(i + (f >> 3)) % WIN_PAL.length];
|
|
348
|
+
bb.setCell(col, HUD_ROWS + row, ch.charCodeAt(0), c);
|
|
349
|
+
}
|
|
350
|
+
// Flashing centered banner with hearts.
|
|
351
|
+
const text = `${G.spark} ${G.heart} YOU SAVED HER! ${G.heart} ${G.spark}`;
|
|
352
|
+
const brow = HUD_ROWS + Math.floor(boardH / 2);
|
|
353
|
+
const bx = Math.max(0, Math.floor((w - text.length) / 2));
|
|
354
|
+
for (let cx = 0; cx < w; cx++) bb.setCell(cx, brow, 32, -1); // clear row for legibility
|
|
355
|
+
bb.setText(bx, brow, clip(text, w - bx), WIN_PAL[(f >> 2) % WIN_PAL.length]);
|
|
356
|
+
// Bonus tally sub-line.
|
|
357
|
+
const sub = `+100 bonus ${fmtNum(game.bonus || 0)} ${ASCII ? "->" : "→"} next level`;
|
|
358
|
+
const sy = Math.min(HUD_ROWS + boardH - 1, brow + 1);
|
|
359
|
+
const sx = Math.max(0, Math.floor((w - sub.length) / 2));
|
|
360
|
+
for (let cx = 0; cx < w; cx++) bb.setCell(cx, sy, 32, -1);
|
|
361
|
+
bb.setText(sx, sy, clip(sub, w - sx), COL.strong);
|
|
362
|
+
}
|
|
363
|
+
|
|
326
364
|
/** Bottom row: in-game keybar. */
|
|
327
365
|
function drawKeybar(bb, w, y) {
|
|
328
366
|
const bar = ASCII
|
|
@@ -335,3 +373,6 @@ function clip(s, w) {
|
|
|
335
373
|
if (w <= 0) return "";
|
|
336
374
|
return s.length > w ? s.slice(0, w) : s;
|
|
337
375
|
}
|
|
376
|
+
|
|
377
|
+
// Exposed for focused render tests (the win celebration + banners).
|
|
378
|
+
export const _internals = { drawWinFlare, drawBanner, drawStats, HUD_ROWS, KEY_ROW };
|