feat(memory): record spawned, social, proposal, and bond events
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,7 @@ export class GameLoop {
|
||||
|
||||
private spawnInitialNPCs(count: number): void {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const entity = spawnNPC(this.world, this.map);
|
||||
const entity = spawnNPC(this.world, this.map, undefined, this.eventMemoryService);
|
||||
this.generateNpcBackstory(entity);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ export class GameLoop {
|
||||
statModifierSystem(this.world);
|
||||
needsDecaySystem(this.world);
|
||||
npcBrainSystem(this.world, this.map);
|
||||
socialSystem(this.world);
|
||||
socialSystem(this.world, this.eventMemoryService);
|
||||
narrationEmitter(this.world, this.narrationService, this.followedEntityIds);
|
||||
relationshipSystem(this.world);
|
||||
movementSystem(this.world);
|
||||
|
||||
@@ -4,8 +4,9 @@ import { generateRandomAppearance } from '../spawner/appearanceGenerator.js';
|
||||
import { generateName } from '../spawner/nameGenerator.js';
|
||||
import { generateStats } from '../spawner/statGenerator.js';
|
||||
import type { EntityId, Position, Needs, Movement, NPCBrain, Appearance, SocialState, Stats, StatModifiers, Relationships } from '@dflike/shared';
|
||||
import type { EventMemoryService } from '../llm/eventMemoryService.js';
|
||||
|
||||
export function spawnNPC(world: World, map: GameMap, positionHint?: Position): EntityId {
|
||||
export function spawnNPC(world: World, map: GameMap, positionHint?: Position, eventMemoryService?: EventMemoryService): EntityId {
|
||||
const entity = world.createEntity();
|
||||
const pos = positionHint && map.isWalkable(positionHint.x, positionHint.y)
|
||||
? positionHint
|
||||
@@ -46,5 +47,11 @@ export function spawnNPC(world: World, map: GameMap, positionHint?: Position): E
|
||||
world.addComponent<Relationships>(entity, 'relationships', new Map());
|
||||
world.addComponent<string>(entity, 'backstory', '');
|
||||
|
||||
eventMemoryService?.record(entity, {
|
||||
type: 'spawned',
|
||||
tick: 0,
|
||||
detail: `${world.getComponent<string>(entity, 'name')} arrived in the village`,
|
||||
});
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ export class SocketServer {
|
||||
|
||||
const target = map.findNearestWalkable(data.x, data.y, 3)
|
||||
?? map.getRandomWalkable();
|
||||
const npcEntity = spawnNPC(world, map, target);
|
||||
const npcEntity = spawnNPC(world, map, target, this.gameLoop.eventMemoryService);
|
||||
this.gameLoop.generateNpcBackstory(npcEntity);
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getEffectiveStat } from './statHelpers.js';
|
||||
import type { BondRegistry } from './bondRegistry.js';
|
||||
import { addBond } from './bondRegistry.js';
|
||||
import { relationshipConfig as cfg } from '../config/relationshipConfig.js';
|
||||
import type { EventMemoryService } from '../llm/eventMemoryService.js';
|
||||
|
||||
function directionTo(from: Position, to: Position): number {
|
||||
const dx = to.x - from.x;
|
||||
@@ -36,7 +37,7 @@ function isTargetBusy(world: World, target: EntityId): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function socialSystem(world: World): void {
|
||||
export function socialSystem(world: World, eventMemoryService?: EventMemoryService): void {
|
||||
const entities = world.query('socialState', 'position', 'movement', 'npcBrain', 'needs');
|
||||
const npcs = entities.filter(e => !world.getComponent(e, 'playerControlled'));
|
||||
|
||||
@@ -174,6 +175,29 @@ export function socialSystem(world: World): void {
|
||||
tick: 0,
|
||||
};
|
||||
|
||||
if (eventMemoryService) {
|
||||
const nameA = world.getComponent<string>(e, 'name') ?? 'Unknown';
|
||||
const nameB = world.getComponent<string>(partnerId!, 'name') ?? 'Unknown';
|
||||
eventMemoryService.record(e, {
|
||||
type: social.outcome === 'positive' ? 'social_positive' : 'social_negative',
|
||||
tick: 0,
|
||||
otherEntityId: partnerId!,
|
||||
otherName: nameB,
|
||||
detail: social.outcome === 'positive'
|
||||
? `Had a positive interaction with ${nameB}`
|
||||
: `Had a negative interaction with ${nameB}`,
|
||||
});
|
||||
eventMemoryService.record(partnerId!, {
|
||||
type: partnerSocial.outcome === 'positive' ? 'social_positive' : 'social_negative',
|
||||
tick: 0,
|
||||
otherEntityId: e,
|
||||
otherName: nameA,
|
||||
detail: partnerSocial.outcome === 'positive'
|
||||
? `Had a positive interaction with ${nameA}`
|
||||
: `Had a negative interaction with ${nameA}`,
|
||||
});
|
||||
}
|
||||
|
||||
social.phase = 'none';
|
||||
social.partnerId = null;
|
||||
social.phaseTimer = 0;
|
||||
@@ -267,6 +291,56 @@ export function socialSystem(world: World): void {
|
||||
social.lastOutcome = { partnerId: partnerId!, outcome: social.outcome!, tick: 0 };
|
||||
partnerSocial.lastOutcome = { partnerId: e, outcome: partnerSocial.outcome!, tick: 0 };
|
||||
|
||||
if (eventMemoryService) {
|
||||
const nameInit = world.getComponent<string>(initiator, 'name') ?? 'Unknown';
|
||||
const nameRec = world.getComponent<string>(receiver, 'name') ?? 'Unknown';
|
||||
if (accepted) {
|
||||
eventMemoryService.record(initiator, {
|
||||
type: 'proposal_accepted',
|
||||
tick: 0,
|
||||
otherEntityId: receiver,
|
||||
otherName: nameRec,
|
||||
detail: `Proposal to ${nameRec} was accepted`,
|
||||
});
|
||||
eventMemoryService.record(receiver, {
|
||||
type: 'proposal_accepted',
|
||||
tick: 0,
|
||||
otherEntityId: initiator,
|
||||
otherName: nameInit,
|
||||
detail: `Accepted ${nameInit}'s proposal`,
|
||||
});
|
||||
eventMemoryService.record(initiator, {
|
||||
type: 'bond_formed',
|
||||
tick: 0,
|
||||
otherEntityId: receiver,
|
||||
otherName: nameRec,
|
||||
detail: `Became partners with ${nameRec}`,
|
||||
});
|
||||
eventMemoryService.record(receiver, {
|
||||
type: 'bond_formed',
|
||||
tick: 0,
|
||||
otherEntityId: initiator,
|
||||
otherName: nameInit,
|
||||
detail: `Became partners with ${nameInit}`,
|
||||
});
|
||||
} else {
|
||||
eventMemoryService.record(initiator, {
|
||||
type: 'proposal_rejected',
|
||||
tick: 0,
|
||||
otherEntityId: receiver,
|
||||
otherName: nameRec,
|
||||
detail: `Proposal to ${nameRec} was rejected`,
|
||||
});
|
||||
eventMemoryService.record(receiver, {
|
||||
type: 'proposal_rejected',
|
||||
tick: 0,
|
||||
otherEntityId: initiator,
|
||||
otherName: nameInit,
|
||||
detail: `Rejected ${nameInit}'s proposal`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Reset both entities
|
||||
social.phase = 'none';
|
||||
social.partnerId = null;
|
||||
|
||||
Reference in New Issue
Block a user