263 lines
8.7 KiB
JSON
263 lines
8.7 KiB
JSON
{
|
|
"modVersion": "0.28.49",
|
|
"title": "IdleSpectator interest scoring model",
|
|
"updated": "2026-07-18",
|
|
"note": "Edit weights below - InterestScoringConfig loads this file at mod start. Docs sections are human-facing only (ignored by Unity JsonUtility).",
|
|
|
|
"weights": {
|
|
"charWeightEventLed": 0.18,
|
|
"charWeightCharacterLed": 0.55,
|
|
"visualMultiplier": 12,
|
|
"noveltyMultiplier": 4,
|
|
|
|
"cutInMargin": 35,
|
|
"stickyCutInMargin": 50,
|
|
"combatUrgentEventStrengthMin": 95,
|
|
"combatUrgentCutInMargin": 20,
|
|
"stickyVarietyValveAfterSeconds": 16,
|
|
"stickyVarietyValveAfterSecondsPair": 8,
|
|
"stickyVarietyValveMaxWatchFrac": 0.4,
|
|
"stickyVarietyValveMargin": 20,
|
|
"fightBalanceLopsidedFloor": 0.28,
|
|
"fightBalanceFullAt": 0.55,
|
|
"warFrontBalanceBonus": 12,
|
|
"rotateSlack": 12,
|
|
"fillScoreMax": 55,
|
|
"noticeScoreMin": 70,
|
|
"hotScoreMin": 110,
|
|
"dwellFill": 8,
|
|
"dwellNormal": 15,
|
|
"dwellHot": 15,
|
|
"fillRotateSeconds": 8,
|
|
"minCameraDwell": 15,
|
|
|
|
"maxWatchMoment": 15,
|
|
"maxWatchStatus": 30,
|
|
"maxWatchGrief": 45,
|
|
"maxWatchCombat": 60,
|
|
"maxWatchDuel": 24,
|
|
"maxWatchEpic": 50,
|
|
|
|
"massFighterThreshold": 4,
|
|
"massBaseBonus": 28,
|
|
"massPerExtraFighter": 4,
|
|
"massExtraCap": 24,
|
|
"massCrowdThreshold": 8,
|
|
"duelMaxFighters": 2,
|
|
"duelNotablePairBonus": 55,
|
|
"duelSingleNotableBonus": 15,
|
|
"duelAnonymousPenalty": -24,
|
|
"skirmishMinFighters": 3,
|
|
"notableSkirmishPer": 12,
|
|
"notableSkirmishCap": 35,
|
|
|
|
"clusterDeathWeight": 10,
|
|
"clusterFighterWeight": 14,
|
|
"clusterNotableWeight": 22,
|
|
"clusterMassBonus": 28,
|
|
"clusterDuelPenalty": -8,
|
|
"clusterNotableDuelBonus": 45,
|
|
"clusterSingleNotableBonus": 12,
|
|
"preferNotableDuelMargin": 15,
|
|
"battleCharPerNotable": 18,
|
|
|
|
"metaFavorite": 22,
|
|
"metaKing": 18,
|
|
"metaLeader": 14,
|
|
"metaRenownDivisor": 120,
|
|
"metaRenownCap": 8,
|
|
"metaKillsFactor": 0.35,
|
|
"metaKillsCap": 12,
|
|
"metaLevelFactor": 0.4,
|
|
"metaLevelCap": 10,
|
|
"speciesSingletonBonus": 14,
|
|
"speciesRareBonus": 8,
|
|
"speciesRareMaxPop": 5,
|
|
|
|
"notableKills": 40,
|
|
"notableRenown": 200,
|
|
"extremeNotableKills": 80,
|
|
"extremeNotableRenown": 400,
|
|
|
|
"scannerAttackBonus": 40,
|
|
"scannerHotBonus": 18,
|
|
"scannerWarmBonus": 8,
|
|
"scannerSpectacleBonus": 12,
|
|
"scannerSpectacleIdleFloor": 48,
|
|
"scannerSpectaclePreferMargin": 28,
|
|
"scannerActionKillsFactor": 0.2,
|
|
"scannerActionKillsCap": 10,
|
|
"scannerWarriorBonus": 6,
|
|
"scannerSingletonBonus": 8,
|
|
"scannerCharKillsFactor": 0.15,
|
|
"scannerCharKillsCap": 8,
|
|
"scannerCharRenownDivisor": 120,
|
|
"scannerCharRenownCap": 8,
|
|
"scannerColdActionMul": 0.25,
|
|
"scannerColdCharMul": 0.35,
|
|
"scannerRankCharWeight": 0.18,
|
|
"characterLedEventMul": 0.35,
|
|
|
|
"eventLedTarget": 0.7,
|
|
"mixWindow": 20,
|
|
"hardCooldownSeconds": 12,
|
|
"fixedDwellBeatCooldownSeconds": 45,
|
|
"repeatArcPenaltyPer": 24,
|
|
"repeatArcPenaltyCap": 72,
|
|
"arcFrequencyWindow": 16,
|
|
"arcFairShare": 0.12,
|
|
"arcOverSharePenaltyMax": 48,
|
|
"arcUnderShareBoostMax": 22,
|
|
"arcCombatShareTarget": 0.35,
|
|
"arcCombatOverSharePenaltyMax": 36,
|
|
"enrichTopK": 8,
|
|
"metaCacheTtl": 2
|
|
},
|
|
|
|
"story": {
|
|
"nearTieEpsilon": 8,
|
|
"causalHeatHalfLifeSeconds": 180,
|
|
"causalHeatWeight": 6,
|
|
"causalRelatedWeight": 3,
|
|
"arcOwnershipBoost": 18,
|
|
"arcHoldMargin": 50,
|
|
"aftermathStrength": 62,
|
|
"aftermathMinWatch": 12,
|
|
"aftermathMaxWatch": 20,
|
|
"epilogueStrength": 48,
|
|
"epilogueMaxWatch": 14,
|
|
"ledgerCap": 16,
|
|
"ledgerWeight": 8,
|
|
"historyDensityWindowSeconds": 600,
|
|
"ledgerBleedLifeChapter": 0.4,
|
|
"ledgerBleedVillage": 0.82,
|
|
"sagaMcWeight": 6,
|
|
"sagaPreferWeight": 10,
|
|
"sagaGameFavoriteWeight": 8,
|
|
"sagaDullSeconds": 300,
|
|
"familyLifeBeatCooldownSeconds": 90,
|
|
"crisisWarParticipantMin": 8,
|
|
"crisisDisasterStrengthMin": 95,
|
|
"crisisOwnershipBoost": 22,
|
|
"crisisHoldMargin": 40,
|
|
"crisisLingerSeconds": 10,
|
|
"crisisDisasterLingerSeconds": 28,
|
|
"crisisMaxSeconds": 180,
|
|
"crisisCooldownSeconds": 45,
|
|
"crisisDisasterCooldownSeconds": 120,
|
|
"crisisEpilogueStrength": 52,
|
|
"crisisEpilogueMaxWatch": 16,
|
|
"softFillQuietSeconds": 18
|
|
},
|
|
|
|
"sources": [
|
|
"IdleSpectator/scoring-model.json",
|
|
"IdleSpectator/InterestScoringConfig.cs",
|
|
"IdleSpectator/InterestScoring.cs",
|
|
"IdleSpectator/WorldActivityScanner.cs",
|
|
"IdleSpectator/InterestDirector.cs",
|
|
"IdleSpectator/InterestVariety.cs",
|
|
"IdleSpectator/Events/Feeds/InterestFeeds.cs"
|
|
],
|
|
"designIntent": [
|
|
"Actions matter much more than characters.",
|
|
"Same action intensity → more important character wins (tie-break).",
|
|
"Multi-person fights outrank anonymous 1v1s.",
|
|
"Live CombatActive holds the camera until the scrap goes cold (see who wins).",
|
|
"Extremely urgent peers (EventStrength >= combatUrgentEventStrengthMin, or stickyCutInMargin) may cut mid-fight.",
|
|
"Extremely notable 1v1s (kings/favorites) can beat nameless melees.",
|
|
"Founding tips fire on meta outcomes (new_city / new_kingdom), not decision intent.",
|
|
"Consecutive same variety-arc shows stack a score penalty until a different arc is shown.",
|
|
"Rolling window: over-shown arcs lose score; absent/rare arcs gain score (rarer feels hotter).",
|
|
"Combat share above arcCombatShareTarget gets an extra monopoly penalty.",
|
|
"TotalScore is the sole ranking signal; typed rules (combat vs vignette) gate interrupts."
|
|
],
|
|
"pipeline": [
|
|
{
|
|
"id": "feeds",
|
|
"name": "Feeds → candidates",
|
|
"detail": "WorldLog, happiness, scanner, battle clusters, harness injects upsert InterestCandidate with EventStrength, CharacterSignificance, ParticipantCount, NotableParticipantCount (no Urgency tier)."
|
|
},
|
|
{
|
|
"id": "variety",
|
|
"name": "Variety pick",
|
|
"detail": "InterestVariety.Pick soft-splits EventLed vs CharacterLed (~70/30), applies novelty penalties, RecalcTotal, EnrichTopK, then takes #1 unless near-tie (story.nearTieEpsilon) among top-3. StoryPlanner ownership can prefer aftermath/epilogue beats."
|
|
},
|
|
{
|
|
"id": "story",
|
|
"name": "StoryPlanner",
|
|
"detail": "Owns short arcs (climax→aftermath→epilogue). Injects aftermath on climax cold, applies causal/ledger heat and arc ownership boosts. Never calls MoveCamera."
|
|
},
|
|
{
|
|
"id": "director",
|
|
"name": "Director switch",
|
|
"detail": "InterestDirector.CanSwitchTo gates camera changes via score margins (cutInMargin/rotateSlack), settle grace, and typed combat-vs-vignette cut-ins."
|
|
},
|
|
{
|
|
"id": "camera",
|
|
"name": "Camera + UI",
|
|
"detail": "CameraDirector.Watch + dossier story beats from action verbs (Fighting/War/Grief). Fill/ambient with no sharper beat leaves the reason row empty."
|
|
}
|
|
],
|
|
"totalScore": {
|
|
"file": "InterestScoring.RecalcTotal",
|
|
"formula": "EventStrength + scaleBonus + CharacterSignificance * charWeight + VisualConfidence * visualMultiplier + Novelty * noveltyMultiplier",
|
|
"charWeight": {
|
|
"EventLed": "weights.charWeightEventLed",
|
|
"CharacterLed": "weights.charWeightCharacterLed"
|
|
},
|
|
"scaleBonus": "See weights.mass* / duel* / notableSkirmish*",
|
|
"directorPolicy": {
|
|
"cutInMargin": "weights.cutInMargin",
|
|
"rotateSlack": "weights.rotateSlack",
|
|
"fillScoreMax": "weights.fillScoreMax",
|
|
"dwell": "weights.dwellFill / dwellNormal / dwellHot"
|
|
}
|
|
},
|
|
"workedExamples": [
|
|
{
|
|
"id": "fight_king_vs_peasant",
|
|
"scenario": "Same evt=70 Action fight; king char=40 notables=1 vs peasant char=5 notables=0",
|
|
"winner": "FightKing",
|
|
"harnessAssert": "ap12 interest_score_order"
|
|
},
|
|
{
|
|
"id": "melee_vs_anon_duel",
|
|
"scenario": "Same evt=55; 8-fighter anon melee vs 2-fighter anon duel",
|
|
"winner": "Melee",
|
|
"harnessAssert": "ap23 interest_score_order"
|
|
},
|
|
{
|
|
"id": "king_duel_vs_nameless_melee",
|
|
"scenario": "Same evt=55; king duel notables=2 vs 8-fighter nameless melee",
|
|
"winner": "DuelKings",
|
|
"harnessAssert": "ap33 interest_score_order"
|
|
}
|
|
],
|
|
"tweakKnobs": [
|
|
{
|
|
"id": "charWeightEventLed",
|
|
"effect": "How much who-you-are matters on event scenes"
|
|
},
|
|
{
|
|
"id": "massBaseBonus",
|
|
"effect": "How strongly mass fights beat thin duels"
|
|
},
|
|
{
|
|
"id": "duelNotablePairBonus",
|
|
"effect": "How easily king duels outrank nameless melees"
|
|
},
|
|
{
|
|
"id": "duelAnonymousPenalty",
|
|
"effect": "Penalty on anonymous 1v1"
|
|
},
|
|
{
|
|
"id": "clusterFighterWeight",
|
|
"effect": "Changes battle EventStrength seed"
|
|
},
|
|
{
|
|
"id": "eventLedTarget",
|
|
"effect": "Soft mix toward actions vs character vignettes"
|
|
}
|
|
]
|
|
}
|