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

@@ -14,13 +14,17 @@ import {
resolveEncounterRecruitCharacter,
} from '../data/characterPresets';
import { getMonsterPresetById } from '../data/hostileNpcPresets';
import { createSceneMonstersFromIds } from '../data/hostileNpcs';
import { createSceneHostileNpcsFromIds } from '../data/hostileNpcs';
import {
describeConversationStyle as describeNpcConversationStyle,
describeDisclosureStage,
describeWarmthStage,
} from '../data/npcInteractions';
import { buildSceneEntityCatalogText, getScenePresetById } from '../data/scenePresets';
import {
buildSceneEntityCatalogText,
getSceneHostileNpcPresetIds,
getScenePresetById,
} from '../data/scenePresets';
import {
buildFunctionCatalogText,
getFunctionById,
@@ -31,7 +35,7 @@ import {
CharacterGender,
CustomWorldProfile,
FacingDirection,
SceneMonster,
SceneHostileNpc,
StoryMoment,
StoryOption,
WorldType,
@@ -147,9 +151,12 @@ function describeWorldForPrompt(world: WorldType, customWorldProfile?: CustomWor
: describeWorld(world);
}
function describeCustomWorldSection(customWorldProfile?: CustomWorldProfile | null) {
return customWorldProfile
? `自定义世界补充档案:\n${buildCustomWorldReferenceText(customWorldProfile)}`
function describeCustomWorldSection(context: StoryGenerationContext) {
return context.customWorldProfile
? `自定义世界补充档案:\n${buildCustomWorldReferenceText(context.customWorldProfile, {
activeThreadIds: context.activeThreadIds,
highlightNpcNames: context.encounterName ? [context.encounterName] : [],
})}`
: null;
}
@@ -292,6 +299,316 @@ function describeFirstMeaningfulContactDirective(context: StoryGenerationContext
].join('\n');
}
function hasVisibilityFact(
slice: StoryGenerationContext['visibilitySlice'],
factId: string,
) {
return Boolean(slice?.sayableFactIds.includes(factId));
}
function describeVisibilityFactLabel(factId: string) {
if (factId === 'publicMask') return '公开面';
if (factId === 'firstContactMask') return '首遇遮挡说辞';
if (factId === 'visibleLine') return '表层线';
if (factId === 'immediatePressure') return '当前压力';
if (factId === 'contradiction') return '说辞错位';
if (factId === 'hiddenLine') return '隐藏线';
if (factId === 'debtOrBurden') return '债务或负担';
if (factId === 'taboo') return '禁区';
if (factId.startsWith('thread:')) return '故事线程索引';
if (factId.startsWith('scar:')) return '旧痕索引';
if (factId.startsWith('chapter:')) return '已解锁背景摘要';
if (factId.startsWith('reaction:')) return '反应钩子';
return factId;
}
function describeVisibilitySliceSection(context: StoryGenerationContext) {
if (!context.visibilitySlice) {
return null;
}
const sayable = context.visibilitySlice.sayableFactIds
.map(describeVisibilityFactLabel)
.join('、');
const inferred = context.visibilitySlice.inferredFactIds
.map(describeVisibilityFactLabel)
.join('、');
const forbidden = context.visibilitySlice.forbiddenFactIds
.map(describeVisibilityFactLabel)
.join('、');
return [
'当前信息可见性切片:',
sayable ? `- 可直接进入本轮上下文:${sayable}` : null,
inferred ? `- 只能写成推测或缝隙:${inferred}` : null,
forbidden ? `- 禁止直接说破:${forbidden}` : null,
...(context.visibilitySlice.misdirectionHints ?? []).map(
(hint) => `- 误导/遮挡提示:${hint}`,
),
]
.filter(Boolean)
.join('\n');
}
function describeSceneNarrativeDirectiveSection(context: StoryGenerationContext) {
if (!context.sceneNarrativeDirective) {
return null;
}
const directive = context.sceneNarrativeDirective;
return [
'当前场景导演指令:',
`- 主压力:${directive.primaryPressure}`,
`- 激活线程:${directive.activeThreadIds.join('、') || '暂无'}`,
`- 揭示预算:${directive.revealBudget}`,
`- 情绪节奏:${directive.emotionalCadence}`,
].join('\n');
}
function describeRecentCompanionReactionsSection(context: StoryGenerationContext) {
if (!context.recentCompanionReactions?.length) {
return null;
}
return [
'最近一次同行反应:',
...context.recentCompanionReactions.slice(-3).map(
(reaction) =>
`- ${reaction.characterId} / ${reaction.reactionType}${reaction.reason}`,
),
].join('\n');
}
function describeRecentCarrierEchoesSection(context: StoryGenerationContext) {
if (!context.recentCarrierEchoes?.length) {
return null;
}
return [
'最近叙事载体回响:',
...context.recentCarrierEchoes.slice(0, 4).map((echo) => `- ${echo}`),
].join('\n');
}
function describeCampaignSection(context: StoryGenerationContext) {
if (!context.campaignState && !context.actState) {
return null;
}
return [
'当前战役状态:',
context.campaignState
? `- Campaign${context.campaignState.title}Act ${context.campaignState.currentActIndex + 1}`
: null,
context.actState
? `- 当前 Act${context.actState.title} / ${context.actState.status} / ${context.actState.theme}`
: null,
].filter(Boolean).join('\n');
}
function describeConsequenceLedgerSection(context: StoryGenerationContext) {
if (!context.consequenceLedger?.length) {
return null;
}
return [
'关键后果账本:',
...context.consequenceLedger.slice(-5).map(
(record) => `- ${record.title}(权重 ${record.weight}${record.summary}`,
),
].join('\n');
}
function describeConstraintSection(context: StoryGenerationContext) {
if (!context.authorialConstraintPack) {
return null;
}
const pack = context.authorialConstraintPack;
return [
'作者性约束:',
`- 基调规则:${pack.toneRules.join('、') || '暂无'}`,
`- 禁止模式:${pack.noGoPatterns.join('、') || '暂无'}`,
`- 必须回收:${pack.requiredPayoffs.join('、') || '暂无'}`,
context.branchBudgetPressure
? `- 当前分支预算压力:${context.branchBudgetPressure}`
: null,
].filter(Boolean).join('\n');
}
function describePackSection(context: StoryGenerationContext) {
if (!context.activeScenarioPack && !context.activeCampaignPack) {
return null;
}
return [
'当前内容包:',
context.activeScenarioPack
? `- Scenario Pack${context.activeScenarioPack.title} v${context.activeScenarioPack.version}`
: null,
context.activeCampaignPack
? `- Campaign Pack${context.activeCampaignPack.title} / ${context.activeCampaignPack.authoringStyle}`
: null,
].filter(Boolean).join('\n');
}
function describePlayerStyleSection(context: StoryGenerationContext) {
if (!context.playerStyleProfile) {
return null;
}
return [
'当前玩家画像:',
`- 风格:${context.playerStyleProfile.dominantStyle}`,
`- 倾向:剧情 ${context.playerStyleProfile.preferenceWeights.story} / 探索 ${context.playerStyleProfile.preferenceWeights.exploration} / 战斗 ${context.playerStyleProfile.preferenceWeights.combat} / 同伴 ${context.playerStyleProfile.preferenceWeights.companion} / 收集 ${context.playerStyleProfile.preferenceWeights.collection}`,
].join('\n');
}
function describeNarrativeQaSection(context: StoryGenerationContext) {
if (!context.narrativeQaReport) {
return null;
}
return [
'当前叙事 QA',
`- 摘要:${context.narrativeQaReport.summary}`,
...context.narrativeQaReport.issues.slice(0, 4).map(
(issue) => `- ${issue.severity}/${issue.category}${issue.summary}`,
),
context.releaseGateReport
? `- Release Gate${context.releaseGateReport.status} / ${context.releaseGateReport.summary}`
: null,
context.simulationRunResults?.length
? `- Simulation 覆盖:${context.simulationRunResults.length}`
: null,
].join('\n');
}
function describeChapterSection(context: StoryGenerationContext) {
if (!context.chapterState) {
return null;
}
return [
'当前章节状态:',
`- 标题:${context.chapterState.title}`,
`- 阶段:${context.chapterState.stage}`,
`- 主题:${context.chapterState.theme}`,
`- 摘要:${context.chapterState.chapterSummary}`,
].join('\n');
}
function describeJourneyBeatSection(context: StoryGenerationContext) {
if (!context.journeyBeat) {
return null;
}
return [
'当前旅程段落:',
`- 类型:${context.journeyBeat.beatType}`,
`- 标题:${context.journeyBeat.title}`,
`- 情绪目标:${context.journeyBeat.emotionalGoal}`,
].join('\n');
}
function describeCampEventSection(context: StoryGenerationContext) {
if (!context.currentCampEvent) {
return null;
}
return [
'当前可触发营地/旅途事件:',
`- 标题:${context.currentCampEvent.title}`,
`- 类型:${context.currentCampEvent.eventType}`,
`- 原因:${context.currentCampEvent.triggerReason}`,
].join('\n');
}
function describeSetpieceSection(context: StoryGenerationContext) {
if (!context.setpieceDirective) {
return null;
}
return [
'当前高光导演指令:',
`- 类型:${context.setpieceDirective.setpieceType}`,
`- 标题:${context.setpieceDirective.title}`,
`- 核心问题:${context.setpieceDirective.dramaticQuestion}`,
].join('\n');
}
function describeWorldMutationSection(context: StoryGenerationContext) {
if (!context.recentWorldMutations?.length) {
return null;
}
return [
'最近世界变化:',
...context.recentWorldMutations.slice(-4).map(
(mutation) =>
`- ${mutation.mutationType} / ${mutation.targetId}${mutation.reason}`,
),
].join('\n');
}
function describeFactionTensionSection(context: StoryGenerationContext) {
if (!context.recentFactionTensionStates?.length) {
return null;
}
return [
'当前阵营温度:',
...context.recentFactionTensionStates.slice(0, 4).map(
(tension) =>
`- ${tension.factionId} / 温度 ${tension.temperature}${tension.pressureSummary}`,
),
].join('\n');
}
function describeChronicleSection(context: StoryGenerationContext) {
if (!context.recentChronicleSummary?.trim()) {
return null;
}
return `近期旅程回顾:\n${context.recentChronicleSummary}`;
}
function buildCustomEncounterBackstoryLines(context: StoryGenerationContext) {
const encounterCustomProfile = context.encounterCustomProfile;
const narrativeProfile = context.encounterNarrativeProfile;
if (!encounterCustomProfile || !narrativeProfile) {
return ['对方有自己的来路与立场,只是暂时没有完全表现出来。'];
}
const lines: string[] = [];
if (hasVisibilityFact(context.visibilitySlice, 'publicMask')) {
lines.push(narrativeProfile.publicMask);
}
if (hasVisibilityFact(context.visibilitySlice, 'firstContactMask')) {
lines.push(narrativeProfile.firstContactMask);
}
if (hasVisibilityFact(context.visibilitySlice, 'visibleLine')) {
lines.push(narrativeProfile.visibleLine);
}
if (hasVisibilityFact(context.visibilitySlice, 'immediatePressure')) {
lines.push(narrativeProfile.immediatePressure);
}
(encounterCustomProfile.backstoryReveal?.chapters ?? []).forEach((chapter) => {
if (hasVisibilityFact(context.visibilitySlice, `chapter:${chapter.id}`)) {
const snippet =
chapter.contextSnippet || chapter.teaser || encounterCustomProfile.backstoryReveal?.publicSummary;
if (snippet) {
lines.push(snippet);
}
}
});
return lines.length > 0
? [...new Set(lines.filter(Boolean))]
: [encounterCustomProfile.backstoryReveal?.publicSummary ?? narrativeProfile.publicMask];
}
function describeBackstoryContext(label: string, snippets: string[]) {
const normalized = snippets
.map(snippet => snippet.trim())
@@ -408,7 +725,7 @@ function describeSkills(character: Character, context: StoryGenerationContext) {
function describeFrontEntity(
world: WorldType,
context: StoryGenerationContext,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
) {
const schema = resolveAttributeSchema(world, context.customWorldProfile);
if (context.encounterName) {
@@ -427,37 +744,20 @@ function describeFrontEntity(
const attributeProfile = encounterCharacter
? resolveCharacterAttributeProfile(encounterCharacter, world, context.customWorldProfile)
: inferEncounterAttributeProfile(world, context, `encounter:${context.encounterName}`, [
encounterCustomProfile?.personality ||
inferEncounterPersonality(
context.encounterContext,
context.encounterDescription,
),
encounterCustomProfile?.backstory ?? '',
encounterCustomProfile?.motivation ?? '',
encounterCustomProfile?.combatStyle ?? '',
...(encounterCustomProfile?.relationshipHooks ?? []),
...(encounterCustomProfile?.tags ?? []),
...(encounterCustomProfile?.backstoryReveal?.chapters ?? []).flatMap(
(chapter) => [
chapter.title,
chapter.teaser,
chapter.content,
chapter.contextSnippet,
],
),
...(encounterCustomProfile?.skills ?? []).flatMap((skill) => [
skill.name,
skill.summary,
skill.style,
]),
...(encounterCustomProfile?.initialItems ?? []).flatMap((item) => [
item.name,
item.category,
item.description,
...item.tags,
]),
]);
: inferEncounterAttributeProfile(world, context, `encounter:${context.encounterName}`, [
encounterCustomProfile?.personality ||
inferEncounterPersonality(
context.encounterContext,
context.encounterDescription,
),
context.encounterNarrativeProfile?.publicMask ?? '',
context.encounterNarrativeProfile?.visibleLine ?? '',
context.encounterNarrativeProfile?.immediatePressure ?? '',
...(context.visibilitySlice?.sayableFactIds.includes('contradiction')
&& context.encounterNarrativeProfile?.contradiction
? [context.encounterNarrativeProfile.contradiction]
: []),
]);
const title =
encounterCharacter?.title ??
encounterCustomProfile?.title ??
@@ -484,17 +784,7 @@ function describeFrontEntity(
world,
)
: encounterCustomProfile
? [
encounterCustomProfile.backstoryReveal?.publicSummary ??
'对方有自己的来路与立场。',
encounterCustomProfile.backstory,
...(
encounterCustomProfile.backstoryReveal?.chapters.map(
(chapter) =>
chapter.contextSnippet || chapter.content || chapter.teaser,
) ?? []
),
].filter((line): line is string => Boolean(line))
? buildCustomEncounterBackstoryLines(context)
: ['对方有自己的来路与立场,只是暂时没有完全表现出来。'];
const status = context.encounterKind === 'npc'
? context.isFirstMeaningfulContact
@@ -511,24 +801,37 @@ function describeFrontEntity(
`- 描述:${description}`,
...describeBackstoryContext('背景', backstoryLines).map(line => `- ${line}`),
`- 性格:${personality}`,
encounterCustomProfile?.motivation
context.encounterNarrativeProfile?.firstContactMask
? `- 首遇遮挡说辞:${context.encounterNarrativeProfile.firstContactMask}`
: null,
context.encounterNarrativeProfile?.visibleLine
? `- 表层线:${context.encounterNarrativeProfile.visibleLine}`
: null,
context.encounterNarrativeProfile?.immediatePressure
? `- 当前压力:${context.encounterNarrativeProfile.immediatePressure}`
: null,
context.visibilitySlice?.inferredFactIds.includes('contradiction') &&
context.encounterNarrativeProfile?.contradiction
? `- 可写成推测的错位:${context.encounterNarrativeProfile.contradiction}`
: null,
!context.encounterNarrativeProfile && encounterCustomProfile?.motivation
? `- 当前动机:${encounterCustomProfile.motivation}`
: null,
encounterCustomProfile?.combatStyle
!context.encounterNarrativeProfile && encounterCustomProfile?.combatStyle
? `- 战斗风格:${encounterCustomProfile.combatStyle}`
: null,
encounterCustomProfile?.relationshipHooks?.length
!context.encounterNarrativeProfile && encounterCustomProfile?.relationshipHooks?.length
? `- 关系切入口:${encounterCustomProfile.relationshipHooks.join('、')}`
: null,
encounterCustomProfile?.tags?.length
!context.encounterNarrativeProfile && encounterCustomProfile?.tags?.length
? `- 标签:${encounterCustomProfile.tags.join('、')}`
: null,
encounterCustomProfile?.skills?.length
!context.encounterNarrativeProfile && encounterCustomProfile?.skills?.length
? `- 自定义技能:${encounterCustomProfile.skills
.map((skill) => `${skill.name}(${skill.style})${skill.summary}`)
.join('')}`
: null,
encounterCustomProfile?.initialItems?.length
!context.encounterNarrativeProfile && encounterCustomProfile?.initialItems?.length
? `- 随身物:${encounterCustomProfile.initialItems
.map(
(item) =>
@@ -602,7 +905,7 @@ function describePlayerState(world: WorldType, character: Character, context: St
].filter(Boolean).join('\n');
}
function describeMonsters(monsters: SceneMonster[]) {
function describeMonsters(monsters: SceneHostileNpc[]) {
if (monsters.length === 0) {
return '当前没有可见敌对目标。';
}
@@ -646,7 +949,7 @@ function describeStoryHistory(history: StoryMoment[]) {
function _buildResolvedUserPrompt(
world: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
choice?: string,
@@ -674,12 +977,12 @@ function _buildResolvedUserPrompt(
const hasOpeningCampFollowupContext = hasProvidedNpcChatOptions
&& Boolean(context.openingCampBackground?.trim())
&& Boolean(context.openingCampDialogue?.trim());
const sceneMonsterIds = scene?.monsterIds ?? [];
const sceneMonsterIds = getSceneHostileNpcPresetIds(scene);
const battleCatalog = scene
? buildFunctionCatalogText({
...functionContext,
inBattle: true,
monsters: createSceneMonstersFromIds(world, sceneMonsterIds, context.playerX),
monsters: createSceneHostileNpcsFromIds(world, sceneMonsterIds, context.playerX),
})
: '';
const idleCatalog = buildFunctionCatalogText({
@@ -694,9 +997,26 @@ function _buildResolvedUserPrompt(
const sections = [
`世界:${describeWorldForPrompt(world, context.customWorldProfile)}`,
describePlayerState(world, character, context),
describeCustomWorldSection(context.customWorldProfile),
describeCustomWorldSection(context),
`主角性别:${describeGender(character.gender ?? 'unknown')}`,
describeFrontEntity(world, context, monsters),
describePackSection(context),
describePlayerStyleSection(context),
describeCampaignSection(context),
describeChapterSection(context),
describeJourneyBeatSection(context),
describeSceneNarrativeDirectiveSection(context),
describeVisibilitySliceSection(context),
describeConsequenceLedgerSection(context),
describeConstraintSection(context),
describeCampEventSection(context),
describeSetpieceSection(context),
describeRecentCompanionReactionsSection(context),
describeRecentCarrierEchoesSection(context),
describeWorldMutationSection(context),
describeFactionTensionSection(context),
describeChronicleSection(context),
describeNarrativeQaSection(context),
describeConversationSituationDirective(context),
describeEncounterConversationDirective(context),
context.encounterName ? `当前面前实体性别:${describeGender(getEncounterGender(context))}` : null,
@@ -824,7 +1144,7 @@ function describeProvidedOptions(options: StoryOption[]) {
function buildCatalogAwareUserPrompt(
world: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
choice?: string,
@@ -856,7 +1176,7 @@ function buildCatalogAwareUserPrompt(
? buildFunctionCatalogText({
...functionContext,
inBattle: true,
monsters: createSceneMonstersFromIds(world, scene?.monsterIds ?? [], context.playerX),
monsters: createSceneHostileNpcsFromIds(world, getSceneHostileNpcPresetIds(scene), context.playerX),
})
: '';
const idleCatalog = buildFunctionCatalogText({
@@ -871,9 +1191,26 @@ function buildCatalogAwareUserPrompt(
const sections = [
`世界:${describeWorldForPrompt(world, context.customWorldProfile)}`,
describePlayerState(world, character, context),
describeCustomWorldSection(context.customWorldProfile),
describeCustomWorldSection(context),
`主角性别:${describeGender(character.gender ?? 'unknown')}`,
describeFrontEntity(world, context, monsters),
describePackSection(context),
describePlayerStyleSection(context),
describeCampaignSection(context),
describeChapterSection(context),
describeJourneyBeatSection(context),
describeSceneNarrativeDirectiveSection(context),
describeVisibilitySliceSection(context),
describeConsequenceLedgerSection(context),
describeConstraintSection(context),
describeCampEventSection(context),
describeSetpieceSection(context),
describeRecentCompanionReactionsSection(context),
describeRecentCarrierEchoesSection(context),
describeWorldMutationSection(context),
describeFactionTensionSection(context),
describeChronicleSection(context),
describeNarrativeQaSection(context),
context.encounterName ? `当前面前实体性别:${describeGender(getEncounterGender(context))}` : null,
describeSkills(character, context),
`当前敌对目标状态:\n${describeMonsters(monsters)}`,
@@ -940,7 +1277,7 @@ function buildCatalogAwareUserPrompt(
export function buildUserPrompt(
world: WorldType,
character: Character,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
choice?: string,
@@ -954,7 +1291,7 @@ function buildResolvedNpcChatDialoguePrompt(
world: WorldType,
character: Character,
encounterName: string,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
topic: string,
@@ -963,8 +1300,26 @@ function buildResolvedNpcChatDialoguePrompt(
return [
`世界:${describeWorldForPrompt(world, context.customWorldProfile)}`,
describePlayerState(world, character, context),
describeCustomWorldSection(context),
`主角性别:${describeGender(character.gender ?? 'unknown')}`,
describeFrontEntity(world, context, monsters),
describePackSection(context),
describePlayerStyleSection(context),
describeCampaignSection(context),
describeChapterSection(context),
describeJourneyBeatSection(context),
describeSceneNarrativeDirectiveSection(context),
describeVisibilitySliceSection(context),
describeConsequenceLedgerSection(context),
describeConstraintSection(context),
describeCampEventSection(context),
describeSetpieceSection(context),
describeRecentCompanionReactionsSection(context),
describeRecentCarrierEchoesSection(context),
describeWorldMutationSection(context),
describeFactionTensionSection(context),
describeChronicleSection(context),
describeNarrativeQaSection(context),
`当前面前实体性别:${describeGender(getEncounterGender(context))}`,
describeSkills(character, context),
`当前敌对目标状态:\n${describeMonsters(monsters)}`,
@@ -988,7 +1343,7 @@ function buildNpcChatDialoguePrompt(
world: WorldType,
character: Character,
encounterName: string,
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
topic: string,
@@ -1010,7 +1365,7 @@ export function buildStrictNpcChatDialoguePrompt(
world: WorldType,
character: Character,
encounter: { npcName: string },
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
topic: string,
@@ -1030,17 +1385,35 @@ export function buildNpcRecruitDialoguePrompt(
world: WorldType,
character: Character,
encounter: { npcName: string },
monsters: SceneMonster[],
monsters: SceneHostileNpc[],
history: StoryMoment[],
context: StoryGenerationContext,
invitationText: string,
recruitSummary: string,
) {
return [
`世界:${describeWorld(world)}`,
`世界:${describeWorldForPrompt(world, context.customWorldProfile)}`,
describePlayerState(world, character, context),
describeCustomWorldSection(context),
`主角性别:${describeGender(character.gender ?? 'unknown')}`,
describeFrontEntity(world, context, monsters),
describePackSection(context),
describePlayerStyleSection(context),
describeCampaignSection(context),
describeChapterSection(context),
describeJourneyBeatSection(context),
describeSceneNarrativeDirectiveSection(context),
describeVisibilitySliceSection(context),
describeConsequenceLedgerSection(context),
describeConstraintSection(context),
describeCampEventSection(context),
describeSetpieceSection(context),
describeRecentCompanionReactionsSection(context),
describeRecentCarrierEchoesSection(context),
describeWorldMutationSection(context),
describeFactionTensionSection(context),
describeChronicleSection(context),
describeNarrativeQaSection(context),
`当前招募对象性别:${describeGender(getEncounterGender(context))}`,
describeSkills(character, context),
`当前敌对目标状态:\n${describeMonsters(monsters)}`,