@jcendal/opencode-digital-pet 0.2.2 → 0.3.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/CHANGELOG.md +14 -0
- package/dist/digital-pet-animation/sequences/evolution-artwork.d.ts +1 -1
- package/dist/digital-pet-animation/sequences/evolution-battle-artwork.d.ts +13 -1
- package/dist/digital-pet-animation/sessions/evolution-battle-session.d.ts +4 -1
- package/dist/digital-pet-animation/sessions/evolution-reveal-session.d.ts +2 -0
- package/dist/digital-pet-animation/sessions/presentation-state.d.ts +16 -0
- package/dist/tui/sidebar-card.d.ts +2 -0
- package/dist/tui.js +52 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-10-06
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Feat/cursor pet display
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [0.2.3] - 2026-10-06
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- chore(digital-pet-core): mark stages for a release probe
|
|
22
|
+
|
|
23
|
+
|
|
10
24
|
## [0.2.2] - 2026-10-06
|
|
11
25
|
|
|
12
26
|
### Changed
|
|
@@ -5,4 +5,4 @@ export type EvolutionArtworkScene = {
|
|
|
5
5
|
readonly progress: number;
|
|
6
6
|
};
|
|
7
7
|
export declare const renderEvolutionArtwork: (catalog: MonsterFrameCatalog, fromSprite: string, toSprite: string, scene: EvolutionArtworkScene, viewportWidth: number, tick?: number) => string;
|
|
8
|
-
export declare const runEvolutionAnimation: (catalog: MonsterFrameCatalog, fromSprite: string, toSprite: string, viewportWidth: number, onFrame: (artwork: string) => Promise<void>) => Promise<void>;
|
|
8
|
+
export declare const runEvolutionAnimation: (catalog: MonsterFrameCatalog, fromSprite: string, toSprite: string, viewportWidth: number, onFrame: (artwork: string) => Promise<void>, onReveal?: () => Promise<void>) => Promise<void>;
|
|
@@ -8,6 +8,18 @@ export type EvolutionBattleShooter = "player" | "opponent";
|
|
|
8
8
|
export type EvolutionBattleOutcome = EvolutionBattleShooter;
|
|
9
9
|
export type BattleSpritePose = "attack" | "injured" | "happy" | "idle";
|
|
10
10
|
export type EvolutionBattleResultLabel = "hit" | "miss" | null;
|
|
11
|
+
/** Scores and caption accompanying the shared frame; frontends choose their HUD style. */
|
|
12
|
+
export type BattleFrameHud = {
|
|
13
|
+
readonly playerHits: number;
|
|
14
|
+
readonly opponentHits: number;
|
|
15
|
+
readonly hitsToWin: number;
|
|
16
|
+
readonly caption: string | null;
|
|
17
|
+
readonly gapStartColumn: number;
|
|
18
|
+
readonly gapColumns: number;
|
|
19
|
+
readonly scoreRow: number;
|
|
20
|
+
readonly captionRow: number;
|
|
21
|
+
};
|
|
22
|
+
export type BattleFrameListener = (artwork: string, hud?: BattleFrameHud) => Promise<void>;
|
|
11
23
|
export type EvolutionBattleShot = {
|
|
12
24
|
readonly shooter: EvolutionBattleShooter;
|
|
13
25
|
readonly hit: boolean;
|
|
@@ -30,4 +42,4 @@ export declare const planEvolutionBattle: (outcome: EvolutionBattleOutcome, rand
|
|
|
30
42
|
export declare const defaultBattleScene: (overrides?: Partial<EvolutionBattleScene>) => EvolutionBattleScene;
|
|
31
43
|
export declare const renderBattleIntroArtwork: (viewportWidth: number, _progress: number, tick?: number) => string;
|
|
32
44
|
export declare const renderEvolutionBattleArtwork: (catalog: MonsterFrameCatalog, playerSprite: string, opponentSprite: string, scene: EvolutionBattleScene, viewportWidth: number) => string;
|
|
33
|
-
export declare const runEvolutionBattleAnimation: (catalog: MonsterFrameCatalog, playerSprite: string, opponentSprite: string, viewportWidth: number, outcome: EvolutionBattleOutcome, onFrame:
|
|
45
|
+
export declare const runEvolutionBattleAnimation: (catalog: MonsterFrameCatalog, playerSprite: string, opponentSprite: string, viewportWidth: number, outcome: EvolutionBattleOutcome, onFrame: BattleFrameListener, random?: () => number) => Promise<EvolutionBattleOutcome>;
|
|
@@ -3,12 +3,15 @@ import type { SidebarSnapshot } from "../../digital-pet-core/application/ports/s
|
|
|
3
3
|
import { type EvolutionBattleRepository } from "../../digital-pet-core/application/use-cases/resolve-evolution-battle.js";
|
|
4
4
|
import type { DigimonCatalog } from "../../digital-pet-core/data/catalog.js";
|
|
5
5
|
import type { MonsterFrameCatalog } from "../../digital-pet-core/data/monster-frame-catalog.js";
|
|
6
|
+
import { type BattleFrameListener } from "../sequences/evolution-battle-artwork.js";
|
|
7
|
+
import type { PresentationStateListener } from "./presentation-state.js";
|
|
6
8
|
export type EvolutionBattleSessionDependencies = {
|
|
7
9
|
readonly frameCatalog: MonsterFrameCatalog;
|
|
8
10
|
readonly digimonCatalog: DigimonCatalog;
|
|
9
11
|
readonly repository: EvolutionBattleRepository;
|
|
10
12
|
readonly random?: () => number;
|
|
11
|
-
readonly
|
|
13
|
+
readonly onState?: PresentationStateListener;
|
|
14
|
+
readonly onArtwork: BattleFrameListener;
|
|
12
15
|
readonly onResolved?: (result: ResolveEvolutionBattleOutcome) => Promise<void>;
|
|
13
16
|
};
|
|
14
17
|
export declare const runEvolutionBattleSession: (snapshot: SidebarSnapshot, viewportWidth: number, dependencies: EvolutionBattleSessionDependencies) => Promise<boolean>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { UsageEvolutionTransition } from "../../digital-pet-core/application/models/usage.js";
|
|
2
2
|
import type { DigimonCatalog } from "../../digital-pet-core/data/catalog.js";
|
|
3
3
|
import type { MonsterFrameCatalog } from "../../digital-pet-core/data/monster-frame-catalog.js";
|
|
4
|
+
import type { PresentationStateListener } from "./presentation-state.js";
|
|
4
5
|
export type EvolutionRevealSessionDependencies = {
|
|
5
6
|
readonly frameCatalog: MonsterFrameCatalog;
|
|
6
7
|
readonly digimonCatalog: DigimonCatalog;
|
|
8
|
+
readonly onState?: PresentationStateListener;
|
|
7
9
|
readonly onArtwork: (artwork: string) => Promise<void>;
|
|
8
10
|
};
|
|
9
11
|
export declare const runEvolutionRevealSession: (evolution: UsageEvolutionTransition, viewportWidth: number, dependencies: EvolutionRevealSessionDependencies) => Promise<boolean>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Semantic animation phases; each frontend chooses its own layout and styling. */
|
|
2
|
+
export type PresentationState = {
|
|
3
|
+
readonly phase: "idle";
|
|
4
|
+
} | {
|
|
5
|
+
readonly phase: "battle";
|
|
6
|
+
readonly fromNodeId: string;
|
|
7
|
+
readonly opponentNodeId: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly phase: "evolving" | "evolved";
|
|
10
|
+
readonly fromNodeId: string;
|
|
11
|
+
readonly toNodeId: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly phase: "defeated";
|
|
14
|
+
readonly fromNodeId: string;
|
|
15
|
+
};
|
|
16
|
+
export type PresentationStateListener = (state: PresentationState) => Promise<void>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { PresentationState } from "../digital-pet-animation/sessions/presentation-state.js";
|
|
1
2
|
import { type Accessor } from "solid-js";
|
|
2
3
|
import type { SidebarCardModel } from "../digital-pet-core/view-models/sidebar-view-model.js";
|
|
3
4
|
import type { MonsterAnimationOutput, MonsterAnimationResult } from "../digital-pet-animation/idle/monster-animation.js";
|
|
4
5
|
export declare const DigitalPetSidebarCard: (props: {
|
|
5
6
|
readonly model: Accessor<SidebarCardModel>;
|
|
6
7
|
readonly animation: Accessor<MonsterAnimationOutput | MonsterAnimationResult>;
|
|
8
|
+
readonly presentationState?: Accessor<PresentationState>;
|
|
7
9
|
readonly customArtwork?: Accessor<string | undefined>;
|
|
8
10
|
readonly onArtworkWidthChange?: (width: number) => void;
|
|
9
11
|
readonly onUrlClick?: (url: string) => void;
|
package/dist/tui.js
CHANGED
|
@@ -7680,7 +7680,17 @@ var renderEvolutionBattleArtwork = (catalog, playerSprite, opponentSprite, scene
|
|
|
7680
7680
|
return rows.map((row) => `${" ".repeat(padding)}${row}`).join("\n");
|
|
7681
7681
|
};
|
|
7682
7682
|
var postScene = async (catalog, playerSprite, opponentSprite, viewportWidth, scene, onFrame) => {
|
|
7683
|
-
|
|
7683
|
+
const battleWidth = MONSTER_FRAME_COLUMNS * 2 + BATTLE_GAP_COLUMNS;
|
|
7684
|
+
await onFrame(renderEvolutionBattleArtwork(catalog, playerSprite, opponentSprite, scene, viewportWidth), {
|
|
7685
|
+
playerHits: scene.playerHits,
|
|
7686
|
+
opponentHits: scene.opponentHits,
|
|
7687
|
+
hitsToWin: EVOLUTION_BATTLE_HITS_TO_WIN,
|
|
7688
|
+
caption: scene.outcomeLabel ?? (scene.lastResult === null ? null : scene.lastResult === "hit" ? "HIT!" : "MISS"),
|
|
7689
|
+
gapStartColumn: Math.max(Math.floor((viewportWidth - battleWidth) / 2), 0) + MONSTER_FRAME_COLUMNS,
|
|
7690
|
+
gapColumns: BATTLE_GAP_COLUMNS,
|
|
7691
|
+
scoreRow: 0,
|
|
7692
|
+
captionRow: MONSTER_FRAME_ROWS - 1
|
|
7693
|
+
});
|
|
7684
7694
|
};
|
|
7685
7695
|
var animateBattleIntro = async (viewportWidth, onFrame) => {
|
|
7686
7696
|
const steps = Math.max(1, Math.ceil(EVOLUTION_BATTLE_INTRO_MS / EVOLUTION_BATTLE_TICK_MS));
|
|
@@ -7868,10 +7878,11 @@ var animatePhase = async (catalog, fromSprite, toSprite, viewportWidth, phase, d
|
|
|
7868
7878
|
if (step < steps) await sleep(EVOLUTION_TICK_MS);
|
|
7869
7879
|
}
|
|
7870
7880
|
};
|
|
7871
|
-
var runEvolutionAnimation = async (catalog, fromSprite, toSprite, viewportWidth, onFrame) => {
|
|
7881
|
+
var runEvolutionAnimation = async (catalog, fromSprite, toSprite, viewportWidth, onFrame, onReveal) => {
|
|
7872
7882
|
await sleep(EVOLUTION_PRE_ANIMATION_MS);
|
|
7873
7883
|
await animatePhase(catalog, fromSprite, toSprite, viewportWidth, "glow", EVOLUTION_GLOW_MS, onFrame);
|
|
7874
7884
|
await animatePhase(catalog, fromSprite, toSprite, viewportWidth, "morph", EVOLUTION_MORPH_MS, onFrame);
|
|
7885
|
+
await onReveal?.();
|
|
7875
7886
|
await animatePhase(catalog, fromSprite, toSprite, viewportWidth, "reveal", EVOLUTION_REVEAL_MS, onFrame);
|
|
7876
7887
|
};
|
|
7877
7888
|
|
|
@@ -7880,12 +7891,16 @@ var runEvolutionRevealSession = async (evolution, viewportWidth, dependencies) =
|
|
|
7880
7891
|
const from = dependencies.digimonCatalog.byId.get(evolution.fromNodeId);
|
|
7881
7892
|
const to = dependencies.digimonCatalog.byId.get(evolution.toNodeId);
|
|
7882
7893
|
if (from === void 0 || to === void 0) return false;
|
|
7894
|
+
await dependencies.onState?.({ phase: "evolving", ...evolution });
|
|
7883
7895
|
await runEvolutionAnimation(
|
|
7884
7896
|
dependencies.frameCatalog,
|
|
7885
7897
|
from.sprite,
|
|
7886
7898
|
to.sprite,
|
|
7887
7899
|
viewportWidth,
|
|
7888
|
-
|
|
7900
|
+
dependencies.onArtwork,
|
|
7901
|
+
async () => {
|
|
7902
|
+
await dependencies.onState?.({ phase: "evolved", ...evolution });
|
|
7903
|
+
}
|
|
7889
7904
|
);
|
|
7890
7905
|
return true;
|
|
7891
7906
|
};
|
|
@@ -7898,7 +7913,8 @@ var runEvolutionBattleSession = async (snapshot, viewportWidth, dependencies) =>
|
|
|
7898
7913
|
if (player === void 0 || opponent === void 0) return false;
|
|
7899
7914
|
const random = dependencies.random ?? Math.random;
|
|
7900
7915
|
const outcome = random() < 0.5 ? "player" : "opponent";
|
|
7901
|
-
const postArtwork = async (artwork) => dependencies.onArtwork(artwork);
|
|
7916
|
+
const postArtwork = async (artwork, hud) => dependencies.onArtwork(artwork, hud);
|
|
7917
|
+
await dependencies.onState?.({ phase: "battle", fromNodeId: player.id, opponentNodeId: opponent.id });
|
|
7902
7918
|
await runEvolutionBattleAnimation(
|
|
7903
7919
|
dependencies.frameCatalog,
|
|
7904
7920
|
player.sprite,
|
|
@@ -7915,10 +7931,12 @@ var runEvolutionBattleSession = async (snapshot, viewportWidth, dependencies) =>
|
|
|
7915
7931
|
{
|
|
7916
7932
|
frameCatalog: dependencies.frameCatalog,
|
|
7917
7933
|
digimonCatalog: dependencies.digimonCatalog,
|
|
7918
|
-
onArtwork: postArtwork
|
|
7934
|
+
onArtwork: postArtwork,
|
|
7935
|
+
...dependencies.onState === void 0 ? {} : { onState: dependencies.onState }
|
|
7919
7936
|
}
|
|
7920
7937
|
);
|
|
7921
7938
|
} else {
|
|
7939
|
+
await dependencies.onState?.({ phase: "defeated", fromNodeId: player.id });
|
|
7922
7940
|
await runDefeatAnimation(dependencies.frameCatalog, player.sprite, viewportWidth, postArtwork);
|
|
7923
7941
|
}
|
|
7924
7942
|
const result = resolveEvolutionBattleForPartner(
|
|
@@ -8958,13 +8976,18 @@ var DigitalPetSidebarCard = (props) => {
|
|
|
8958
8976
|
updateText(gauge, "");
|
|
8959
8977
|
updateText(url, "");
|
|
8960
8978
|
return;
|
|
8961
|
-
case "partner":
|
|
8979
|
+
case "partner": {
|
|
8962
8980
|
updateText(name, model.isSetOverride ? `${model.name} (set)` : model.name);
|
|
8963
8981
|
updateText(stage, model.frozen ? `${model.stage} (frozen)` : model.stage);
|
|
8964
|
-
|
|
8982
|
+
const phase = props.presentationState?.().phase ?? "idle";
|
|
8983
|
+
updateText(
|
|
8984
|
+
nextCheck,
|
|
8985
|
+
phase === "idle" ? buildNextCheckLine(model, nextCheckWidth?.width ?? 0) : phase.toUpperCase()
|
|
8986
|
+
);
|
|
8965
8987
|
updateText(gauge, model.isTerminal ? "-/-" : `${formatCount(model.gauge)}/${formatCount(model.threshold)}`);
|
|
8966
8988
|
updateText(url, URL_LABEL);
|
|
8967
8989
|
return;
|
|
8990
|
+
}
|
|
8968
8991
|
}
|
|
8969
8992
|
};
|
|
8970
8993
|
createEffect(renderCard);
|
|
@@ -9429,6 +9452,7 @@ var createTui = (loadInputs, settings, scheduling = {}) => async (api) => {
|
|
|
9429
9452
|
offset: 0,
|
|
9430
9453
|
facing: "left"
|
|
9431
9454
|
});
|
|
9455
|
+
const [presentationState, setPresentationState] = createSignal({ phase: "idle" });
|
|
9432
9456
|
const [customArtwork, setCustomArtwork] = createSignal();
|
|
9433
9457
|
const controller = new MonsterAnimationController(
|
|
9434
9458
|
MONSTER_FRAME_CATALOG,
|
|
@@ -9458,6 +9482,15 @@ var createTui = (loadInputs, settings, scheduling = {}) => async (api) => {
|
|
|
9458
9482
|
digimonCatalog: DIGIMON_CATALOG,
|
|
9459
9483
|
repository: scheduling.battleRepository,
|
|
9460
9484
|
...scheduling.random === void 0 ? {} : { random: scheduling.random },
|
|
9485
|
+
onState: async (state) => {
|
|
9486
|
+
setPresentationState(state);
|
|
9487
|
+
if (state.phase === "evolved") {
|
|
9488
|
+
const current = inputs();
|
|
9489
|
+
const node = DIGIMON_CATALOG.byId.get(state.toNodeId);
|
|
9490
|
+
if (current.kind === "partner" && node !== void 0) setInputs({ ...current, node });
|
|
9491
|
+
}
|
|
9492
|
+
api.renderer.requestRender();
|
|
9493
|
+
},
|
|
9461
9494
|
onArtwork: async (artwork) => {
|
|
9462
9495
|
setCustomArtwork(artwork);
|
|
9463
9496
|
api.renderer.requestRender();
|
|
@@ -9481,6 +9514,7 @@ var createTui = (loadInputs, settings, scheduling = {}) => async (api) => {
|
|
|
9481
9514
|
return battled;
|
|
9482
9515
|
} finally {
|
|
9483
9516
|
battleInProgress = false;
|
|
9517
|
+
setPresentationState({ phase: "idle" });
|
|
9484
9518
|
setCustomArtwork(void 0);
|
|
9485
9519
|
}
|
|
9486
9520
|
};
|
|
@@ -9498,6 +9532,15 @@ var createTui = (loadInputs, settings, scheduling = {}) => async (api) => {
|
|
|
9498
9532
|
const revealed = await runEvolutionRevealSession(evolution, artworkWidth, {
|
|
9499
9533
|
frameCatalog: MONSTER_FRAME_CATALOG,
|
|
9500
9534
|
digimonCatalog: DIGIMON_CATALOG,
|
|
9535
|
+
onState: async (state) => {
|
|
9536
|
+
setPresentationState(state);
|
|
9537
|
+
if (state.phase === "evolved") {
|
|
9538
|
+
const current = inputs();
|
|
9539
|
+
const node = DIGIMON_CATALOG.byId.get(state.toNodeId);
|
|
9540
|
+
if (current.kind === "partner" && node !== void 0) setInputs({ ...current, node });
|
|
9541
|
+
}
|
|
9542
|
+
api.renderer.requestRender();
|
|
9543
|
+
},
|
|
9501
9544
|
onArtwork: async (artwork) => {
|
|
9502
9545
|
setCustomArtwork(artwork);
|
|
9503
9546
|
api.renderer.requestRender();
|
|
@@ -9512,6 +9555,7 @@ var createTui = (loadInputs, settings, scheduling = {}) => async (api) => {
|
|
|
9512
9555
|
return true;
|
|
9513
9556
|
} finally {
|
|
9514
9557
|
battleInProgress = false;
|
|
9558
|
+
setPresentationState({ phase: "idle" });
|
|
9515
9559
|
setCustomArtwork(void 0);
|
|
9516
9560
|
}
|
|
9517
9561
|
};
|
|
@@ -9651,6 +9695,7 @@ var createTui = (loadInputs, settings, scheduling = {}) => async (api) => {
|
|
|
9651
9695
|
model: () => buildSidebarCardModel(inputs(), settings),
|
|
9652
9696
|
animation,
|
|
9653
9697
|
customArtwork,
|
|
9698
|
+
presentationState,
|
|
9654
9699
|
onArtworkWidthChange: (width) => {
|
|
9655
9700
|
if (disposed) return;
|
|
9656
9701
|
artworkWidth = width;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jcendal/opencode-digital-pet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "An OpenCode virtual-pet plugin with /digital-pet-spawn, /digital-pet-freeze, /digital-pet-unfreeze, /digital-pet-set, /digital-pet-dex, and /digital-pet-history commands.",
|
|
5
5
|
"author": "Jorge Cendal",
|
|
6
6
|
"contributors": [
|