Extract shared D&D combat policy
This commit is contained in:
@@ -5,9 +5,7 @@ into multiple scenes or use facts that are not supported by it.
|
|||||||
Return one kind, one concise title, and one concise summary. Choose exactly one
|
Return one kind, one concise title, and one concise summary. Choose exactly one
|
||||||
kind:
|
kind:
|
||||||
|
|
||||||
- combat: active combat materially organizes the scene, including
|
- combat: a scene classified as combat under the shared combat policy.
|
||||||
initiative-like exchanges or sustained hostile action. Planning a fight or
|
|
||||||
discussing a completed fight is not combat by itself.
|
|
||||||
- narrative: current-session in-world play that is not principally active
|
- narrative: current-session in-world play that is not principally active
|
||||||
combat, a prior-session recap, or sustained out-of-character session
|
combat, a prior-session recap, or sustained out-of-character session
|
||||||
discussion. This includes exploration, travel, dialogue, investigation,
|
discussion. This includes exploration, travel, dialogue, investigation,
|
||||||
@@ -20,15 +18,13 @@ kind:
|
|||||||
play.
|
play.
|
||||||
|
|
||||||
Narrative is the default for actual current-session gameplay that does not meet
|
Narrative is the default for actual current-session gameplay that does not meet
|
||||||
another definition. When the accepted chunk is mixed:
|
another definition. When the accepted chunk has no substantive active combat:
|
||||||
|
|
||||||
1. use combat when active combat is a substantive central activity, even with
|
1. use recap when recounting a previous session is the chunk's
|
||||||
brief setup, rules clarification, or immediate aftermath;
|
|
||||||
2. otherwise use recap when recounting a previous session is the chunk's
|
|
||||||
primary table purpose;
|
primary table purpose;
|
||||||
3. otherwise use meta when sustained out-of-character session discussion is
|
2. otherwise use meta when sustained out-of-character session discussion is
|
||||||
primary and in-world progression is no more than incidental; and
|
primary and in-world progression is no more than incidental; and
|
||||||
4. use narrative for all remaining current-session in-world play.
|
3. use narrative for all remaining current-session in-world play.
|
||||||
|
|
||||||
Brief table talk, dice resolution, rules clarification, jokes, or
|
Brief table talk, dice resolution, rules clarification, jokes, or
|
||||||
administrative comments do not make a gameplay scene meta. A short recollection
|
administrative comments do not make a gameplay scene meta. A short recollection
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ messages:
|
|||||||
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
content_file: ./sharedassets/common-dnd-transcript-chunk.md
|
||||||
cache_control:
|
cache_control:
|
||||||
type: ephemeral
|
type: ephemeral
|
||||||
|
- role: user
|
||||||
|
content_file: ./sharedassets/common-dnd-scene-combat-policy.md
|
||||||
- role: user
|
- role: user
|
||||||
content_file: ./instructions.md
|
content_file: ./instructions.md
|
||||||
cache_control:
|
cache_control:
|
||||||
|
|||||||
13
assets/dnd/shared/prompts/common-dnd-scene-combat-policy.md
Normal file
13
assets/dnd/shared/prompts/common-dnd-scene-combat-policy.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
Use `combat` only when substantive active combat materially organizes the
|
||||||
|
scene. Active combat includes initiative or turn exchanges, attacks, combat
|
||||||
|
spells, damage, saves, movement, or similarly sustained hostile action.
|
||||||
|
|
||||||
|
Do not use `combat` for planning or preparing for a possible fight; threats,
|
||||||
|
hostile dialogue, or a tense confrontation; immediate aftermath, looting,
|
||||||
|
healing, or discussion of a completed fight; a recap or in-world recollection
|
||||||
|
of earlier combat; or out-of-character rules discussion without active
|
||||||
|
encounter play.
|
||||||
|
|
||||||
|
When a chunk contains substantive active combat alongside brief setup, rules
|
||||||
|
clarification, interruption, phase transition, or immediate aftermath, classify
|
||||||
|
it as `combat`.
|
||||||
@@ -59,6 +59,8 @@ before beginning the next one.
|
|||||||
|
|
||||||
## Stage 1: Extract The Shared Combat Policy
|
## Stage 1: Extract The Shared Combat Policy
|
||||||
|
|
||||||
|
✅ Complete
|
||||||
|
|
||||||
### Goal
|
### Goal
|
||||||
|
|
||||||
Give the existing extractor and future validator one byte-identical owner for
|
Give the existing extractor and future validator one byte-identical owner for
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ var promptAssetManifest = shared.PromptAssetManifest{
|
|||||||
"common-dnd-identity.md",
|
"common-dnd-identity.md",
|
||||||
"common-dnd-references.md",
|
"common-dnd-references.md",
|
||||||
"common-dnd-transcript-chunk.md",
|
"common-dnd-transcript-chunk.md",
|
||||||
|
"common-dnd-scene-combat-policy.md",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,23 @@ func TestRegisterPromptAssetsPreparesSceneDescriptionPrompt(t *testing.T) {
|
|||||||
if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_scene_descriptions_llm.v1.json" {
|
if prepared.PromptID != PromptID || prepared.OutputContract.SchemaPath != "dnd_scene_descriptions_llm.v1.json" {
|
||||||
t.Fatalf("prepared prompt = %#v, want scene-description prompt identity and schema wiring", prepared)
|
t.Fatalf("prepared prompt = %#v, want scene-description prompt identity and schema wiring", prepared)
|
||||||
}
|
}
|
||||||
|
const combatPolicySentinel = "substantive active combat materially organizes the"
|
||||||
|
transcriptIndex := -1
|
||||||
|
policyIndex := -1
|
||||||
|
instructionsIndex := len(prepared.Messages) - 1
|
||||||
|
policyOccurrences := 0
|
||||||
|
for index, message := range prepared.Messages {
|
||||||
|
if strings.Contains(message.Content, "scene-description-transcript") {
|
||||||
|
transcriptIndex = index
|
||||||
|
}
|
||||||
|
if count := strings.Count(message.Content, combatPolicySentinel); count > 0 {
|
||||||
|
policyIndex = index
|
||||||
|
policyOccurrences += count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if policyOccurrences != 1 || policyIndex <= transcriptIndex || policyIndex >= instructionsIndex {
|
||||||
|
t.Fatalf("combat policy appeared %d times at message %d; want once after transcript message %d and before instructions message %d", policyOccurrences, policyIndex, transcriptIndex, instructionsIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPromptMetadataAndDiagnosticsDoNotContainRawAssets(t *testing.T) {
|
func TestPromptMetadataAndDiagnosticsDoNotContainRawAssets(t *testing.T) {
|
||||||
|
|||||||
@@ -23,13 +23,14 @@ import (
|
|||||||
|
|
||||||
func TestExtractionPromptComposition(t *testing.T) {
|
func TestExtractionPromptComposition(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
transcriptSentinel = "shared-transcript-sentinel"
|
transcriptSentinel = "shared-transcript-sentinel"
|
||||||
playersSentinel = "shared-players-sentinel"
|
playersSentinel = "shared-players-sentinel"
|
||||||
partySentinel = "shared-party-sentinel"
|
partySentinel = "shared-party-sentinel"
|
||||||
glossarySentinel = "shared-glossary-sentinel"
|
glossarySentinel = "shared-glossary-sentinel"
|
||||||
npcSentinel = "npc-registry-sentinel"
|
npcSentinel = "npc-registry-sentinel"
|
||||||
catalogSentinel = "spell-catalog-sentinel"
|
catalogSentinel = "spell-catalog-sentinel"
|
||||||
evidenceSentinel = "Transcript units are the only evidence"
|
evidenceSentinel = "Transcript units are the only evidence"
|
||||||
|
combatPolicySentinel = "substantive active combat materially organizes the"
|
||||||
)
|
)
|
||||||
registry := llm.NewAssetRegistry()
|
registry := llm.NewAssetRegistry()
|
||||||
if err := registerPromptAssets(registry); err != nil {
|
if err := registerPromptAssets(registry); err != nil {
|
||||||
@@ -43,11 +44,12 @@ func TestExtractionPromptComposition(t *testing.T) {
|
|||||||
"glossary": promptkit.Inline(glossarySentinel),
|
"glossary": promptkit.Inline(glossarySentinel),
|
||||||
}
|
}
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
promptID string
|
promptID string
|
||||||
promptVersion string
|
promptVersion string
|
||||||
inputs map[string]promptkit.ArtifactRef
|
inputs map[string]promptkit.ArtifactRef
|
||||||
suffixGroups [][]string
|
suffixGroups [][]string
|
||||||
|
middleSentinel string
|
||||||
}{
|
}{
|
||||||
{name: "npcs", promptID: npcextract.PromptID, promptVersion: npcextract.SchemaVersion, inputs: commonInputs, suffixGroups: [][]string{{evidenceSentinel}}},
|
{name: "npcs", promptID: npcextract.PromptID, promptVersion: npcextract.SchemaVersion, inputs: commonInputs, suffixGroups: [][]string{{evidenceSentinel}}},
|
||||||
{name: "locations", promptID: locationextract.PromptID, promptVersion: locationextract.SchemaVersion, inputs: commonInputs, suffixGroups: [][]string{{evidenceSentinel}}},
|
{name: "locations", promptID: locationextract.PromptID, promptVersion: locationextract.SchemaVersion, inputs: commonInputs, suffixGroups: [][]string{{evidenceSentinel}}},
|
||||||
@@ -55,7 +57,7 @@ func TestExtractionPromptComposition(t *testing.T) {
|
|||||||
"item_registry": promptkit.Inline(`{"items":[{"id":"item-registry-sentinel","name":"Torch"}]}`),
|
"item_registry": promptkit.Inline(`{"items":[{"id":"item-registry-sentinel","name":"Torch"}]}`),
|
||||||
}), suffixGroups: [][]string{{evidenceSentinel}, {"item-registry-sentinel"}}},
|
}), suffixGroups: [][]string{{evidenceSentinel}, {"item-registry-sentinel"}}},
|
||||||
{name: "item registry", promptID: itemregistryextract.PromptID, promptVersion: itemregistryextract.SchemaVersion, inputs: commonInputs, suffixGroups: [][]string{{evidenceSentinel}}},
|
{name: "item registry", promptID: itemregistryextract.PromptID, promptVersion: itemregistryextract.SchemaVersion, inputs: commonInputs, suffixGroups: [][]string{{evidenceSentinel}}},
|
||||||
{name: "scene descriptions", promptID: scenedescriptionextract.PromptID, promptVersion: scenedescriptionextract.SchemaVersion, inputs: commonInputs},
|
{name: "scene descriptions", promptID: scenedescriptionextract.PromptID, promptVersion: scenedescriptionextract.SchemaVersion, inputs: commonInputs, middleSentinel: combatPolicySentinel},
|
||||||
{
|
{
|
||||||
name: "combat turns",
|
name: "combat turns",
|
||||||
promptID: combatextract.PromptID,
|
promptID: combatextract.PromptID,
|
||||||
@@ -155,6 +157,12 @@ func TestExtractionPromptComposition(t *testing.T) {
|
|||||||
previousIndex = groupIndex
|
previousIndex = groupIndex
|
||||||
}
|
}
|
||||||
instructionIndex := len(prepared.Messages) - 1
|
instructionIndex := len(prepared.Messages) - 1
|
||||||
|
if testCase.middleSentinel != "" {
|
||||||
|
policyIndex := renderedInputMessageIndex(t, prepared.Messages, testCase.middleSentinel)
|
||||||
|
if policyIndex <= transcriptIndex || policyIndex >= instructionIndex {
|
||||||
|
t.Fatalf("combat policy rendered at message %d, want after transcript message %d and before instructions message %d", policyIndex, transcriptIndex, instructionIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
if instructionIndex <= previousIndex {
|
if instructionIndex <= previousIndex {
|
||||||
t.Fatalf("instructions rendered at message %d, want after lane input message %d", instructionIndex, previousIndex)
|
t.Fatalf("instructions rendered at message %d, want after lane input message %d", instructionIndex, previousIndex)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ var sharedPromptPaths = map[string]string{
|
|||||||
"common-dnd-identity.md": "prompts/common-dnd-identity.md",
|
"common-dnd-identity.md": "prompts/common-dnd-identity.md",
|
||||||
"common-dnd-transcript-full.md": "prompts/common-dnd-transcript-full.md",
|
"common-dnd-transcript-full.md": "prompts/common-dnd-transcript-full.md",
|
||||||
"common-dnd-transcript-chunk.md": "prompts/common-dnd-transcript-chunk.md",
|
"common-dnd-transcript-chunk.md": "prompts/common-dnd-transcript-chunk.md",
|
||||||
|
"common-dnd-scene-combat-policy.md": "prompts/common-dnd-scene-combat-policy.md",
|
||||||
"common-dnd-references.md": "prompts/common-dnd-references.md",
|
"common-dnd-references.md": "prompts/common-dnd-references.md",
|
||||||
"common-dnd-npc-registry.md": "prompts/common-dnd-npc-registry.md",
|
"common-dnd-npc-registry.md": "prompts/common-dnd-npc-registry.md",
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user