From 6cf099821bd511c0440d280ec198577e9b04af72 Mon Sep 17 00:00:00 2001 From: Aditya Gupta Date: Thu, 9 Jul 2026 03:04:48 +0530 Subject: [PATCH] refactor: Serializers include available location attributes --- content/scenario-core/src/loader.ts | 2 + .../scenario-core/tests/talking-room.test.ts | 15 +++++ packages/core/src/repository.ts | 24 ++++++++ packages/core/src/world.ts | 61 +++++++++++++++++++ 4 files changed, 102 insertions(+) diff --git a/content/scenario-core/src/loader.ts b/content/scenario-core/src/loader.ts index 9c9452f..2497f6f 100644 --- a/content/scenario-core/src/loader.ts +++ b/content/scenario-core/src/loader.ts @@ -72,6 +72,7 @@ export class ScenarioLoader { } // Save location record linked to the world instance + world.addLocation(location); this.coreRepo.saveLocation(location, world.id); } } @@ -102,6 +103,7 @@ export class ScenarioLoader { } // Save entity record linked to the world instance + world.addEntity(entity); this.coreRepo.saveEntity(entity, world.id); // Seed initial memory buffer history diff --git a/content/scenario-core/tests/talking-room.test.ts b/content/scenario-core/tests/talking-room.test.ts index 3711332..d5859ea 100644 --- a/content/scenario-core/tests/talking-room.test.ts +++ b/content/scenario-core/tests/talking-room.test.ts @@ -84,6 +84,21 @@ describe("Talking Room Demo Scenario Test (Tier 1)", () => { expect(beta!.locationId).toBe("white-room"); expect(beta!.aliases.get(alphaId)).toBe("the person in the Alpha jumpsuit"); + // Verify subjective world state serializes the location attributes (epistemic inclusion) + const { serializeSubjectiveWorldState } = await import("@omnia/core"); + const subjectiveState = serializeSubjectiveWorldState(world!, alphaId); + expect(subjectiveState).toContain("You are at location: white-room"); + expect(subjectiveState).toContain("Location attributes:"); + expect(subjectiveState).toContain("description: A pristine, featureless room"); + expect(subjectiveState).toContain("lighting: Bright, uniform illumination"); + + // Verify objective world state serializes locations (physics awareness) + const { serializeObjectiveWorldState } = await import("@omnia/core"); + const objectiveState = serializeObjectiveWorldState(world!); + expect(objectiveState).toContain("Locations:"); + expect(objectiveState).toContain("- Location [ID: white-room]:"); + expect(objectiveState).toContain("description: A pristine, featureless room"); + // 7. Assert initial pre-seeded memories const alphaMemories = bufferRepo.listForOwner(alphaId); expect(alphaMemories).toHaveLength(1); diff --git a/packages/core/src/repository.ts b/packages/core/src/repository.ts index e8f2847..cfc1cc7 100644 --- a/packages/core/src/repository.ts +++ b/packages/core/src/repository.ts @@ -4,6 +4,8 @@ import { AttributableObject, AttributeVisibility } from "./attribute.js"; import { Entity } from "./entity.js"; import { WorldState } from "./world.js"; +class GenericObject extends AttributableObject {} + export class SQLiteRepository { private db: Database.Database; @@ -195,6 +197,9 @@ export class SQLiteRepository { for (const entity of worldState.entities.values()) { this.saveEntity(entity, worldState.id); } + for (const location of worldState.locations.values()) { + this.saveLocation(location, worldState.id); + } }); saveWorldTx(); } @@ -288,6 +293,25 @@ export class SQLiteRepository { const worldState = new WorldState(id, startTime); this.reconstituteAttributes(worldState); + // Reconstitute all locations belonging to this world + const locationRows = this.db + .prepare( + ` + SELECT id, location_id, connections_json FROM objects WHERE type = 'location' AND world_id = ? + `, + ) + .all(id) as { id: string; location_id: string | null; connections_json: string | null }[]; + + for (const row of locationRows) { + const loc = new GenericObject(row.id); + (loc as { parentId?: string | null }).parentId = row.location_id; + if (row.connections_json) { + (loc as { connections?: unknown[] }).connections = JSON.parse(row.connections_json); + } + this.reconstituteAttributes(loc); + worldState.addLocation(loc); + } + // Reconstitute all entities belonging to this world const entityRows = this.db .prepare( diff --git a/packages/core/src/world.ts b/packages/core/src/world.ts index eb8fc68..9c5858d 100644 --- a/packages/core/src/world.ts +++ b/packages/core/src/world.ts @@ -8,6 +8,7 @@ export class WorldState extends AttributableObject { * Universe's current state (distinct from how it started) */ readonly entities: Map = new Map(); + readonly locations: Map = new Map(); readonly clock: WorldClock; constructor(id?: string, startTime?: Date) { @@ -27,6 +28,19 @@ export class WorldState extends AttributableObject { getEntity(id: string): Entity | undefined { return this.entities.get(id); } + + addLocation(location: AttributableObject): void { + if (this.locations.has(location.id)) { + throw new Error( + `Location with ID ${location.id} already exists in the world`, + ); + } + this.locations.set(location.id, location); + } + + getLocation(id: string): AttributableObject | undefined { + return this.locations.get(id); + } } /** @@ -43,6 +57,45 @@ export function serializeObjectiveWorldState(worldState: WorldState): string { lines.push(worldAttrsStr.split("\n").map(l => " " + l).join("\n")); } + // Serialize locations and their attributes/portals + lines.push("Locations:"); + if (worldState.locations.size > 0) { + for (const loc of worldState.locations.values()) { + lines.push(` - Location [ID: ${loc.id}]:`); + + const parentId = (loc as { parentId?: string | null }).parentId; + if (parentId) { + lines.push(` * Parent Location ID: ${parentId}`); + } + + if (loc.attributes.size > 0) { + const locAttrsStr = serializeAttributes(Array.from(loc.attributes.values())); + lines.push(locAttrsStr.split("\n").map(l => " " + l).join("\n")); + } else { + lines.push(" * (No attributes)"); + } + + const connections = (loc as { connections?: unknown[] }).connections as { + targetId: string; + portalName?: string; + portalStateDescriptor?: string; + visionProp: number; + soundProp: number; + bidirectional: boolean; + }[] | undefined; + + if (connections && connections.length > 0) { + lines.push(" * Connections:"); + for (const conn of connections) { + const portalStr = conn.portalName ? ` via ${conn.portalName} (${conn.portalStateDescriptor || "normal"})` : ""; + lines.push(` -> To: ${conn.targetId}${portalStr} (Vision: ${conn.visionProp}, Sound: ${conn.soundProp})`); + } + } + } + } else { + lines.push(" (No locations)"); + } + // Serialize entities and their attributes lines.push("Entities:"); if (worldState.entities.size > 0) { @@ -129,6 +182,14 @@ export function serializeSubjectiveWorldState( lines.push("What you perceive around you:"); if (viewer.locationId) { lines.push(` You are at location: ${viewer.locationId}`); + const location = worldState.getLocation(viewer.locationId); + if (location) { + const locVisible = location.getVisibleAttributesFor(viewerId); + if (locVisible.length > 0) { + lines.push(" Location attributes:"); + lines.push(serializeVisibleAttributes(locVisible).split("\n").map((l) => " " + l).join("\n")); + } + } } else { lines.push(" You are not located anywhere in particular."); }