[{"data":1,"prerenderedAt":1711},["ShallowReactive",2],{"doc-global_essentials\u002Fmiscellaneous\u002Fnarrative_system":3},{"id":4,"title":5,"body":6,"description":1701,"extension":1702,"meta":1703,"navigation":752,"path":1707,"seo":1708,"stem":1709,"__hash__":1710},"docs\u002Fdocs\u002Fglobal_essentials\u002Fmiscellaneous\u002Fnarrative_system.md","Narrative System",{"type":7,"value":8,"toc":1675},"minimark",[9,13,22,25,30,35,43,46,99,106,110,121,155,161,165,168,179,185,188,191,252,258,260,264,270,319,325,329,336,362,369,372,374,378,455,457,461,464,604,607,617,620,624,639,645,673,680,682,686,690,693,886,890,900,963,967,970,1113,1115,1119,1123,1128,1164,1169,1221,1226,1248,1251,1257,1263,1273,1279,1288,1294,1298,1554,1561,1563,1567,1671],[10,11,5],"h1",{"id":12},"narrative-system",[14,15,16,17,21],"p",{},"The narrative system generates dynamic text by selecting and composing ",[18,19,20],"strong",{},"segments"," based on live game state. Instead of writing branching if\u002Felse text blocks, you define reusable text fragments (segments) with conditions, and the engine picks the best match at runtime.",[23,24],"hr",{},[26,27,29],"h2",{"id":28},"key-concepts","Key Concepts",[31,32,34],"h3",{"id":33},"slots","Slots",[14,36,37,38,42],{},"A slot is a named insertion point in the narrative. When the engine encounters ",[39,40,41],"code",{},"|@slotId|"," in any text, it resolves the slot by selecting the best matching segment.",[14,44,45],{},"Each slot has:",[47,48,49,62],"table",{},[50,51,52],"thead",{},[53,54,55,59],"tr",{},[56,57,58],"th",{},"Field",[56,60,61],{},"Description",[63,64,65,79,89],"tbody",{},[53,66,67,73],{},[68,69,70],"td",{},[39,71,72],{},"id",[68,74,75,76,78],{},"Slot ID used in ",[39,77,41],{}," syntax",[53,80,81,86],{},[68,82,83],{},[39,84,85],{},"description",[68,87,88],{},"What this slot represents",[53,90,91,96],{},[68,92,93],{},[39,94,95],{},"fallback_content",[68,97,98],{},"Default text when no segment matches",[14,100,101,102,105],{},"Slots are defined in the editor under ",[18,103,104],{},"Narrative > Slots",".",[31,107,109],{"id":108},"states","States",[14,111,112,113,116,117,120],{},"States connect the narrative to live game data. Each state has a ",[18,114,115],{},"type"," (boolean, number, range, chooseOne, chooseMany) and a ",[18,118,119],{},"mode"," that controls matching behavior:",[47,122,123,133],{},[50,124,125],{},[53,126,127,130],{},[56,128,129],{},"Mode",[56,131,132],{},"Behavior",[63,134,135,145],{},[53,136,137,142],{},[68,138,139],{},[18,140,141],{},"Gate",[68,143,144],{},"Hard filter — if the segment's gate value doesn't match the runtime state, the segment is eliminated",[53,146,147,152],{},[68,148,149],{},[18,150,151],{},"Identity",[68,153,154],{},"Soft preference — match adds specificity (segment ranks higher), mismatch is ignored",[14,156,157,158,105],{},"States are defined in the editor under ",[18,159,160],{},"Narrative > States",[31,162,164],{"id":163},"tags","Tags",[14,166,167],{},"Tags are categorized value pools used by states. A tag entry groups related values under a category ID.",[14,169,170,171,174,175,178],{},"Example: a ",[39,172,173],{},"culture"," tag with values ",[39,176,177],{},"[\"military\", \"religious\", \"wild\", \"noble\"]",". An identity state can reference these values — segments tagged with matching culture values rank higher.",[14,180,181,182,105],{},"Tags are defined in the editor under ",[18,183,184],{},"Narrative > Tags",[31,186,187],{"id":20},"Segments",[14,189,190],{},"A segment is a text fragment that fills a specific slot. Each segment has:",[47,192,193,201],{},[50,194,195],{},[53,196,197,199],{},[56,198,58],{},[56,200,61],{},[63,202,203,212,222,232,242],{},[53,204,205,209],{},[68,206,207],{},[39,208,115],{},[68,210,211],{},"Which slot this fills",[53,213,214,219],{},[68,215,216],{},[39,217,218],{},"gates",[68,220,221],{},"Hard conditions — mismatch eliminates",[53,223,224,229],{},[68,225,226],{},[39,227,228],{},"identity",[68,230,231],{},"Soft preferences — match increases priority",[53,233,234,239],{},[68,235,236],{},[39,237,238],{},"weight",[68,240,241],{},"Frequency weight within same priority tier",[53,243,244,249],{},[68,245,246],{},[39,247,248],{},"content",[68,250,251],{},"The narrative content (supports full text pipeline)",[14,253,254,255,105],{},"Segments are defined in the editor under ",[18,256,257],{},"Narrative > Segments",[23,259],{},[26,261,263],{"id":262},"how-selection-works","How Selection Works",[14,265,266,267,269],{},"When ",[39,268,41],{}," is resolved, the engine runs this pipeline:",[271,272,273,280,286,292,298,304,310],"ol",{},[274,275,276,279],"li",{},[18,277,278],{},"Collect"," — find all segments assigned to this slot",[274,281,282,285],{},[18,283,284],{},"Filter by gates"," — eliminate segments whose gate values don't match runtime state",[274,287,288,291],{},[18,289,290],{},"Score identity"," — add specificity for each matching identity value",[274,293,294,297],{},[18,295,296],{},"Top tier"," — keep only the highest-specificity segments",[274,299,300,303],{},[18,301,302],{},"Anti-repeat"," — exclude the last 2 segments used for this slot",[274,305,306,309],{},[18,307,308],{},"Weighted random"," — pick one from remaining candidates using weight values",[274,311,312,315,316,318],{},[18,313,314],{},"Resolve content"," — process the chosen segment's ",[39,317,248],{}," through the full text pipeline",[14,320,321,322,324],{},"If no segments match, the slot's ",[39,323,95],{}," is used.",[31,326,328],{"id":327},"specificity-scoring","Specificity Scoring",[14,330,331,332,335],{},"Each filled state adds its ",[39,333,334],{},"specificity"," value (default 1) when matched:",[337,338,339,345,351,356],"ul",{},[274,340,341,344],{},[18,342,343],{},"Gate match",": +specificity",[274,346,347,350],{},[18,348,349],{},"Gate mismatch",": segment eliminated",[274,352,353,344],{},[18,354,355],{},"Identity match",[274,357,358,361],{},[18,359,360],{},"Identity mismatch",": ignored (no penalty)",[14,363,364,365,368],{},"For ",[39,366,367],{},"chooseMany"," identity states, each overlapping value adds specificity independently. A segment matching 3 out of 5 culture tags gets 3x the specificity bonus.",[14,370,371],{},"Segments with more matching conditions rank higher than generic ones, ensuring the most contextually appropriate text wins.",[23,373],{},[26,375,377],{"id":376},"state-types","State Types",[47,379,380,393],{},[50,381,382],{},[53,383,384,387,390],{},[56,385,386],{},"Type",[56,388,389],{},"Gate behavior",[56,391,392],{},"Identity behavior",[63,394,395,408,419,432,443],{},[53,396,397,402,405],{},[68,398,399],{},[39,400,401],{},"boolean",[68,403,404],{},"Must match exactly",[68,406,407],{},"Match adds specificity",[53,409,410,415,417],{},[68,411,412],{},[39,413,414],{},"number",[68,416,404],{},[68,418,407],{},[53,420,421,426,429],{},[68,422,423],{},[39,424,425],{},"range",[68,427,428],{},"Value must be within min\u002Fmax",[68,430,431],{},"Value within bounds adds specificity",[53,433,434,439,441],{},[68,435,436],{},[39,437,438],{},"chooseOne",[68,440,404],{},[68,442,407],{},[53,444,445,449,452],{},[68,446,447],{},[39,448,367],{},[68,450,451],{},"Must include ALL values",[68,453,454],{},"Each overlapping value adds specificity",[23,456],{},[26,458,460],{"id":459},"text-syntax","Text Syntax",[14,462,463],{},"Segment content supports the full text resolution pipeline:",[47,465,466,478],{},[50,467,468],{},[53,469,470,473,475],{},[56,471,472],{},"Syntax",[56,474,61],{},[56,476,477],{},"Example",[63,479,480,495,510,525,539,559,574,589],{},[53,481,482,487,490],{},[68,483,484],{},[39,485,486],{},"|placeholder|",[68,488,489],{},"Dynamic value substitution",[68,491,492],{},[39,493,494],{},"|faction| warriors approach",[53,496,497,502,505],{},[68,498,499],{},[39,500,501],{},"|placeholder(args)|",[68,503,504],{},"Placeholder with arguments",[68,506,507],{},[39,508,509],{},"|sire(1)| leads the group",[53,511,512,517,520],{},[68,513,514],{},[39,515,516],{},"if{condition}text.else{}text.fi",[68,518,519],{},"Conditional text",[68,521,522],{},[39,523,524],{},"if{has_leader}The leader speaks.fi",[53,526,527,531,534],{},[68,528,529],{},[39,530,41],{},[68,532,533],{},"Nested slot (recursive)",[68,535,536],{},[39,537,538],{},"|@approach| |@main|",[53,540,541,546,549],{},[68,542,543],{},[39,544,545],{},"|@slotId(args)|",[68,547,548],{},"Nested slot with arguments",[68,550,551,554,555,558],{},[39,552,553],{},"|@action(1)|"," — passes ",[39,556,557],{},"1"," to the segment",[53,560,561,566,569],{},[68,562,563],{},[39,564,565],{},"|$templateId|",[68,567,568],{},"Template reference",[68,570,571],{},[39,572,573],{},"|$greeting|",[53,575,576,581,584],{},[68,577,578],{},[39,579,580],{},"*text*",[68,582,583],{},"Bold",[68,585,586],{},[39,587,588],{},"*important*",[53,590,591,596,599],{},[68,592,593],{},[39,594,595],{},"**text**",[68,597,598],{},"Italic",[68,600,601],{},[39,602,603],{},"**whispered**",[14,605,606],{},"Slots can nest other slots, enabling hierarchical narrative composition:",[608,609,614],"pre",{"className":610,"code":612,"language":613},[611],"language-text","Root slot fallback: |@header| |@approach| |@main| |@after|\n","text",[39,615,612],{"__ignoreMap":616},"",[14,618,619],{},"Each sub-slot independently selects the best segment based on current state.",[31,621,623],{"id":622},"slot-arguments","Slot Arguments",[14,625,626,627,630,631,634,635,638],{},"Slots accept arguments via ",[39,628,629],{},"|@slotId(arg1, arg2)|",". Inside the selected segment, ",[39,632,633],{},"$1",", ",[39,636,637],{},"$2",", etc. are replaced with the corresponding argument values before the content is resolved.",[608,640,643],{"className":641,"code":642,"language":613},[611],"\u002F\u002F Parent segment calls a sub-slot with character index:\n|name(1)| approaches you. |@talk(1)| Then, |name(2)| joins in. |@talk(2)|\n\n\u002F\u002F Sub-slot `talk` segment content uses $1 as the character reference:\n|name($1)| talks about this and that\n",[39,644,642],{"__ignoreMap":616},[14,646,266,647,649,650,652,653,655,656,659,660,663,664,655,667,659,669,672],{},[39,648,553],{}," resolves, ",[39,651,633],{}," in the chosen segment becomes ",[39,654,557],{},", so ",[39,657,658],{},"|name($1)|"," → ",[39,661,662],{},"|name(1)|"," or becomes ",[39,665,666],{},"2",[39,668,658],{},[39,670,671],{},"|name(2)|",", depending on the passed parameter.",[14,674,675,676,679],{},"Arguments are substituted before string resolution, so ",[39,677,678],{},"$N"," works inside any placeholder, template or if condition syntax.",[23,681],{},[26,683,685],{"id":684},"scripting-api","Scripting API",[31,687,689],{"id":688},"registering-states","Registering States",[14,691,692],{},"Game scripts must register evaluator functions that provide runtime values for each state.",[608,694,698],{"className":695,"code":696,"language":697,"meta":616,"style":616},"language-js shiki shiki-themes github-light github-dark","\u002F\u002F Boolean state — does the troop have a leader?\ngame.registerNarrativeState(\"has_leader\", () => leaderId != null);\n\n\u002F\u002F Number state — exact match\ngame.registerNarrativeState(\"faction_tier\", () => currentFaction.tier);\n\n\u002F\u002F Range state — checked against segment's min\u002Fmax\ngame.registerNarrativeState(\"troop_size\", () => troopIds.length);\n\n\u002F\u002F Array state — provides available tags for chooseMany matching\ngame.registerNarrativeState(\"faction_culture\", () => {\n    const faction = factionsData.get(currentFactionId);\n    return faction?.culture_tags || [];\n});\n","js",[39,699,700,709,747,754,760,779,784,790,814,819,825,844,865,880],{"__ignoreMap":616},[701,702,705],"span",{"class":703,"line":704},"line",1,[701,706,708],{"class":707},"sJ8bj","\u002F\u002F Boolean state — does the troop have a leader?\n",[701,710,712,716,720,723,727,730,734,737,740,744],{"class":703,"line":711},2,[701,713,715],{"class":714},"sVt8B","game.",[701,717,719],{"class":718},"sScJk","registerNarrativeState",[701,721,722],{"class":714},"(",[701,724,726],{"class":725},"sZZnC","\"has_leader\"",[701,728,729],{"class":714},", () ",[701,731,733],{"class":732},"szBVR","=>",[701,735,736],{"class":714}," leaderId ",[701,738,739],{"class":732},"!=",[701,741,743],{"class":742},"sj4cs"," null",[701,745,746],{"class":714},");\n",[701,748,750],{"class":703,"line":749},3,[701,751,753],{"emptyLinePlaceholder":752},true,"\n",[701,755,757],{"class":703,"line":756},4,[701,758,759],{"class":707},"\u002F\u002F Number state — exact match\n",[701,761,763,765,767,769,772,774,776],{"class":703,"line":762},5,[701,764,715],{"class":714},[701,766,719],{"class":718},[701,768,722],{"class":714},[701,770,771],{"class":725},"\"faction_tier\"",[701,773,729],{"class":714},[701,775,733],{"class":732},[701,777,778],{"class":714}," currentFaction.tier);\n",[701,780,782],{"class":703,"line":781},6,[701,783,753],{"emptyLinePlaceholder":752},[701,785,787],{"class":703,"line":786},7,[701,788,789],{"class":707},"\u002F\u002F Range state — checked against segment's min\u002Fmax\n",[701,791,793,795,797,799,802,804,806,809,812],{"class":703,"line":792},8,[701,794,715],{"class":714},[701,796,719],{"class":718},[701,798,722],{"class":714},[701,800,801],{"class":725},"\"troop_size\"",[701,803,729],{"class":714},[701,805,733],{"class":732},[701,807,808],{"class":714}," troopIds.",[701,810,811],{"class":742},"length",[701,813,746],{"class":714},[701,815,817],{"class":703,"line":816},9,[701,818,753],{"emptyLinePlaceholder":752},[701,820,822],{"class":703,"line":821},10,[701,823,824],{"class":707},"\u002F\u002F Array state — provides available tags for chooseMany matching\n",[701,826,828,830,832,834,837,839,841],{"class":703,"line":827},11,[701,829,715],{"class":714},[701,831,719],{"class":718},[701,833,722],{"class":714},[701,835,836],{"class":725},"\"faction_culture\"",[701,838,729],{"class":714},[701,840,733],{"class":732},[701,842,843],{"class":714}," {\n",[701,845,847,850,853,856,859,862],{"class":703,"line":846},12,[701,848,849],{"class":732},"    const",[701,851,852],{"class":742}," faction",[701,854,855],{"class":732}," =",[701,857,858],{"class":714}," factionsData.",[701,860,861],{"class":718},"get",[701,863,864],{"class":714},"(currentFactionId);\n",[701,866,868,871,874,877],{"class":703,"line":867},13,[701,869,870],{"class":732},"    return",[701,872,873],{"class":714}," faction?.culture_tags ",[701,875,876],{"class":732},"||",[701,878,879],{"class":714}," [];\n",[701,881,883],{"class":703,"line":882},14,[701,884,885],{"class":714},"});\n",[31,887,889],{"id":888},"triggering-narrative-resolution","Triggering Narrative Resolution",[14,891,892,893,896,897,899],{},"Use ",[39,894,895],{},"game.resolveString()"," with the ",[39,898,41],{}," syntax:",[608,901,903],{"className":695,"code":902,"language":697,"meta":616,"style":616},"\u002F\u002F Resolve a single slot\nconst text = game.resolveString(\"|@root_scene|\").output;\n\n\u002F\u002F Slots work inside any resolved string\nconst message = game.resolveString(\"The |faction| warriors: |@approach|\").output;\n",[39,904,905,910,934,938,943],{"__ignoreMap":616},[701,906,907],{"class":703,"line":704},[701,908,909],{"class":707},"\u002F\u002F Resolve a single slot\n",[701,911,912,915,918,920,923,926,928,931],{"class":703,"line":711},[701,913,914],{"class":732},"const",[701,916,917],{"class":742}," text",[701,919,855],{"class":732},[701,921,922],{"class":714}," game.",[701,924,925],{"class":718},"resolveString",[701,927,722],{"class":714},[701,929,930],{"class":725},"\"|@root_scene|\"",[701,932,933],{"class":714},").output;\n",[701,935,936],{"class":703,"line":749},[701,937,753],{"emptyLinePlaceholder":752},[701,939,940],{"class":703,"line":756},[701,941,942],{"class":707},"\u002F\u002F Slots work inside any resolved string\n",[701,944,945,947,950,952,954,956,958,961],{"class":703,"line":762},[701,946,914],{"class":732},[701,948,949],{"class":742}," message",[701,951,855],{"class":732},[701,953,922],{"class":714},[701,955,925],{"class":718},[701,957,722],{"class":714},[701,959,960],{"class":725},"\"The |faction| warriors: |@approach|\"",[701,962,933],{"class":714},[31,964,966],{"id":965},"registering-placeholders","Registering Placeholders",[14,968,969],{},"Placeholders provide dynamic values for text substitution inside segment content:",[608,971,973],{"className":695,"code":972,"language":697,"meta":616,"style":616},"game.registerPlaceholder(\"faction\", () => currentFaction?.name || \"\");\ngame.registerPlaceholder(\"leader\", () => getCharacter(leaderId)?.getName() || \"\");\ngame.registerPlaceholder(\"count\", () => String(troopIds.length));\n\n\u002F\u002F With arguments\ngame.registerPlaceholder(\"sire\", (index) => getSire(index)?.getName() || \"\");\n\u002F\u002F Usage in segment: |sire(1)| and |sire(2)|\n",[39,974,975,1001,1034,1060,1064,1069,1108],{"__ignoreMap":616},[701,976,977,979,982,984,987,989,991,994,996,999],{"class":703,"line":704},[701,978,715],{"class":714},[701,980,981],{"class":718},"registerPlaceholder",[701,983,722],{"class":714},[701,985,986],{"class":725},"\"faction\"",[701,988,729],{"class":714},[701,990,733],{"class":732},[701,992,993],{"class":714}," currentFaction?.name ",[701,995,876],{"class":732},[701,997,998],{"class":725}," \"\"",[701,1000,746],{"class":714},[701,1002,1003,1005,1007,1009,1012,1014,1016,1019,1022,1025,1028,1030,1032],{"class":703,"line":711},[701,1004,715],{"class":714},[701,1006,981],{"class":718},[701,1008,722],{"class":714},[701,1010,1011],{"class":725},"\"leader\"",[701,1013,729],{"class":714},[701,1015,733],{"class":732},[701,1017,1018],{"class":718}," getCharacter",[701,1020,1021],{"class":714},"(leaderId)?.",[701,1023,1024],{"class":718},"getName",[701,1026,1027],{"class":714},"() ",[701,1029,876],{"class":732},[701,1031,998],{"class":725},[701,1033,746],{"class":714},[701,1035,1036,1038,1040,1042,1045,1047,1049,1052,1055,1057],{"class":703,"line":749},[701,1037,715],{"class":714},[701,1039,981],{"class":718},[701,1041,722],{"class":714},[701,1043,1044],{"class":725},"\"count\"",[701,1046,729],{"class":714},[701,1048,733],{"class":732},[701,1050,1051],{"class":718}," String",[701,1053,1054],{"class":714},"(troopIds.",[701,1056,811],{"class":742},[701,1058,1059],{"class":714},"));\n",[701,1061,1062],{"class":703,"line":756},[701,1063,753],{"emptyLinePlaceholder":752},[701,1065,1066],{"class":703,"line":762},[701,1067,1068],{"class":707},"\u002F\u002F With arguments\n",[701,1070,1071,1073,1075,1077,1080,1083,1087,1090,1092,1095,1098,1100,1102,1104,1106],{"class":703,"line":781},[701,1072,715],{"class":714},[701,1074,981],{"class":718},[701,1076,722],{"class":714},[701,1078,1079],{"class":725},"\"sire\"",[701,1081,1082],{"class":714},", (",[701,1084,1086],{"class":1085},"s4XuR","index",[701,1088,1089],{"class":714},") ",[701,1091,733],{"class":732},[701,1093,1094],{"class":718}," getSire",[701,1096,1097],{"class":714},"(index)?.",[701,1099,1024],{"class":718},[701,1101,1027],{"class":714},[701,1103,876],{"class":732},[701,1105,998],{"class":725},[701,1107,746],{"class":714},[701,1109,1110],{"class":703,"line":786},[701,1111,1112],{"class":707},"\u002F\u002F Usage in segment: |sire(1)| and |sire(2)|\n",[23,1114],{},[26,1116,1118],{"id":1117},"example-faction-aware-narrative","Example: Faction-Aware Narrative",[31,1120,1122],{"id":1121},"setup","Setup",[14,1124,1125,1127],{},[18,1126,164],{}," (Narrative > Tags):",[47,1129,1130,1139],{},[50,1131,1132],{},[53,1133,1134,1136],{},[56,1135,72],{},[56,1137,1138],{},"values",[63,1140,1141],{},[53,1142,1143,1147],{},[68,1144,1145],{},[39,1146,173],{},[68,1148,1149,634,1152,634,1155,634,1158,634,1161],{},[39,1150,1151],{},"military",[39,1153,1154],{},"religious",[39,1156,1157],{},"wild",[39,1159,1160],{},"noble",[39,1162,1163],{},"scavenger",[14,1165,1166,1168],{},[18,1167,109],{}," (Narrative > States):",[47,1170,1171,1181],{},[50,1172,1173],{},[53,1174,1175,1177,1179],{},[56,1176,72],{},[56,1178,119],{},[56,1180,115],{},[63,1182,1183,1195,1206],{},[53,1184,1185,1190,1193],{},[68,1186,1187],{},[39,1188,1189],{},"has_leader",[68,1191,1192],{},"gate",[68,1194,401],{},[53,1196,1197,1202,1204],{},[68,1198,1199],{},[39,1200,1201],{},"troop_size",[68,1203,1192],{},[68,1205,425],{},[53,1207,1208,1213,1215],{},[68,1209,1210],{},[39,1211,1212],{},"faction_culture",[68,1214,228],{},[68,1216,1217,1218,1220],{},"chooseMany (from ",[39,1219,173],{}," tag)",[14,1222,1223,1225],{},[18,1224,34],{}," (Narrative > Slots):",[47,1227,1228,1236],{},[50,1229,1230],{},[53,1231,1232,1234],{},[56,1233,72],{},[56,1235,95],{},[63,1237,1238],{},[53,1239,1240,1245],{},[68,1241,1242],{},[39,1243,1244],{},"approach",[68,1246,1247],{},"The creatures draw near.",[31,1249,187],{"id":1250},"segments-1",[14,1252,1253,1256],{},[18,1254,1255],{},"Generic approach"," (no gates, no identity):",[608,1258,1261],{"className":1259,"code":1260,"language":613},[611],"content: \"The |faction| troop approaches cautiously.\"\nspecificity: 0\n",[39,1262,1260],{"__ignoreMap":616},[14,1264,1265,1268,1269,1272],{},[18,1266,1267],{},"Military approach with leader"," (gate: has_leader=true, identity: faction_culture=",[701,1270,1271],{},"\"military\"","):",[608,1274,1277],{"className":1275,"code":1276,"language":613},[611],"content: \"The |leader| barks an order. The soldiers of the |faction| form ranks and march forward.\"\nspecificity: 2 (gate match + identity match)\n",[39,1278,1276],{"__ignoreMap":616},[14,1280,1281,1284,1285,1272],{},[18,1282,1283],{},"Wild approach, large troop"," (gate: troop_size min=4, identity: faction_culture=",[701,1286,1287],{},"\"wild\"",[608,1289,1292],{"className":1290,"code":1291,"language":613},[611],"content: \"A pack of |count| creatures bursts from the treeline, snarling and snapping.\"\nspecificity: 2 (gate match + identity match)\n",[39,1293,1291],{"__ignoreMap":616},[31,1295,1297],{"id":1296},"script","Script",[608,1299,1301],{"className":695,"code":1300,"language":697,"meta":616,"style":616},"const factionsData = game.getData(\"plugins_data\u002Fmy_plugin\u002Ffactions\", true);\n\n\u002F\u002F Register state evaluators\ngame.registerNarrativeState(\"has_leader\", () => ctx?.leaderId != null);\ngame.registerNarrativeState(\"troop_size\", () => ctx?.sireIds?.length ?? 0);\ngame.registerNarrativeState(\"faction_culture\", () => {\n    const faction = factionsData.get(ctx?.factionId);\n    return faction?.culture_tags || [];\n});\n\n\u002F\u002F Register text placeholders\ngame.registerPlaceholder(\"faction\", () => ctx?.factionName || \"\");\ngame.registerPlaceholder(\"leader\", () => getChar(ctx?.leaderId)?.getName() || \"\");\ngame.registerPlaceholder(\"count\", () => String(ctx?.sireIds?.length ?? 0));\n\n\u002F\u002F Resolve — engine picks the best segment automatically\nconst result = game.resolveString(\"|@approach|\").output;\n",[39,1302,1303,1329,1333,1338,1361,1388,1404,1419,1429,1433,1437,1442,1465,1495,1522,1527,1533],{"__ignoreMap":616},[701,1304,1305,1307,1310,1312,1314,1317,1319,1322,1324,1327],{"class":703,"line":704},[701,1306,914],{"class":732},[701,1308,1309],{"class":742}," factionsData",[701,1311,855],{"class":732},[701,1313,922],{"class":714},[701,1315,1316],{"class":718},"getData",[701,1318,722],{"class":714},[701,1320,1321],{"class":725},"\"plugins_data\u002Fmy_plugin\u002Ffactions\"",[701,1323,634],{"class":714},[701,1325,1326],{"class":742},"true",[701,1328,746],{"class":714},[701,1330,1331],{"class":703,"line":711},[701,1332,753],{"emptyLinePlaceholder":752},[701,1334,1335],{"class":703,"line":749},[701,1336,1337],{"class":707},"\u002F\u002F Register state evaluators\n",[701,1339,1340,1342,1344,1346,1348,1350,1352,1355,1357,1359],{"class":703,"line":756},[701,1341,715],{"class":714},[701,1343,719],{"class":718},[701,1345,722],{"class":714},[701,1347,726],{"class":725},[701,1349,729],{"class":714},[701,1351,733],{"class":732},[701,1353,1354],{"class":714}," ctx?.leaderId ",[701,1356,739],{"class":732},[701,1358,743],{"class":742},[701,1360,746],{"class":714},[701,1362,1363,1365,1367,1369,1371,1373,1375,1378,1380,1383,1386],{"class":703,"line":762},[701,1364,715],{"class":714},[701,1366,719],{"class":718},[701,1368,722],{"class":714},[701,1370,801],{"class":725},[701,1372,729],{"class":714},[701,1374,733],{"class":732},[701,1376,1377],{"class":714}," ctx?.sireIds?.",[701,1379,811],{"class":742},[701,1381,1382],{"class":732}," ??",[701,1384,1385],{"class":742}," 0",[701,1387,746],{"class":714},[701,1389,1390,1392,1394,1396,1398,1400,1402],{"class":703,"line":781},[701,1391,715],{"class":714},[701,1393,719],{"class":718},[701,1395,722],{"class":714},[701,1397,836],{"class":725},[701,1399,729],{"class":714},[701,1401,733],{"class":732},[701,1403,843],{"class":714},[701,1405,1406,1408,1410,1412,1414,1416],{"class":703,"line":786},[701,1407,849],{"class":732},[701,1409,852],{"class":742},[701,1411,855],{"class":732},[701,1413,858],{"class":714},[701,1415,861],{"class":718},[701,1417,1418],{"class":714},"(ctx?.factionId);\n",[701,1420,1421,1423,1425,1427],{"class":703,"line":792},[701,1422,870],{"class":732},[701,1424,873],{"class":714},[701,1426,876],{"class":732},[701,1428,879],{"class":714},[701,1430,1431],{"class":703,"line":816},[701,1432,885],{"class":714},[701,1434,1435],{"class":703,"line":821},[701,1436,753],{"emptyLinePlaceholder":752},[701,1438,1439],{"class":703,"line":827},[701,1440,1441],{"class":707},"\u002F\u002F Register text placeholders\n",[701,1443,1444,1446,1448,1450,1452,1454,1456,1459,1461,1463],{"class":703,"line":846},[701,1445,715],{"class":714},[701,1447,981],{"class":718},[701,1449,722],{"class":714},[701,1451,986],{"class":725},[701,1453,729],{"class":714},[701,1455,733],{"class":732},[701,1457,1458],{"class":714}," ctx?.factionName ",[701,1460,876],{"class":732},[701,1462,998],{"class":725},[701,1464,746],{"class":714},[701,1466,1467,1469,1471,1473,1475,1477,1479,1482,1485,1487,1489,1491,1493],{"class":703,"line":867},[701,1468,715],{"class":714},[701,1470,981],{"class":718},[701,1472,722],{"class":714},[701,1474,1011],{"class":725},[701,1476,729],{"class":714},[701,1478,733],{"class":732},[701,1480,1481],{"class":718}," getChar",[701,1483,1484],{"class":714},"(ctx?.leaderId)?.",[701,1486,1024],{"class":718},[701,1488,1027],{"class":714},[701,1490,876],{"class":732},[701,1492,998],{"class":725},[701,1494,746],{"class":714},[701,1496,1497,1499,1501,1503,1505,1507,1509,1511,1514,1516,1518,1520],{"class":703,"line":882},[701,1498,715],{"class":714},[701,1500,981],{"class":718},[701,1502,722],{"class":714},[701,1504,1044],{"class":725},[701,1506,729],{"class":714},[701,1508,733],{"class":732},[701,1510,1051],{"class":718},[701,1512,1513],{"class":714},"(ctx?.sireIds?.",[701,1515,811],{"class":742},[701,1517,1382],{"class":732},[701,1519,1385],{"class":742},[701,1521,1059],{"class":714},[701,1523,1525],{"class":703,"line":1524},15,[701,1526,753],{"emptyLinePlaceholder":752},[701,1528,1530],{"class":703,"line":1529},16,[701,1531,1532],{"class":707},"\u002F\u002F Resolve — engine picks the best segment automatically\n",[701,1534,1536,1538,1541,1543,1545,1547,1549,1552],{"class":703,"line":1535},17,[701,1537,914],{"class":732},[701,1539,1540],{"class":742}," result",[701,1542,855],{"class":732},[701,1544,922],{"class":714},[701,1546,925],{"class":718},[701,1548,722],{"class":714},[701,1550,1551],{"class":725},"\"|@approach|\"",[701,1553,933],{"class":714},[14,1555,1556,1557,1560],{},"When a military faction with a leader resolves ",[39,1558,1559],{},"|@approach|",", the military+leader segment wins (specificity 2 > 0). For a wild faction with 5 creatures, the wild+large segment wins instead. A faction with no matching segments falls back to the generic approach.",[23,1562],{},[26,1564,1566],{"id":1565},"quick-reference","Quick Reference",[47,1568,1569,1579],{},[50,1570,1571],{},[53,1572,1573,1576],{},[56,1574,1575],{},"I want to...",[56,1577,1578],{},"Do this",[63,1580,1581,1589,1597,1605,1613,1624,1634,1644,1655],{},[53,1582,1583,1586],{},[68,1584,1585],{},"Define insertion points",[68,1587,1588],{},"Narrative > Slots — create slots with fallback content",[53,1590,1591,1594],{},[68,1592,1593],{},"Define matching conditions",[68,1595,1596],{},"Narrative > States — create states with gate or identity mode",[53,1598,1599,1602],{},[68,1600,1601],{},"Define value categories",[68,1603,1604],{},"Narrative > Tags — create tag groups",[53,1606,1607,1610],{},[68,1608,1609],{},"Write narrative fragments",[68,1611,1612],{},"Narrative > Segments — assign to slot, set gates\u002Fidentity, write content",[53,1614,1615,1618],{},[68,1616,1617],{},"Connect to game state",[68,1619,1620,1623],{},[39,1621,1622],{},"game.registerNarrativeState(id, evaluator)"," in scripts",[53,1625,1626,1629],{},[68,1627,1628],{},"Provide text values",[68,1630,1631,1623],{},[39,1632,1633],{},"game.registerPlaceholder(id, func)",[53,1635,1636,1639],{},[68,1637,1638],{},"Trigger resolution",[68,1640,1641],{},[39,1642,1643],{},"game.resolveString(\"|@slotId|\")",[53,1645,1646,1649],{},[68,1647,1648],{},"Nest slots in content",[68,1650,892,1651,1654],{},[39,1652,1653],{},"|@otherSlot|"," inside segment content",[53,1656,1657,1660],{},[68,1658,1659],{},"Pass context to sub-slots",[68,1661,892,1662,1665,1666,634,1668,1670],{},[39,1663,1664],{},"|@slot(arg)|"," — segment uses ",[39,1667,633],{},[39,1669,637],{}," for arguments",[1672,1673,1674],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":616,"searchDepth":711,"depth":711,"links":1676},[1677,1683,1686,1687,1690,1695,1700],{"id":28,"depth":711,"text":29,"children":1678},[1679,1680,1681,1682],{"id":33,"depth":749,"text":34},{"id":108,"depth":749,"text":109},{"id":163,"depth":749,"text":164},{"id":20,"depth":749,"text":187},{"id":262,"depth":711,"text":263,"children":1684},[1685],{"id":327,"depth":749,"text":328},{"id":376,"depth":711,"text":377},{"id":459,"depth":711,"text":460,"children":1688},[1689],{"id":622,"depth":749,"text":623},{"id":684,"depth":711,"text":685,"children":1691},[1692,1693,1694],{"id":688,"depth":749,"text":689},{"id":888,"depth":749,"text":889},{"id":965,"depth":749,"text":966},{"id":1117,"depth":711,"text":1118,"children":1696},[1697,1698,1699],{"id":1121,"depth":749,"text":1122},{"id":1250,"depth":749,"text":187},{"id":1296,"depth":749,"text":1297},{"id":1565,"depth":711,"text":1566},"The narrative system generates dynamic text by selecting and composing segments based on live game state. Instead of writing branching if\u002Felse text blocks, you define reusable text fragments (segments) with conditions, and the engine picks the best match at runtime.","md",{"plugin":1704,"category":1705,"page":1706},"global_essentials","miscellaneous","narrative_system","\u002Fdocs\u002Fglobal_essentials\u002Fmiscellaneous\u002Fnarrative_system",{"title":5,"description":1701},"docs\u002Fglobal_essentials\u002Fmiscellaneous\u002Fnarrative_system","Uea_111OnGp85NabsV9xEMJJjLWEvosIxHBzywsNAE0",1779582261706]