feat: Implement scene rulebook and validation engine
- Added a new SceneRulebook system to manage data-driven validation rules for actions. - Introduced rule checks for actions like "take", "open", "move", "introduce", and "describe". - Created a rulebook engine to evaluate conditions and enforce rules during action validation. - Enhanced action handling with support for scene entry and character descriptions. - Updated the architecture documentation to reflect the new rule-based validation approach. - Added new endpoints and improved the persistence layer for rulebooks.
This commit is contained in:
@@ -4,6 +4,7 @@ import Database from "better-sqlite3";
|
||||
|
||||
import type { Action } from "./contracts/action";
|
||||
import type { Entity } from "./contracts/entity";
|
||||
import type { SceneRulebook } from "./contracts/rulebook";
|
||||
import type { Turn } from "./contracts/turn";
|
||||
import type { ValidationResult } from "./contracts/validation";
|
||||
import type { WorldState } from "./contracts/world";
|
||||
@@ -24,6 +25,10 @@ export interface CharacterGardenDatabase {
|
||||
insertValidationResults(turnId: string, results: ValidationResult[]): void;
|
||||
insertWorldState(turnId: string | null, worldState: WorldState): void;
|
||||
getLatestWorldState(): WorldState | null;
|
||||
upsertRulebook(rulebook: SceneRulebook): void;
|
||||
getRulebook(id: string): SceneRulebook | null;
|
||||
listRulebooks(): SceneRulebook[];
|
||||
deleteRulebook(id: string): void;
|
||||
wipe(): void;
|
||||
}
|
||||
|
||||
@@ -88,6 +93,17 @@ export function createDatabase(config: DatabaseConfig): CharacterGardenDatabase
|
||||
FOREIGN KEY(turn_id) REFERENCES turns(id)
|
||||
)
|
||||
`,
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS rulebooks (
|
||||
id TEXT PRIMARY KEY,
|
||||
world_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
rules_json TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
`,
|
||||
];
|
||||
|
||||
for (const statement of initStatements) {
|
||||
@@ -169,6 +185,32 @@ export function createDatabase(config: DatabaseConfig): CharacterGardenDatabase
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
const upsertRulebookStatement = sqlite.prepare(`
|
||||
INSERT INTO rulebooks (id, world_id, name, description, rules_json, created_at, updated_at)
|
||||
VALUES (@id, @world_id, @name, @description, @rules_json, @created_at, @updated_at)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
description = excluded.description,
|
||||
rules_json = excluded.rules_json,
|
||||
updated_at = excluded.updated_at
|
||||
`);
|
||||
|
||||
const getRulebookStatement = sqlite.prepare(`
|
||||
SELECT id, world_id, name, description, rules_json, created_at, updated_at
|
||||
FROM rulebooks
|
||||
WHERE id = @id
|
||||
`);
|
||||
|
||||
const listRulebooksStatement = sqlite.prepare(`
|
||||
SELECT id, world_id, name, description, rules_json, created_at, updated_at
|
||||
FROM rulebooks
|
||||
ORDER BY created_at ASC
|
||||
`);
|
||||
|
||||
const deleteRulebookStatement = sqlite.prepare(`
|
||||
DELETE FROM rulebooks WHERE id = @id
|
||||
`);
|
||||
|
||||
return {
|
||||
sqlite,
|
||||
|
||||
@@ -296,5 +338,66 @@ export function createDatabase(config: DatabaseConfig): CharacterGardenDatabase
|
||||
}
|
||||
return parseJson<WorldState>(row.state_json);
|
||||
},
|
||||
|
||||
upsertRulebook(rulebook) {
|
||||
upsertRulebookStatement.run({
|
||||
id: rulebook.id,
|
||||
world_id: rulebook.worldId,
|
||||
name: rulebook.name,
|
||||
description: rulebook.description ?? null,
|
||||
rules_json: JSON.stringify(rulebook.rules),
|
||||
created_at: rulebook.createdAt,
|
||||
updated_at: rulebook.updatedAt,
|
||||
});
|
||||
},
|
||||
|
||||
getRulebook(id) {
|
||||
const row = getRulebookStatement.get({ id }) as
|
||||
| {
|
||||
id: string;
|
||||
world_id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
rules_json: string;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
| undefined;
|
||||
if (!row) return null;
|
||||
return {
|
||||
id: row.id,
|
||||
worldId: row.world_id,
|
||||
name: row.name,
|
||||
description: row.description ?? undefined,
|
||||
rules: parseJson(row.rules_json),
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
},
|
||||
|
||||
listRulebooks() {
|
||||
const rows = listRulebooksStatement.all() as Array<{
|
||||
id: string;
|
||||
world_id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
rules_json: string;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}>;
|
||||
return rows.map((row) => ({
|
||||
id: row.id,
|
||||
worldId: row.world_id,
|
||||
name: row.name,
|
||||
description: row.description ?? undefined,
|
||||
rules: parseJson(row.rules_json),
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
}));
|
||||
},
|
||||
|
||||
deleteRulebook(id) {
|
||||
deleteRulebookStatement.run({ id });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user