Files
CharacterGardenStack/Implementation_plan.md
spencer 998635f542 feat: implement processTurn function to handle turn processing and world state updates
refactor: remove legacy types.ts file and update frontend to use new contracts

feat: add applyActions function to manage action application and world state mutation

chore: remove empty .gitkeep file from sqlite data directory

refactor: update frontend App component to align with new API contracts and improve UX

docs: revise project.md to reflect updated architecture and system requirements

docs: update thoughts.md with current status, architecture decisions, and remaining checks
2026-04-24 01:04:17 -04:00

6.9 KiB

CharacterGarden — Iterative Implementation Plan

Copilot Operating Rules

Work in small, reviewable steps.

After every completed step:

  1. Update thoughts.md
  2. Record files changed
  3. Record assumptions made
  4. Record next step
  5. Do not skip ahead unless the current step is complete

Do not redesign the project without updating this plan.


Phase 1 — Contracts First

Step 1.1 — Create contracts folder

Create:

app/src/contracts/

Add:

app/src/contracts/action.ts
app/src/contracts/turn.ts
app/src/contracts/validation.ts
app/src/contracts/world.ts
app/src/contracts/entity.ts

Goal: all shared types live here.


Step 1.2 — Define Action contract

In action.ts:

export type Action = {
  actorId: string;
  type: string;
  targetId?: string;
  locationId?: string;
  metadata?: Record<string, unknown>;
};

No other action shape should be used.


Step 1.3 — Define ValidationResult contract

In validation.ts:

export type ValidationResult = {
  actionIndex: number;
  success: boolean;
  reason?: string;
  message?: string;
};

Step 1.4 — Define Turn contract

In turn.ts:

import type { Action } from "./action";
import type { ValidationResult } from "./validation";

export type Turn = {
  id: string;
  rawText: string;
  actions: Action[];
  validation: ValidationResult[];
  createdAt: number;
};

Step 1.5 — Define Entity contract

In entity.ts:

export type Entity = {
  id: string;
  name: string;
  type: string;
  attributes: Record<string, unknown>;
};

Step 1.6 — Define WorldState contract

In world.ts:

import type { Entity } from "./entity";

export type WorldState = {
  id: string;
  entities: Record<string, Entity>;
  metadata: Record<string, unknown>;
  createdAt: number;
};

Phase 2 — Enforce Layer Boundaries

Step 2.1 — Refactor truth engine imports

Update truthEngine.ts so it imports:

import type { Action } from "./contracts/action";
import type { ValidationResult } from "./contracts/validation";
import type { WorldState } from "./contracts/world";

Truth engine must only receive structured actions.


Step 2.2 — Remove text parsing from truth engine

Search truthEngine.ts for:

  • string parsing
  • natural language interpretation
  • prompt logic
  • LLM calls

Move any such logic out.

Truth engine should expose:

export function validateActions(
  actions: Action[],
  worldState: WorldState
): ValidationResult[] {
  // deterministic validation only
}

Step 2.3 — Create parser layer

Create:

app/src/parser/
app/src/parser/parseTextToActions.ts

Function:

import type { Action } from "../contracts/action";

export function parseTextToActions(text: string): Action[] {
  // temporary simple parser
  return [];
}

For now, returning [] is acceptable.


Step 2.4 — Create world state engine

Create:

app/src/world/
app/src/world/applyActions.ts

Function:

import type { Action } from "../contracts/action";
import type { ValidationResult } from "../contracts/validation";
import type { WorldState } from "../contracts/world";

export function applyActions(
  actions: Action[],
  results: ValidationResult[],
  worldState: WorldState
): WorldState {
  // apply only successful actions
  return worldState;
}

Phase 3 — Build First Deterministic Test Domain

Use a simple door/key room before anything complex.

Step 3.1 — Seed initial world

Create initial world state:

  • actor: player
  • room: room_start
  • door: door_1
  • key: key_1

Door starts locked.

Key starts in room.

Player starts in room.


Step 3.2 — Support action types

Truth engine should recognize:

inspect
take
open
move

Unknown action types fail with:

reason: "unknown_action"

Step 3.3 — Validate take action

Rules:

  • Actor must exist
  • Target must exist
  • Target must be in same location
  • Target must be takeable

Failure reasons:

  • actor_not_found
  • target_not_found
  • not_in_same_location
  • not_takeable

Step 3.4 — Validate open action

Rules:

  • Actor must exist
  • Target must exist
  • Target must be openable
  • If locked, actor must have matching key

Failure reasons:

  • actor_not_found
  • target_not_found
  • not_openable
  • locked_requires_key

Step 3.5 — Apply successful take

If take succeeds:

  • move item into actor inventory

Step 3.6 — Apply successful open

If open succeeds:

  • set door attribute open: true

Phase 4 — Wire Full Turn Processing

Step 4.1 — Create turn processor

Create:

app/src/turns/processTurn.ts

Function:

export async function processTurn(rawText: string): Promise<Turn> {
  // parse
  // validate
  // apply
  // persist
  // return turn
}

Step 4.2 — Enforce pipeline order

The turn processor must call:

parseTextToActions
validateActions
applyActions
persistTurn

In that order.

No layer may skip ahead.


Step 4.3 — Add debug response

API should return:

{
  rawText,
  actions,
  validation,
  worldState
}

This is for MVP debugging.


Phase 5 — Persistence

Step 5.1 — Add database tables

Minimum SQLite tables:

turns
actions
validation_results
entities
world_states

Step 5.2 — Persist every turn

Each call to processTurn must save:

  • raw text
  • parsed actions
  • validation results
  • resulting world state snapshot

Step 5.3 — Add reset endpoint

Add an endpoint to reset world state to seed state.

This is needed for testing.


Phase 6 — LLM Adapter Reintroduction

Only after deterministic flow works.

Step 6.1 — LLM parser adapter

Add optional LLM parser:

parseTextToActionsWithLLM(text: string, worldState: WorldState): Promise<Action[]>

It must output only valid Action[].


Step 6.2 — LLM narrative adapter

Add:

generateNarrative(turn: Turn, worldState: WorldState): Promise<string>

The narrative adapter may describe results but must not alter them.


Phase 7 — Frontend Debug UI

Step 7.1 — Show raw text input

User can submit a turn.


Step 7.2 — Show parsed actions

Display action JSON.


Step 7.3 — Show validation results

Display success/failure reasons.


Step 7.4 — Show world state

Display current world state JSON.


MVP Completion Criteria

MVP is complete when this works:

  1. User enters: take key
  2. Parser returns a take action
  3. Truth engine validates it
  4. World state moves key to inventory
  5. User enters: open door
  6. Truth engine verifies key ownership
  7. Door becomes open
  8. All steps are visible in debug UI
  9. All turns are persisted

Do Not Do Yet

Do not implement:

  • autonomous agents
  • complex memory retrieval
  • embeddings
  • relationship simulation
  • long-term summaries
  • branching timelines

Until the deterministic MVP is working.