Release Healer Man 0.1.21

This commit is contained in:
phenom committed 2026-09-08 10:40:26 -04:00
1 parent 699fc858c9
commit f6f2375145
210 files changed
+976039 -6247

No files matched your search

+23 -1
View File
@@ -4,12 +4,15 @@ const PLAYER_TIMEOUT_MS = 15_000;
const ROOM_TIMEOUT_MS = 30 * 60_000;
const MAX_EVENTS = 256;
const MAX_BATCH_EVENTS = 48;
const MAX_WORLD_BYTES = 4 * 1024 * 1024;
const ALLOWED_EVENT_KINDS = new Set([
"damage",
"healing",
"resurrection",
"loot",
"portal",
"interact",
"party-teleport",
]);
const ALLOWED_SCHOOLS = new Set([
"physical",
@@ -43,6 +46,23 @@ function jsonClone(value, fallback = null) {
}
}
function boundedWorldClone(value) {
let serialized;
try {
serialized = JSON.stringify(value);
} catch {
throw new GameDatabaseError("Shared world state must be JSON serializable.", 400, "invalid_world_state");
}
if (!serialized || Buffer.byteLength(serialized, "utf8") > MAX_WORLD_BYTES) {
throw new GameDatabaseError("Shared world state exceeds the 4 MiB activity limit.", 413, "world_state_too_large");
}
const clone = JSON.parse(serialized);
if (Array.isArray(clone.encounters) && clone.encounters.length > 128) {
throw new GameDatabaseError("Shared world state has too many encounter snapshots.", 400, "invalid_world_state");
}
return clone;
}
function requireActivity(account, snapshot) {
const group = snapshot?.group;
const activity = group?.activity;
@@ -90,6 +110,7 @@ function sanitizeEvent(input, accountId, leaderAccountId) {
if (!targetActorId && kind !== "portal") return null;
const sourceActorId = boundedString(input?.sourceActorId);
const localPlayerActorId = `online-player:${accountId}`;
if (kind === "party-teleport" && accountId !== leaderAccountId) return null;
// Non-leaders may submit only their own player actions. Enemy and AI events
// are produced by the activity authority and cannot be forged by members.
if (accountId !== leaderAccountId && sourceActorId !== localPlayerActorId) return null;
@@ -105,6 +126,7 @@ function sanitizeEvent(input, accountId, leaderAccountId) {
effectiveAmount: Math.max(0, Math.min(1_000_000_000, finite(input?.effectiveAmount))),
critical: input?.critical === true,
occurredAt: Math.max(0, Math.trunc(finite(input?.occurredAt, Date.now()))),
...(kind === "party-teleport" ? { position: vector3(input?.position) } : {}),
};
}
@@ -154,7 +176,7 @@ export function createOnlineSessionManager() {
throw new GameDatabaseError("Shared world state must be an object.", 400, "invalid_world_state");
}
room.world = {
...jsonClone(input.world, {}),
...boundedWorldClone(input.world),
publishedAt: now,
publishedBy: account.id,
};
+49
View File
@@ -112,3 +112,52 @@ test("rejects non-leader world updates and forged enemy events", () => {
});
assert.equal(state.events.length, 0);
});
test("relays member interactions but bounds authoritative world snapshots", () => {
const manager = createOnlineSessionManager();
const snapshot = groupSnapshot();
const interaction = manager.sync({ id: "healer" }, snapshot, {
presence,
events: [{
clientEventId: "interact:door:1",
kind: "interact",
sourceActorId: "online-player:healer",
targetActorId: "temple-door",
}],
});
assert.equal(interaction.events[0]?.kind, "interact");
assert.equal(interaction.events[0]?.targetActorId, "temple-door");
assert.throws(() => manager.sync({ id: "leader" }, snapshot, {
presence,
world: { padding: "x".repeat(4 * 1024 * 1024) },
}), (error) => error instanceof GameDatabaseError && error.code === "world_state_too_large");
});
test("accepts party teleports only from the activity leader", () => {
const manager = createOnlineSessionManager();
const snapshot = groupSnapshot();
const forged = manager.sync({ id: "healer" }, snapshot, {
presence,
events: [{
clientEventId: "teleport:forged",
kind: "party-teleport",
sourceActorId: "online-player:healer",
targetActorId: "party",
position: [9, 8, 7],
}],
});
assert.equal(forged.events.length, 0);
const accepted = manager.sync({ id: "leader" }, snapshot, {
presence,
events: [{
clientEventId: "teleport:leader",
kind: "party-teleport",
sourceActorId: "online-player:leader",
targetActorId: "party",
position: [9, 8, 7],
}],
});
assert.deepEqual(accepted.events[0]?.position, [9, 8, 7]);
});