Release Healer Man 0.1.32

This commit is contained in:
phenom committed 2026-09-12 11:07:25 -04:00
1 parent d3d6aaebdb
commit 6dcb1034e5
102 files changed
+10772 -788

No files matched your search

+44
View File
@@ -1,6 +1,10 @@
import { describe, expect, it, vi } from "vitest";
import type { OnlineGroupActivity } from "./onlineGroupTypes";
import { enterOnlineGroupActivity } from "./OnlineGroupCoordinator";
import { SHELVED_MODES_ENABLED } from "./releaseFeatures";
const normalReleaseIt = SHELVED_MODES_ENABLED ? it.skip : it;
const shelvedModeIt = SHELVED_MODES_ENABLED ? it : it.skip;
const DUNGEON_ACTIVITY: OnlineGroupActivity = {
id: "activity-dungeon-1",
@@ -12,6 +16,14 @@ const DUNGEON_ACTIVITY: OnlineGroupActivity = {
startedAt: 1,
};
const MANASTORM_ACTIVITY: OnlineGroupActivity = {
...DUNGEON_ACTIVITY,
id: "activity-manastorm-1",
type: "manastorm",
partySize: 3,
startingLevel: 6,
};
function entryDependencies(enterDungeon: () => boolean) {
return {
installDungeonRuntime: vi.fn(async () => undefined),
@@ -75,4 +87,36 @@ describe("online group activity entry", () => {
expect(dependencies.enterDungeon).not.toHaveBeenCalled();
expect(consumeActivity).not.toHaveBeenCalled();
});
normalReleaseIt("does not load or enter a shelved group activity", async () => {
const consumeActivity = vi.fn();
const dependencies = entryDependencies(() => true);
await expect(enterOnlineGroupActivity(
MANASTORM_ACTIVITY,
"healer",
consumeActivity,
dependencies,
)).resolves.toBe(false);
expect(dependencies.installManastormRuntime).not.toHaveBeenCalled();
expect(dependencies.enterManastorm).not.toHaveBeenCalled();
expect(consumeActivity).not.toHaveBeenCalled();
});
shelvedModeIt("restores Manastorm group entry in the developer mode", async () => {
const consumeActivity = vi.fn();
const dependencies = entryDependencies(() => true);
await expect(enterOnlineGroupActivity(
MANASTORM_ACTIVITY,
"healer",
consumeActivity,
dependencies,
)).resolves.toBe(true);
expect(dependencies.installManastormRuntime).toHaveBeenCalledOnce();
expect(dependencies.enterManastorm).toHaveBeenCalledWith("healer", 3, 6);
expect(consumeActivity).toHaveBeenCalledWith(MANASTORM_ACTIVITY.id);
});
});
+8 -4
View File
@@ -6,9 +6,11 @@ import {
defaultPartyRoleForClass,
type PartyRole,
} from "../game/partyRoles";
import { isManastormPartySize, type ManastormPartySize } from "../game/manastormProgress";
import type { ManastormPartySize } from "../game/manastormProgress";
import type { OnlineGroupActivity } from "./onlineGroupTypes";
import { useContentManagerSnapshot } from "../content/useContentManager";
import { SHELVED_MODES_ENABLED } from "./releaseFeatures";
import { installShelvedOnlineGroupRuntime } from "@shelvedOnlineGroupRuntime";
const GROUP_POLL_MS = 2_500;
const ACTIVITY_JOIN_WINDOW_MS = 10 * 60 * 1000;
@@ -27,8 +29,7 @@ const DEFAULT_ACTIVITY_ENTRY_DEPENDENCIES: OnlineGroupActivityEntryDependencies
},
enterDungeon: (dungeonId, role) => useShellStore.getState().enterDungeon(dungeonId, role),
installManastormRuntime: async () => {
const { installManastormShellRuntime } = await import("./manastormShellRuntime");
installManastormShellRuntime();
await installShelvedOnlineGroupRuntime();
},
enterManastorm: (role, partySize, startingLevel) => useShellStore.getState().enterManastorm(
role,
@@ -48,8 +49,11 @@ export async function enterOnlineGroupActivity(
await dependencies.installDungeonRuntime();
entered = dependencies.enterDungeon(activity.contentId, role);
} else {
if (!SHELVED_MODES_ENABLED) return false;
await dependencies.installManastormRuntime();
const partySize = isManastormPartySize(activity.partySize) ? activity.partySize : 5;
const partySize = Number.isInteger(activity.partySize) && activity.partySize >= 1 && activity.partySize <= 5
? activity.partySize as ManastormPartySize
: 5;
entered = dependencies.enterManastorm(role, partySize, activity.startingLevel ?? undefined);
}
if (entered) consumeActivity(activity.id);
+9
View File
@@ -0,0 +1,9 @@
export function ShelvedGameBridges() {
return null;
}
export function lootShelvedBoss(): boolean {
return false;
}
export function releaseShelvedGameRuntime(): void {}
+23
View File
@@ -0,0 +1,23 @@
import { ManastormBridge } from "../game/ManastormBridge";
import { releaseActiveManastormStage } from "../game/manastormStageLoader";
import { lootCurrentManastormBoss } from "../game/manastormLoot";
import { GmReviewCard } from "../ui/GmReviewCard";
import { GmWorldEditor } from "../ui/GmWorldEditor";
export function ShelvedGameBridges() {
return (
<>
<ManastormBridge />
<GmReviewCard />
<GmWorldEditor />
</>
);
}
export function lootShelvedBoss(): boolean {
return lootCurrentManastormBoss();
}
export function releaseShelvedGameRuntime(): void {
releaseActiveManastormStage();
}
+8
View File
@@ -0,0 +1,8 @@
import type { PlayerSession, ShellPhase } from "./types";
export function ShelvedModeRoute(_props: {
readonly phase: ShellPhase;
readonly session: PlayerSession | null;
}) {
return null;
}
+33
View File
@@ -0,0 +1,33 @@
import { lazy, Suspense, useEffect } from "react";
import type { PlayerSession, ShellPhase } from "./types";
import { useManastormAdminStore } from "../game/manastormAdminStore";
const ManastormSelectScreen = lazy(() => import("../ui/ManastormSelectScreen").then((module) => ({ default: module.ManastormSelectScreen })));
const ArenaSelectScreen = lazy(() => import("../ui/ArenaSelectScreen").then((module) => ({ default: module.ArenaSelectScreen })));
const ArenaRuntime = lazy(() => import("../ArenaRuntime"));
const GmAdminScreen = lazy(() => import("../ui/GmAdminScreen").then((module) => ({ default: module.GmAdminScreen })));
function ShelvedTransition() {
return <div className="world-transition"><span>HM</span><strong>Opening expedition</strong><small>Preparing the next screen...</small></div>;
}
export function ShelvedModeRoute({
phase,
session,
}: {
readonly phase: ShellPhase;
readonly session: PlayerSession | null;
}) {
const refreshManastormConfig = useManastormAdminStore((state) => state.refresh);
useEffect(() => {
void refreshManastormConfig(session);
}, [refreshManastormConfig, session]);
let screen = null;
if (phase === "manastorms") screen = <ManastormSelectScreen />;
else if (phase === "arenas") screen = <ArenaSelectScreen />;
else if (phase === "arena-game") screen = <ArenaRuntime />;
else if (phase === "gm-admin") screen = <GmAdminScreen />;
return <Suspense fallback={<ShelvedTransition />}>{screen}</Suspense>;
}
@@ -0,0 +1 @@
export async function installShelvedOnlineGroupRuntime(): Promise<void> {}
@@ -0,0 +1,4 @@
export async function installShelvedOnlineGroupRuntime(): Promise<void> {
const { installManastormShellRuntime } = await import("./manastormShellRuntime");
installManastormShellRuntime();
}
+27
View File
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import {
isShelvedShellPhase,
sessionCanUseShelvedGmTools,
SHELVED_MODES_ENABLED,
} from "./releaseFeatures";
describe("release feature boundary", () => {
it("recognizes every legacy shelved phase", () => {
expect(["manastorms", "arenas", "arena-game", "gm-admin"].every(isShelvedShellPhase)).toBe(true);
expect(isShelvedShellPhase("main-menu")).toBe(false);
expect(isShelvedShellPhase("game")).toBe(false);
});
it("exposes GM tools only in a shelved build with an authenticated GM", () => {
const session = {
kind: "account",
ownerId: "gm-account",
displayName: "Phenom",
accessToken: "token",
roles: ["gm"],
} as const;
expect(sessionCanUseShelvedGmTools(session)).toBe(SHELVED_MODES_ENABLED);
expect(sessionCanUseShelvedGmTools({ ...session, roles: [] })).toBe(false);
expect(sessionCanUseShelvedGmTools({ ...session, accessToken: "" })).toBe(false);
});
});
+23
View File
@@ -0,0 +1,23 @@
export const SHELVED_MODES_ENABLED = import.meta.env.VITE_SHELVED_MODES_ENABLED === "true";
export const SHELVED_SHELL_PHASES = Object.freeze([
"manastorms",
"arenas",
"arena-game",
"gm-admin",
] as const);
export type ShelvedShellPhase = (typeof SHELVED_SHELL_PHASES)[number];
export function isShelvedShellPhase(value: string): value is ShelvedShellPhase {
return (SHELVED_SHELL_PHASES as readonly string[]).includes(value);
}
export function sessionCanUseShelvedGmTools(session: {
readonly kind: string;
readonly accessToken?: string;
readonly roles?: readonly string[];
} | null): boolean {
return SHELVED_MODES_ENABLED
&& Boolean(session?.kind === "account" && session.accessToken && session.roles?.includes("gm"));
}
+28 -2
View File
@@ -2,6 +2,10 @@ import { describe, expect, it, vi } from "vitest";
import { registerDungeonShellRuntime } from "./dungeonShellRuntimeBridge";
import { registerManastormShellRuntime } from "./manastormShellRuntimeBridge";
import { useShellStore } from "./shellStore";
import { SHELVED_MODES_ENABLED } from "./releaseFeatures";
const normalReleaseIt = SHELVED_MODES_ENABLED ? it.skip : it;
const shelvedModeIt = SHELVED_MODES_ENABLED ? it : it.skip;
describe("on-demand shell runtime delegation", () => {
it("delegates dungeon entry after its phase runtime is installed", () => {
@@ -12,7 +16,30 @@ describe("on-demand shell runtime delegation", () => {
expect(enterDungeon).toHaveBeenCalledWith("wailing-caverns", "healer");
});
it("delegates Manastorm operations after its phase runtime is installed", () => {
normalReleaseIt("ignores registered Manastorm delegates in a normal release", () => {
const enterManastorm = vi.fn(() => true);
const saveSelectedLoadout = vi.fn(() => true);
const abandonRun = vi.fn();
const failRun = vi.fn();
registerManastormShellRuntime({
enterManastorm,
saveSelectedLoadout,
abandonRun,
failRun,
leaveActiveGame: vi.fn(),
});
expect(useShellStore.getState().enterManastorm("healer", 3, 6)).toBe(false);
expect(useShellStore.getState().saveSelectedManastormLoadout(["spell-1"])).toBe(false);
useShellStore.getState().abandonManastormRun();
useShellStore.getState().failActiveManastorm();
expect(enterManastorm).not.toHaveBeenCalled();
expect(saveSelectedLoadout).not.toHaveBeenCalled();
expect(abandonRun).not.toHaveBeenCalled();
expect(failRun).not.toHaveBeenCalled();
});
shelvedModeIt("delegates Manastorm operations after its phase runtime is installed", () => {
const enterManastorm = vi.fn(() => true);
const saveSelectedLoadout = vi.fn(() => true);
const abandonRun = vi.fn();
@@ -37,4 +64,3 @@ describe("on-demand shell runtime delegation", () => {
expect(failRun).toHaveBeenCalledOnce();
});
});
+16 -4
View File
@@ -3,6 +3,7 @@ import { createDefaultAppearance } from "./characterCatalog";
import { useShellStore } from "./shellStore";
import { installDungeonShellRuntime } from "./dungeonShellRuntime";
import { installManastormShellRuntime } from "./manastormShellRuntime";
import { SHELVED_MODES_ENABLED } from "./releaseFeatures";
import { DUNGEON_MANIFEST } from "../game/dungeonManifest";
import { useGameStore } from "../game/store";
import { useManastormStore } from "../game/manastormStore";
@@ -23,6 +24,8 @@ import { useManastormAdminStore } from "../game/manastormAdminStore";
installDungeonShellRuntime();
installManastormShellRuntime();
const normalReleaseIt = SHELVED_MODES_ENABLED ? it.skip : it;
const shelvedModeIt = SHELVED_MODES_ENABLED ? it : it.skip;
const character = {
id: "character-1",
@@ -254,7 +257,16 @@ describe("shell store", () => {
});
});
it("opens Manastorms, validates the role, and starts a fresh level-one run", () => {
normalReleaseIt("keeps legacy shelved commands out of the normal shell", () => {
prepareSelectedCharacter();
expect(useShellStore.getState().openManastorms()).toBe(false);
expect(useShellStore.getState().openArenas()).toBe(false);
expect(useShellStore.getState().enterManastorm("healer")).toBe(false);
expect(useShellStore.getState().phase).toBe("characters");
expect(useShellStore.getState().notice).toMatch(/temporarily unavailable/i);
});
shelvedModeIt("opens Manastorms, validates the role, and starts a fresh level-one run", () => {
prepareSelectedCharacter();
expect(useShellStore.getState().openManastorms()).toBe(true);
expect(useShellStore.getState().phase).toBe("manastorms");
@@ -278,7 +290,7 @@ describe("shell store", () => {
);
});
it("starts each party size from only its own unlocked checkpoint", () => {
shelvedModeIt("starts each party size from only its own unlocked checkpoint", () => {
const progress = recordManastormCheckpoint(createEmptyManastormProgress(), 3, 5, 6);
useShellStore.setState({
phase: "manastorms",
@@ -302,7 +314,7 @@ describe("shell store", () => {
});
});
it("selects end-game mode at the character cap and hands off an eligible checkpoint", () => {
shelvedModeIt("selects end-game mode at the character cap and hands off an eligible checkpoint", () => {
const progress = recordManastormCheckpoint(
createEmptyManastormProgress(),
3,
@@ -333,7 +345,7 @@ describe("shell store", () => {
});
});
it("passes a persisted client-grounded spell selection into the run runtime", () => {
shelvedModeIt("passes a persisted client-grounded spell selection into the run runtime", () => {
const model = manastormActiveSpellLoadoutModel(character.classId, character.level);
const selectedIds = model.options.slice(0, 2).map((option) => option.id);
const unlocked = mergeManastormUnlockedContent(
+25 -5
View File
@@ -56,7 +56,7 @@ import {
logoutOnlineSession,
refreshOnlineSession,
} from "./onlineAccountClient";
import { isGameMasterSession } from "../game/manastormAdminConfig";
import { sessionCanUseShelvedGmTools, SHELVED_MODES_ENABLED } from "./releaseFeatures";
import { useGameStore } from "../game/store";
import {
normalizeSpecializationId,
@@ -324,6 +324,10 @@ export const useShellStore = create<ShellState>((set, get) => ({
return true;
},
openManastorms: () => {
if (!SHELVED_MODES_ENABLED) {
set({ notice: "Manastorms are temporarily unavailable." });
return false;
}
const { session, selectedCharacterId, characters } = get();
if (!session || !selectedCharacterId || !characters.some((character) => character.id === selectedCharacterId)) {
set({ phase: session ? "characters" : "login", notice: "Select or create a character first." });
@@ -338,6 +342,10 @@ export const useShellStore = create<ShellState>((set, get) => ({
return true;
},
openArenas: () => {
if (!SHELVED_MODES_ENABLED) {
set({ notice: "Arenas are temporarily unavailable." });
return false;
}
const { session, selectedCharacterId, characters } = get();
if (!session || !selectedCharacterId || !characters.some((character) => character.id === selectedCharacterId)) {
set({ phase: session ? "characters" : "login", notice: "Select or create a character first." });
@@ -348,7 +356,7 @@ export const useShellStore = create<ShellState>((set, get) => ({
},
openGmAdmin: () => {
const { session, selectedCharacterId, characters } = get();
if (!isGameMasterSession(session)) {
if (!sessionCanUseShelvedGmTools(session)) {
set({ notice: "Game Master access is required." });
return false;
}
@@ -429,7 +437,7 @@ export const useShellStore = create<ShellState>((set, get) => ({
},
returnToMainMenu: () => {
useGameStore.setState({ activeGmReviewTarget: null, gmTestMode: false });
manastormShellRuntime()?.leaveActiveGame();
if (SHELVED_MODES_ENABLED) manastormShellRuntime()?.leaveActiveGame();
const { session, characters, activeCharacter, selectedCharacterId } = get();
if (!session) {
set({ phase: "login", activeCharacter: null });
@@ -449,13 +457,21 @@ export const useShellStore = create<ShellState>((set, get) => ({
return false;
},
enterManastorm: (requestedRole, requestedPartySize = 5, requestedStartingLevel) => {
if (!SHELVED_MODES_ENABLED) {
set({ notice: "Manastorms are temporarily unavailable." });
return false;
}
const runtime = manastormShellRuntime();
if (runtime) return runtime.enterManastorm(requestedRole, requestedPartySize, requestedStartingLevel);
set({ notice: "Manastorm data is still loading. Try again in a moment." });
return false;
},
abandonManastormRun: () => manastormShellRuntime()?.abandonRun(),
failActiveManastorm: () => manastormShellRuntime()?.failRun(),
abandonManastormRun: () => {
if (SHELVED_MODES_ENABLED) manastormShellRuntime()?.abandonRun();
},
failActiveManastorm: () => {
if (SHELVED_MODES_ENABLED) manastormShellRuntime()?.failRun();
},
enterWorld: () => get().enterDungeon(DEFAULT_DUNGEON_ID),
returnToCharacters: () => {
useGameStore.setState({ activeGmReviewTarget: null, gmTestMode: false });
@@ -594,6 +610,10 @@ export const useShellStore = create<ShellState>((set, get) => ({
}));
},
saveSelectedManastormLoadout: (loadout) => {
if (!SHELVED_MODES_ENABLED) {
set({ notice: "Manastorms are temporarily unavailable." });
return false;
}
const runtime = manastormShellRuntime();
if (runtime) return runtime.saveSelectedLoadout(loadout);
set({ notice: "Manastorm data is still loading. Try again in a moment." });