Stage 1: Stop displaying battalion backstories in client (#6001)

Disable the battalion backstory popup that appears on hover in the
Heroes & Battalions panel. This is the first stage of eliminating
battalion backstory LLM updates to reduce request volume.

The popup panel is now always hidden when hovering over battalions.
Hero backstories are unaffected.

See docs/eliminate-battalion-backstory-updates.md for the full plan.

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 21:40:20 -08:00
committed by GitHub
co-authored by Claude Opus 4.5
parent 7db53c56ac
commit 94b5d95dd3
2 changed files with 233 additions and 25 deletions
@@ -0,0 +1,230 @@
# Plan: Eliminate Battalion Backstory LLM Updates
## Goal
Dramatically reduce LLM request volume by eliminating battalion backstory updates. Battalions will retain:
- An **initial backstory** (generated once when created)
- A **history of events** (structured data, not LLM-generated prose)
The history will be fed into other LLM updates (hero backstories, chronicle) but will no longer be regenerated into prose for each battalion.
## Current System Overview
### Data Flow
```
Battalion Created → Initial Backstory LLM Request → Text stored
Battalion participates in events → Events accumulated → Update LLM Request → New text version
BattalionView.backstoryTextId → Client displays on hover
```
### Event Types (8 total)
Events are structured data already captured in `EventForBattalionBackstory`:
- OrganizedTroops, SuppressedBeasts, ApprehendedOutlaws, FoughtInBattle
- SuppressedRiot, Trained, Armed, SurvivedStarvation
Each event has date, province, hero, and event-specific details.
### Key Files
| Component | File |
|-----------|------|
| Unity Display | `Assets/Eagle/HeroesAndBattalionsPanelController.cs` |
| View Proto | `views/battalion_view.proto` |
| View Filter | `BattalionViewFilter.scala` |
| Update Action | `BattalionBackstoryUpdateAction.scala` |
| Update Generator | `BattalionBackstoryUpdateActionGenerator.scala` |
| Update Prompt | `BattalionBackstoryUpdatePromptGenerator.scala` |
| Event-to-Text | `BattalionBackstoryUpdatePromptGenerator.textForEvent()` |
| BattalionDescriptions | `BattalionDescriptions.scala` (used in other LLM prompts) |
---
## Stage 1: Stop Displaying Battalion Backstories in Client
**Goal:** Remove the UI that shows battalion backstory text on hover.
### Files to Modify
**`Assets/Eagle/HeroesAndBattalionsPanelController.cs`**
- In `BattalionLongHoverRowChanged()` (lines 143-169):
- Remove or comment out the backstory text population
- Keep the header/name display, remove `battalionPopupPanelBackstory.TextId = ...`
**Unity Scene/Prefab**
- Hide or remove the `battalionPopupPanelBackstory` UI element
### Verification
- [ ] Battalion hover popup no longer shows backstory text
- [ ] No errors when hovering over battalions
- [ ] Other battalion info (name, type, size) still displays
---
## Stage 2: Stop Populating Backstory in BattalionView
**Goal:** Stop sending backstory text ID to the client.
### Files to Modify
**`BattalionViewFilter.scala`**
- In `filteredBattalionViewBelongingToPlayer()`:
- Set `backstoryTextId = ""` instead of `battalion.backstoryTextId`
- In `limitedBattalionView()`:
- Same change
### Verification
- [ ] BattalionView messages have empty backstoryTextId
- [ ] Client handles empty backstoryTextId gracefully (Stage 1 should have removed the display)
---
## Stage 3: Remove backstory_text_id from BattalionView Proto
**Goal:** Clean up the proto schema.
### Files to Modify
**`views/battalion_view.proto`**
- Remove field: `string backstory_text_id = 7;`
- Mark field 7 as reserved to prevent reuse
**`Assets/Eagle/HeroesAndBattalionsPanelController.cs`**
- Remove any remaining references to `BackstoryTextId`
**`BattalionViewFilter.scala`**
- Remove `backstoryTextId` from view construction
### Verification
- [ ] Proto compiles
- [ ] C# client compiles without BackstoryTextId references
- [ ] Full test suite passes
---
## Stage 4: Replace Backstory Usage in BattalionDescriptions
**Goal:** When other LLM prompts need battalion context, use initial backstory + structured history instead of the latest LLM-generated prose.
### Current Usage in `BattalionDescriptions.scala`
```scala
def backstoryText(): TextGenerationResult =
battalion.backstoryVersions.lastOption
.map(v => clientTextStore.getText(v.textId))
.getOrElse(TextGenerationSuccess(""))
def fullDescription(): String =
s"${battalion.name} is $description. $backstoryText"
```
### New Approach
Create `historyDescription()` that:
1. Gets initial backstory (first version's text, if available)
2. Appends structured event history using the same `textForEvent()` logic from `BattalionBackstoryUpdatePromptGenerator`
```scala
def historyDescription(): String = {
val initialBackstory = battalion.backstoryVersions.headOption
.map(v => clientTextStore.getText(v.textId))
.collect { case TextGenerationSuccess(text) => text }
.getOrElse("")
val eventHistory = battalion.backstoryEvents
.map(textForEvent) // Reuse existing event-to-text conversion
.mkString(" ")
if eventHistory.isEmpty then initialBackstory
else s"$initialBackstory $eventHistory"
}
```
### Files to Modify
**`BattalionDescriptions.scala`**
- Add `historyDescription()` method
- Update `fullDescription()` to use `historyDescription()` instead of `backstoryText()`
- Keep `textForEvent()` logic (move from `BattalionBackstoryUpdatePromptGenerator` if needed)
**Move/refactor `textForEvent()`**
- Currently in `BattalionBackstoryUpdatePromptGenerator.scala` (lines 78-262)
- Move to `BattalionDescriptions.scala` or a shared utility
- This converts events to human-readable text without LLM
### Verification
- [ ] Other LLM prompts that use `BattalionDescriptions` still get meaningful context
- [ ] Chronicle updates include battalion history
- [ ] Hero backstory updates include relevant battalion history
---
## Stage 5: Stop Generating Battalion Backstory Updates
**Goal:** Remove the LLM request generation entirely.
### Files to Modify
**`BattalionBackstoryUpdateActionGenerator.scala`**
- Return empty Vector instead of generating actions
- Or delete the file entirely
**`BattalionBackstoryUpdateAction.scala`**
- Delete or mark as deprecated
**`BattalionBackstoryUpdatePromptGenerator.scala`**
- Delete or keep only `textForEvent()` if moved
**`LlmResolver.scala`**
- Remove case for `BattalionBackstoryUpdateRequest`
- Optionally throw error if somehow called
**`EndPlayerCommandsPhaseAction.scala`** (and other callers)
- Find where `BattalionBackstoryUpdateActionGenerator` is called
- Remove those calls
### Files to Search for Callers
```bash
grep -r "BattalionBackstoryUpdateAction" src/
```
### Verification
- [ ] No `BattalionBackstoryUpdateRequest` LLM requests generated
- [ ] Battalions still accumulate events (for history description)
- [ ] Full test suite passes
- [ ] Game runs without errors
---
## Future Considerations
### Keep or Remove Initial Backstory Generation?
The initial backstory (`BattalionInitialBackstoryRequest`) is generated once per battalion. Options:
1. **Keep it** - Low volume, provides flavor text for history
2. **Remove it** - Use a template-based approach instead
Recommendation: Keep for now, evaluate later based on volume.
### Event History Cleanup
Events currently accumulate forever on battalions. Consider:
- Capping event history length
- Summarizing old events
- Only keeping "significant" events
### Affected Tests
Search for tests that verify backstory update behavior:
```bash
bazel query 'tests(//src/test/scala/...) intersect rdeps(//src/test/scala/..., //src/main/scala/net/eagle0/eagle/library/actions/impl/action:battalion_backstory_update_action)'
```
---
## Summary of LLM Request Reduction
| Request Type | Current Volume | After Change |
|--------------|---------------|--------------|
| BattalionInitialBackstoryRequest | 1 per battalion | 1 per battalion (unchanged) |
| BattalionBackstoryUpdateRequest | ~N per battalion per game | **0** |
For a typical game with ~50 battalions and ~20 rounds, this could eliminate **hundreds** of LLM requests.
@@ -141,31 +141,9 @@ namespace eagle {
}
public void BattalionLongHoverRowChanged(int? newIndex) {
if (battalionPopupPanel == null) return;
if (newIndex is {} index) {
var battalion = Battalions.ElementAt(index);
if (!string.IsNullOrEmpty(battalion.BackstoryTextId)) {
// Populate header
if (battalionPopupPanelName != null) {
// Use PrefixedText for plain name; ready for NameTextId when available
battalionPopupPanelName.TextId = "";
battalionPopupPanelName.PrefixedText = battalion.Name;
}
if (battalionPopupPanelTypeIcon != null) {
var textures = GetComponentInParent<EagleCommonTextures>();
battalionPopupPanelTypeIcon.texture =
textures?.BattalionType(battalion.Type);
}
battalionPopupPanelBackstory.TextId = battalion.BackstoryTextId;
battalionPopupPanel.SetActive(true);
} else {
battalionPopupPanel.SetActive(false);
}
} else {
battalionPopupPanel.SetActive(false);
}
// Battalion backstory popups disabled - backstory updates are being eliminated
// to reduce LLM request volume. See docs/eliminate-battalion-backstory-updates.md
if (battalionPopupPanel != null) { battalionPopupPanel.SetActive(false); }
}
private void SetUpHeroesTable() {