Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-06 23:19:00 +08:00
parent d678929064
commit ddcb5d5c8c
241 changed files with 19805 additions and 2478 deletions

View File

@@ -10,6 +10,94 @@ import {
type WorldTemplateType,
} from './core';
import type {ItemStatProfile, ItemUseProfile} from './items';
import type {
ActorNarrativeProfile,
KnowledgeFact,
SceneNarrativeResidue,
ThemePack,
ThreadContract,
WorldStoryGraph,
} from './storyEngine';
export type CustomWorldCreatorInputMode = 'freeform' | 'card';
export type CustomWorldGenerationMode = 'fast' | 'full';
export type CustomWorldGenerationStatus = 'key_only' | 'complete';
export interface CreatorFactionSeed {
id: string;
name: string;
publicGoal: string;
tension: string;
notes: string;
locked?: boolean;
}
export interface CreatorCharacterSeed {
id: string;
name: string;
role: string;
publicMask: string;
hiddenHook: string;
relationToPlayer: string;
notes: string;
locked?: boolean;
}
export interface CreatorLandmarkSeed {
id: string;
name: string;
purpose: string;
mood: string;
secret: string;
locked?: boolean;
}
export interface ActorAnchor {
id: string;
name: string;
summary: string;
}
export interface LandmarkAnchor {
id: string;
name: string;
summary: string;
}
export interface CustomWorldCreatorIntent {
sourceMode: CustomWorldCreatorInputMode;
rawSettingText: string;
worldHook: string;
themeKeywords: string[];
toneDirectives: string[];
playerPremise: string;
openingSituation: string;
coreConflicts: string[];
keyFactions: CreatorFactionSeed[];
keyCharacters: CreatorCharacterSeed[];
keyLandmarks: CreatorLandmarkSeed[];
iconicElements: string[];
forbiddenDirectives: string[];
}
export interface CustomWorldAnchorPack {
worldSummary: string;
creatorIntentSummary: string;
lockedAnchorIds: string[];
keyConflictSummaries: string[];
keyFactionSummaries: string[];
keyCharacterAnchors: ActorAnchor[];
keyLandmarkAnchors: LandmarkAnchor[];
motifDirectives: string[];
}
export interface CustomWorldLockState {
worldLockedFields: string[];
lockedCharacterIds: string[];
lockedLandmarkIds: string[];
lockedConflictIds: string[];
lockedFactionIds: string[];
}
export interface CustomWorldRoleSkill {
id: string;
@@ -45,6 +133,7 @@ export interface CustomWorldRoleProfile {
skills: CustomWorldRoleSkill[];
initialItems: CustomWorldRoleInitialItem[];
attributeProfile?: RoleAttributeProfile;
narrativeProfile?: ActorNarrativeProfile | null;
}
export type CustomWorldNpcVisualRace = 'human' | 'elf' | 'orc' | 'goblin';
@@ -125,6 +214,7 @@ export interface CustomWorldLandmark {
imageSrc?: string;
sceneNpcIds: string[];
connections: CustomWorldSceneConnection[];
narrativeResidues?: SceneNarrativeResidue[] | null;
}
export interface CustomWorldProfile {
@@ -136,9 +226,22 @@ export interface CustomWorldProfile {
tone: string;
playerGoal: string;
templateWorldType: WorldTemplateType;
majorFactions: string[];
coreConflicts: string[];
attributeSchema: WorldAttributeSchema;
playableNpcs: CustomWorldPlayableNpc[];
storyNpcs: CustomWorldNpc[];
items: CustomWorldItem[];
landmarks: CustomWorldLandmark[];
themePack?: ThemePack | null;
storyGraph?: WorldStoryGraph | null;
knowledgeFacts?: KnowledgeFact[] | null;
threadContracts?: ThreadContract[] | null;
creatorIntent?: CustomWorldCreatorIntent | null;
anchorPack?: CustomWorldAnchorPack | null;
lockState?: CustomWorldLockState | null;
generationMode?: CustomWorldGenerationMode | null;
generationStatus?: CustomWorldGenerationStatus | null;
scenarioPackId?: string | null;
campaignPackId?: string | null;
}

View File

@@ -19,6 +19,7 @@ import type {
ScenePresetInfo,
} from './scene';
import type {CharacterChatRecord, QuestLogEntry, StoryMoment} from './story';
import type {CampaignState, ChapterState, StoryEngineMemoryState} from './storyEngine';
export interface GameRuntimeStats {
playTimeMs: number;
@@ -36,6 +37,11 @@ export interface GameState {
runtimeStats: GameRuntimeStats;
currentScene: string;
storyHistory: StoryMoment[];
storyEngineMemory?: StoryEngineMemoryState;
chapterState?: ChapterState | null;
campaignState?: CampaignState | null;
activeScenarioPackId?: string | null;
activeCampaignPackId?: string | null;
characterChats: Record<string, CharacterChatRecord>;
ambientIdleMode?: 'observe_signs';
lastObserveSignsSceneId?: string | null;
@@ -44,8 +50,7 @@ export interface GameState {
currentEncounter: Encounter | null;
npcInteractionActive: boolean;
currentScenePreset: ScenePresetInfo | null;
sceneMonsters: SceneHostileNpc[];
sceneHostileNpcs?: SceneHostileNpc[];
sceneHostileNpcs: SceneHostileNpc[];
playerX: number;
playerOffsetY: number;
playerFacing: 'left' | 'right';

View File

@@ -2,6 +2,10 @@ import type {WorldType} from './core';
import type {CustomWorldProfile} from './customWorld';
import type {InventoryItem} from './items';
import type {Encounter, NpcPersistentState, ScenePresetInfo} from './scene';
import type {
ActorNarrativeProfile,
CarrierStoryFingerprint,
} from './storyEngine';
export type RuntimeItemGenerationChannel =
| 'treasure'
@@ -33,6 +37,7 @@ export interface RuntimeItemMetadata {
relationAnchor?: RuntimeRelationAnchor;
sourceReason: string;
recentEventHook?: string;
storyFingerprint?: CarrierStoryFingerprint | null;
}
export interface RuntimeItemGenerationContext {
@@ -48,9 +53,11 @@ export interface RuntimeItemGenerationContext {
encounterNpcName: string | null;
encounterContextText: string | null;
relatedNpcState: NpcPersistentState | null;
relatedNpcNarrativeProfile?: ActorNarrativeProfile | null;
relatedScene: Pick<ScenePresetInfo, 'id' | 'name' | 'description' | 'treasureHints'> | null;
recentStorySummary: string;
recentActions: string[];
activeThreadIds?: string[] | null;
playerCharacterId: string;
playerBuildTags: string[];
playerBuildGaps: string[];
@@ -73,6 +80,7 @@ export interface RuntimeItemAiPromptInput {
encounterSummary: string;
relatedNpcSummary: string;
recentStorySummary: string;
activeThreadSummary?: string;
generationChannel: RuntimeItemGenerationChannel;
playerBuildDirection: string[];
playerBuildGaps: string[];
@@ -88,6 +96,12 @@ export interface RuntimeItemAiIntent {
desiredBuildTags: string[];
desiredFunctionalBias: Array<'heal' | 'mana' | 'cooldown' | 'guard' | 'damage'>;
tone: 'grim' | 'mysterious' | 'martial' | 'ritual' | 'survival';
visibleClue?: string;
witnessMark?: string;
unfinishedBusiness?: string;
hiddenHook?: string;
reactionHooks?: string[];
namingPattern?: string;
}
export interface RuntimeItemCompileBudget {

View File

@@ -23,6 +23,11 @@ import type {
CustomWorldSceneRelativePosition,
} from './customWorld';
import type {InventoryItem} from './items';
import type {
ActorNarrativeProfile,
CompanionStanceProfile,
SceneNarrativeResidue,
} from './storyEngine';
export interface NpcPersistentState {
affinity: number;
@@ -37,6 +42,7 @@ export interface NpcPersistentState {
knownAttributeRumors?: string[];
firstMeaningfulContactResolved?: boolean;
seenBackstoryChapterIds?: string[];
stanceProfile?: CompanionStanceProfile;
}
export interface CompanionState {
@@ -84,7 +90,6 @@ export interface Encounter {
specialBehavior?: 'initial_companion' | 'camp_companion';
xMeters?: number;
characterId?: string;
hostileNpcPresetId?: string;
monsterPresetId?: string;
initialAffinity?: number;
hostile?: boolean;
@@ -101,6 +106,7 @@ export interface Encounter {
initialItems?: CustomWorldRoleInitialItem[];
imageSrc?: string;
visual?: CustomWorldNpcVisual;
narrativeProfile?: ActorNarrativeProfile | null;
}
export interface SceneHostileNpc {
@@ -125,8 +131,6 @@ export interface SceneHostileNpc {
behaviorVectors?: RoleActionDefinition[];
}
export type SceneMonster = SceneHostileNpc;
export interface SceneNpc {
id: string;
name: string;
@@ -136,7 +140,6 @@ export interface SceneNpc {
title?: string;
gender?: CharacterGender;
characterId?: string;
hostileNpcPresetId?: string;
monsterPresetId?: string;
initialAffinity?: number;
hostile?: boolean;
@@ -154,6 +157,7 @@ export interface SceneNpc {
initialItems?: CustomWorldRoleInitialItem[];
imageSrc?: string;
visual?: CustomWorldNpcVisual;
narrativeProfile?: ActorNarrativeProfile | null;
}
export type SceneEncounterKind = 'npc' | 'treasure' | 'none';
@@ -217,11 +221,12 @@ export interface ScenePresetInfo {
imageSrc: string;
forwardSceneId?: string;
connectedSceneIds?: string[];
hostileNpcIds?: string[];
monsterIds?: string[];
npcs?: SceneNpc[];
treasureHints?: string[];
connections?: SceneConnectionInfo[];
narrativeResidues?: SceneNarrativeResidue[];
mutationStateText?: string | null;
currentPressureLevel?: 'low' | 'medium' | 'high' | 'extreme';
}
export interface SceneConnectionInfo {

View File

@@ -70,6 +70,9 @@ export interface QuestLogEntry {
issuerNpcId: string;
issuerNpcName: string;
sceneId: string | null;
actId?: string | null;
threadId?: string | null;
contractId?: string | null;
title: string;
description: string;
summary: string;
@@ -84,6 +87,9 @@ export interface QuestLogEntry {
activeStepId?: string | null;
visibleStage?: number;
hiddenFlags?: string[];
discoveredFactIds?: string[];
relatedCarrierIds?: string[];
consequenceIds?: string[];
}
export interface StoryDialogueTurn {

476
src/types/storyEngine.ts Normal file
View File

@@ -0,0 +1,476 @@
export type StoryThreadVisibility = 'visible' | 'hidden';
export type StoryMotifSemanticRole =
| 'institution'
| 'ritual'
| 'technology'
| 'taboo'
| 'ruin'
| 'memory'
| 'resource'
| 'creature';
export type SceneNarrativeRevealBudget = 'low' | 'medium' | 'high';
export type SceneNarrativeCadence =
| 'tense'
| 'curious'
| 'hostile'
| 'intimate'
| 'tragic'
| 'mysterious';
export interface ThemePack {
id: string;
displayName: string;
toneRange: string[];
institutionLexicon: string[];
tabooLexicon: string[];
artifactClasses: string[];
actorArchetypes: string[];
conflictForms: string[];
clueForms: string[];
namingPatterns: string[];
revealStyles: string[];
}
export interface StoryThread {
id: string;
title: string;
visibility: StoryThreadVisibility;
summary: string;
conflictType: string;
stakes: string;
involvedFactionIds: string[];
involvedActorIds: string[];
relatedLocationIds: string[];
}
export interface StoryScar {
id: string;
title: string;
pastEvent: string;
publicResidue: string;
hiddenTruth: string;
relatedActorIds: string[];
relatedLocationIds: string[];
}
export interface StoryMotif {
id: string;
label: string;
semanticRole: StoryMotifSemanticRole;
lexicalHints: string[];
}
export interface WorldStoryGraph {
visibleThreads: StoryThread[];
hiddenThreads: StoryThread[];
scars: StoryScar[];
motifs: StoryMotif[];
}
export interface ActorNarrativeProfile {
publicMask: string;
firstContactMask: string;
visibleLine: string;
hiddenLine: string;
contradiction: string;
debtOrBurden: string;
taboo: string;
immediatePressure: string;
relatedThreadIds: string[];
relatedScarIds: string[];
reactionHooks: string[];
}
export interface KnowledgeFact {
id: string;
title: string;
content: string;
ownerActorIds: string[];
relatedThreadIds: string[];
relatedScarIds: string[];
sourceType: 'actor' | 'item' | 'document' | 'scene' | 'monster' | 'quest';
visibility: 'public' | 'discoverable' | 'private' | 'forbidden';
sayability: 'direct' | 'indirect' | 'reactive_only';
aliases?: string[];
}
export interface VisibilitySlice {
factIds: string[];
sayableFactIds: string[];
inferredFactIds: string[];
forbiddenFactIds: string[];
misdirectionHints: string[];
}
export interface SceneNarrativeDirective {
primaryPressure: string;
activeThreadIds: string[];
foregroundActorIds: string[];
foregroundCarrierIds: string[];
revealBudget: SceneNarrativeRevealBudget;
emotionalCadence: SceneNarrativeCadence;
}
export interface CarrierStoryFingerprint {
visibleClue: string;
witnessMark: string;
unresolvedQuestion: string;
currentAppearanceReason: string;
relatedThreadIds: string[];
relatedScarIds: string[];
reactionHooks: string[];
}
export interface ThreadContractStep {
id: string;
title: string;
revealText: string;
completionSignalIds: string[];
optionalFactIds: string[];
}
export interface ThreadContract {
id: string;
threadId: string;
issuerActorId?: string | null;
narrativeType:
| 'investigation'
| 'escort'
| 'hunt'
| 'relationship'
| 'trial'
| 'recovery';
currentStepId: string | null;
visibleStage: number;
steps: ThreadContractStep[];
followupThreadIds: string[];
}
export interface StorySignal {
id: string;
signalType:
| 'enter_scene'
| 'leave_scene'
| 'talk_to_actor'
| 'obtain_carrier'
| 'inspect_scene'
| 'win_battle'
| 'give_item'
| 'accept_contract'
| 'resolve_contract_step';
actorId?: string | null;
sceneId?: string | null;
carrierId?: string | null;
threadIds?: string[];
}
export interface CompanionReactionRecord {
id: string;
characterId: string;
reactionType: 'approve' | 'disapprove' | 'concern' | 'silence' | 'curious';
reason: string;
relatedThreadIds: string[];
createdAt: string;
}
export interface SceneNarrativeResidue {
id: string;
title: string;
visibleClue: string;
linkedFactIds: string[];
linkedThreadIds: string[];
}
export interface ChapterState {
id: string;
title: string;
theme: string;
primaryThreadIds: string[];
stage: 'opening' | 'expansion' | 'turning_point' | 'climax' | 'aftermath';
chapterSummary: string;
}
export interface JourneyBeat {
id: string;
beatType:
| 'approach'
| 'investigation'
| 'camp'
| 'conflict'
| 'boss_prelude'
| 'climax'
| 'recovery';
title: string;
triggerThreadIds: string[];
recommendedSceneIds: string[];
emotionalGoal: string;
}
export interface CompanionArcState {
characterId: string;
arcTheme: string;
currentStage: 'closed' | 'guarded' | 'opening' | 'conflicted' | 'bonded' | 'resolved';
activeConflictTags: string[];
pendingEventIds: string[];
resolvedEventIds: string[];
}
export interface CampEvent {
id: string;
eventType:
| 'private_talk'
| 'party_banter'
| 'conflict'
| 'comfort'
| 'reveal'
| 'decision';
title: string;
participantCharacterIds: string[];
triggerReason: string;
relatedThreadIds: string[];
}
export interface WorldMutation {
id: string;
mutationType:
| 'scene_text'
| 'npc_attitude'
| 'shop_style'
| 'enemy_pressure'
| 'route_lock'
| 'route_unlock';
targetId: string;
reason: string;
relatedThreadIds: string[];
}
export interface FactionTensionState {
factionId: string;
temperature: number;
pressureSummary: string;
activeConflictThreadIds: string[];
}
export interface ChronicleEntry {
id: string;
category: 'chapter' | 'thread' | 'companion' | 'carrier' | 'scene' | 'world_event';
title: string;
summary: string;
relatedIds: string[];
createdAt: string;
}
export interface SetpieceDirective {
id: string;
title: string;
setpieceType: 'boss_prelude' | 'showdown' | 'climax' | 'aftermath';
relatedThreadIds: string[];
sceneFocusId?: string | null;
dramaticQuestion: string;
}
export interface CampaignState {
id: string;
title: string;
currentActId: string | null;
currentActIndex: number;
resolvedEndingId?: string | null;
}
export interface ActState {
id: string;
title: string;
actIndex: number;
theme: string;
primaryThreadIds: string[];
status: 'opening' | 'midgame' | 'late_game' | 'finale' | 'resolved';
}
export interface ConsequenceRecord {
id: string;
category: 'thread' | 'companion' | 'faction' | 'world' | 'ending_flag';
title: string;
summary: string;
weight: number;
relatedIds: string[];
irreversible: boolean;
}
export interface CompanionResolution {
characterId: string;
resolutionType:
| 'bonded'
| 'reconciled'
| 'estranged'
| 'departed'
| 'sacrificed'
| 'neutral';
summary: string;
relatedThreadIds: string[];
}
export interface EndingState {
id: string;
title: string;
endingType: 'heroic' | 'tragic' | 'bitter_sweet' | 'fractured' | 'ascendant';
summary: string;
contributingThreadIds: string[];
companionResolutions: CompanionResolution[];
worldOutcomeSummary: string;
}
export interface AuthorialConstraintPack {
toneRules: string[];
noGoPatterns: string[];
branchBudget: {
maxMajorDivergences: number;
maxEndingFamilies: number;
};
mandatoryThemes: string[];
requiredPayoffs: string[];
}
export interface BranchBudgetStatus {
currentMajorDivergences: number;
maxMajorDivergences: number;
currentEndingFamilies: number;
maxEndingFamilies: number;
pressure: 'low' | 'medium' | 'high';
}
export interface NarrativeQaIssue {
id: string;
severity: 'low' | 'medium' | 'high';
category: 'consistency' | 'pacing' | 'payoff' | 'branch_budget' | 'reveal_leak';
summary: string;
relatedIds: string[];
}
export interface NarrativeQaReport {
generatedAt: string;
issues: NarrativeQaIssue[];
summary: string;
}
export interface ScenarioPack {
id: string;
title: string;
version: string;
worldPackIds: string[];
campaignIds: string[];
sharedConstraintPackIds: string[];
}
export interface CampaignPack {
id: string;
scenarioPackId: string;
title: string;
authoringStyle: string;
campaignStateSeed: CampaignState;
actTemplates: ActState[];
requiredCompanionIds: string[];
}
export interface PlayerStyleProfile {
id: string;
preferenceWeights: {
story: number;
exploration: number;
combat: number;
companion: number;
collection: number;
};
dominantStyle:
| 'story_first'
| 'explorer'
| 'combat_driver'
| 'companion_bond'
| 'collector';
}
export interface SimulationRunResult {
id: string;
scenarioPackId: string;
campaignPackId: string;
seed: string;
endingId?: string | null;
activeThreadCountPeak: number;
fracturedCompanionCount: number;
issueCount: number;
summary: string;
}
export interface ReleaseGateReport {
generatedAt: string;
status: 'pass' | 'warn' | 'block';
summary: string;
blockingIssues: string[];
simulationCoverage: number;
}
export interface SaveMigrationManifest {
version: string;
requiredTransforms: string[];
backwardCompatible: boolean;
}
export interface NarrativeCodexEntry {
id: string;
title: string;
summary: string;
category: 'fact' | 'document' | 'scene' | 'thread' | 'companion' | 'ending';
relatedIds: string[];
}
export interface NarrativeCodexSection {
id: string;
title: string;
entries: NarrativeCodexEntry[];
}
export interface CompanionStanceProfile {
trust: number;
warmth: number;
ideologicalFit: number;
fearOrGuard: number;
loyalty: number;
currentConflictTag?: string | null;
recentApprovals: string[];
recentDisapprovals: string[];
}
export interface StoryEngineMemoryState {
discoveredFactIds: string[];
inferredFactIds?: string[];
activeThreadIds: string[];
resolvedScarIds: string[];
recentCarrierIds: string[];
recentSignalIds?: string[];
recentCompanionReactions?: CompanionReactionRecord[];
currentChapter?: ChapterState | null;
currentJourneyBeatId?: string | null;
currentJourneyBeat?: JourneyBeat | null;
companionArcStates?: CompanionArcState[];
worldMutations?: WorldMutation[];
chronicle?: ChronicleEntry[];
factionTensionStates?: FactionTensionState[];
currentCampEvent?: CampEvent | null;
currentSetpieceDirective?: SetpieceDirective | null;
continueGameDigest?: string | null;
campaignState?: CampaignState | null;
actState?: ActState | null;
consequenceLedger?: ConsequenceRecord[];
companionResolutions?: CompanionResolution[];
endingState?: EndingState | null;
authorialConstraintPack?: AuthorialConstraintPack | null;
branchBudgetStatus?: BranchBudgetStatus | null;
narrativeQaReport?: NarrativeQaReport | null;
narrativeCodex?: NarrativeCodexSection[];
playerStyleProfile?: PlayerStyleProfile | null;
simulationRunResults?: SimulationRunResult[];
releaseGateReport?: ReleaseGateReport | null;
saveMigrationManifest?: SaveMigrationManifest | null;
}