Update Healer Man server

This commit is contained in:
phenom committed 2026-10-04 13:13:35 -04:00
1 parent 39458d5de6
commit 6cc1b9217f
203 files changed
+133929 -31271

No files matched your search

+6
View File
@@ -121,6 +121,12 @@ describe("browser content packs", () => {
totalBytes: firstBytes.byteLength,
error: "",
});
const entriesSpy = vi.spyOn(Object, "entries");
for (let index = 0; index < 100; index += 1) {
expect(contentPackIsUsable("characters:test")).toBe(true);
}
expect(entriesSpy).not.toHaveBeenCalled();
entriesSpy.mockRestore();
const active = storage.stores.get(BROWSER_CONTENT_CACHE_NAME);
expect(await (await active?.match(browserContentObjectPath(manifest.files["/assets/characters/test.glb"])))?.text()).toBe("first-model");
const originalObject = browserContentObjectPath(manifest.files["/assets/characters/test.glb"]);
+9 -1
View File
@@ -78,6 +78,7 @@ let initialization: Promise<void> | null = null;
let installedStorageValidation: Promise<void> | null = null;
let installedStorageValidationComplete = Object.keys(installedState.packs).length === 0;
const validatedInstalledFiles = new Set<string>();
const usablePackCache = new Map<string, boolean>();
function safeLocalStorage(): Storage | null {
try { return typeof window === "undefined" ? null : window.localStorage; } catch { return null; }
@@ -148,6 +149,9 @@ function persistInstalledState(): void {
}
function publish(patch: Partial<Omit<ContentManagerSnapshot, "revision">>): void {
// Every receipt/storage/manifest change publishes before notifying consumers.
// Cache both usable and unavailable packs until the next content revision.
usablePackCache.clear();
snapshot = { ...snapshot, ...patch, revision: snapshot.revision + 1 };
listeners.forEach((listener) => listener());
}
@@ -364,7 +368,11 @@ function contentPackIsUsableInternal(packId: string, visiting: ReadonlySet<strin
}
export function contentPackIsUsable(packId: string): boolean {
return contentPackIsUsableInternal(packId, new Set());
const cached = usablePackCache.get(packId);
if (cached !== undefined) return cached;
const usable = contentPackIsUsableInternal(packId, new Set());
usablePackCache.set(packId, usable);
return usable;
}
/** Vite serves the complete source asset tree in development; shipped builds require an installed pack. */
+9
View File
@@ -48,6 +48,15 @@ describe("content manifest", () => {
}).map(([logicalPath]) => logicalPath)).toEqual(["/assets/b.glb"]);
});
it("reuses the pack index and replaces it when a new catalog file map arrives", () => {
const first = filesForContentPack(manifest, "dungeon:test");
expect(filesForContentPack(manifest, "dungeon:test")).toBe(first);
const updated = { ...manifest, files: { "/assets/b.glb": manifest.files["/assets/b.glb"] } };
expect(filesForContentPack(updated, "dungeon:test").map(([url]) => url)).toEqual(["/assets/b.glb"]);
expect(filesForContentPack(manifest, "dungeon:test")).toBe(first);
expect(filesForContentPack(updated, "missing")).toEqual([]);
});
it("resolves a seeded run's logical models to distinct owning packs", () => {
const withSecondPack: ContentManifest = {
...manifest,
+21 -3
View File
@@ -118,13 +118,31 @@ export function appVersionMeetsMinimum(current: string, minimum: string): boolea
return true;
}
type ContentPackFiles = readonly (readonly [string, ContentManifestFile])[];
const packFileIndexes = new WeakMap<ContentManifest["files"], ReadonlyMap<string, ContentPackFiles>>();
const EMPTY_PACK_FILES: ContentPackFiles = [];
/** Catalog file maps are immutable; index once instead of scanning during gameplay. */
export function filesForContentPack(
manifest: ContentManifest,
packId: string,
): readonly (readonly [string, ContentManifestFile])[] {
return Object.entries(manifest.files)
.filter(([, entry]) => entry.packId === packId)
.sort(([left], [right]) => left.localeCompare(right));
let index = packFileIndexes.get(manifest.files);
if (!index) {
const groups = new Map<string, [string, ContentManifestFile][]>();
for (const [logicalPath, entry] of Object.entries(manifest.files)) {
let files = groups.get(entry.packId);
if (!files) {
files = [];
groups.set(entry.packId, files);
}
files.push([logicalPath, entry]);
}
for (const files of groups.values()) files.sort(([left], [right]) => left.localeCompare(right));
index = groups;
packFileIndexes.set(manifest.files, index);
}
return index.get(packId) ?? EMPTY_PACK_FILES;
}
export interface ContentFileIdentity {