---
name: efs-research-tree
description: Analyze Emperor of the Fading Suns (EFS) games with the EFS Research Tree site at https://efs.ignorelists.com/techgraph/. Covers mod data (units, techs, cities, sects, relics, combat tables), the user's game (visible units, stacks, known techs, structures), the galaxy and planet maps, and the combat simulator with batch runs. Use it for questions like "which enemy stacks are on planet X", "what can my house build", or "design a stack that beats this one with minimal losses".
---

# EFS Research Tree: guide for agents

- Site: https://efs.ignorelists.com/techgraph/ (for example `?mod=%2Fdata%2Fhyperion`)
- Written for an agent that has a browser or plain HTTP access and no source code.
- Updated 2026-10-16. Everything except [workflows.md](skill/workflows.md) section 8 ("Planned") exists on the site today.

This page is the short path. The details are in five topic files, each small enough to read in one go. Read only the one you need.

| File | Read it for |
| --- | --- |
| [skill/data.md](skill/data.md) | Sections 1–2: URL parameters, tabs, mods and the fallback chain, raw `.dat` files, prebuilt mod and galaxy JSON |
| [skill/saves.md](skill/saves.md) | Section 3: the user's game, the agent export format, the full export format, resolving unit rows, affiliation |
| [skill/combat-rules.md](skill/combat-rules.md) | Section 4: rounds, hit chance, damage, targeting, stack bonuses, withdrawal, sects, what the sim assumes |
| [skill/combat-sim.md](skill/combat-sim.md) | Section 5 and 6.0: setting up a battle, setup JSON, batch results, driving the page from JavaScript |
| [skill/workflows.md](skill/workflows.md) | W1–W5 step by step, pitfalls (7), planned features (8) |

Section numbers like "(4.5)" point into these files.

## Ground rules

- **Never read, parse or decode a `.sav` file, and never upload one anywhere.** In the user's own browser, with their game already loaded, work there directly. Anywhere else, ask the user for an **agent export**: **Manage**, select the game, **Export for Agents**, pick the planets the question is about, and send you `<name>(<id>)-agent.json`. One or two planets come to 20–50 KB (3.1, 3.2).
- **The page has no JavaScript API.** Use DOM queries and the stable hooks in 6.0; the page is far too large for accessibility-tree tools. All data files can also be fetched over HTTP (2.x).
- **Stacks:** ministry units and house units never share a stack, and relics only go in house stacks (4.5). Every attacker needs movement left, or must be towable with a towing unit in the stack (4.5). At most 20 slots, relics included.

## Quick path: beat a defended stack with minimal losses

Budget: about 15 minutes and 30–50 tool calls. The expensive steps are moving data through the model and reading long printouts. Keep both small.

1. **Read the user's game locally.** Parse the agent export (3.2) in a script and print only what you need: aggregates and short lists, never every unit.
   - A unit's row is the prebuilt unit row with `groupId == type` and `techLevel == tLvl` (3.4). Units with the same `stackId` form a stack.
   - The user's own units are those whose `controllingOwner ?? owner` is the export's `house`. Units with a `ministry` field belong to that ministry office, even when the house controls them (3.4).
2. **Pick the target.** The stack on the planet, its city (the `structures` row on the same hex; a VAU CITY multiplies non-Psy defense by 5), and each unit's weapons, armor, psy defense and rank (2.2).
3. **List the candidates.** Your units within a few jumps (4.9), house and ministry separately. Add buildable units (W3) at experience 0, and house relics if the house knows Theurgy.
4. **Shape the stack** using the rules that decide most fights:
   - **Psy ignores structure armor and fires first.** Against a city garrison, psy units do the killing (4.3).
   - **Screens.** Each enemy shooter targets a different unit, lowest rank first, and only units its weapon can hit (`target.dat`; in Hyperion Direct fire can't hit foot or mech). Add at least as many low-rank, high-armor units as the enemy has shooters able to hit them, so your psy units are never shot (4.4).
   - **A noble and an officer** add +15 accuracy and agility to the whole stack. **Elite** units are ×1.2, green ×0.8. **Assault** orders are ×1.2 attack and ×0.8 defense (4.3, 4.5).
   - **Relics** (house stacks only, need Theurgy): Rod of Holy Smiting ×1.5 strength, Eye of the Prophet +4 accuracy, Sabaton +4 agility (4.5).
5. **Simulate in the page** at `?mod=…&tab=pills-combatsim`:
   - Load only the target planet: filter the export's rows to that planet (about 20 KB) and load it with **Manage → New Game → Create from JSON** (6.0). The new game becomes the active one.
   - Pick the target in `#combat-sim-stack-source` and click **Assign defender**. That fills the hex terrain and the city. Then capture the setup once (below).
   - For each candidate, put its units into that setup's attacker, load it, and run 500 battles. That's one call per candidate, and about 7 s of compute.
6. **Rank** by attacker wins, then deaths per battle, then health lost per battle. Take one unit out at a time (ablation) to see what matters.
7. **Check legality** before you recommend: no house/ministry mix, relics only in house stacks, no jump units, every unit can move or be towed, and the units can reach the hex in time (4.5, 4.9).

```js
// Helpers from 6.0: feed a file picker and catch a download without a dialog.
function feedNextFilePicker(text, name = "data.json") {
  const original = HTMLInputElement.prototype.click;
  HTMLInputElement.prototype.click = function () {
    if (this.type !== "file") return original.call(this);
    HTMLInputElement.prototype.click = original;
    const transfer = new DataTransfer();
    transfer.items.add(new File([text], name, { type: "application/json" }));
    this.files = transfer.files;
    this.dispatchEvent(new Event("change"));
  };
}
function captureNextDownload() {
  return new Promise(resolve => {
    const original = HTMLAnchorElement.prototype.click;
    HTMLAnchorElement.prototype.click = function () {
      HTMLAnchorElement.prototype.click = original;
      fetch(this.href).then(response => response.text()).then(resolve);
    };
  });
}

// Once, after Assign defender: keep the setup with the defender, its hex and its city.
const saved = captureNextDownload();
document.querySelector('button[title="Save setup"]').click();
window.baseSetup = JSON.parse(await saved);

// A slot for the setup's units. typeId is the unit row id; ministry only for ministry units.
const slot = (typeId, sect, experience, health = 100, loyalty = 75, extra = {}) => ({
  magic: `unit-${window.baseSetup.mod}-1`, type: String(typeId), sect, ...extra,
  status: { health, loyalty, experience, spotted: true, routed: false,
            inCargo: false, inCarrier: false, plague: false, mightHavePlague: false } });

// Per candidate, in one call: load it, run the batch, return the summary.
async function runCandidate(units, { stance = "Neutral", battles = 500, knownTech } = {}) {
  const setup = structuredClone(window.baseSetup);
  setup.attacker.stance = stance;
  if (knownTech) setup.attacker.player.knownTech = { ...setup.attacker.player.knownTech, ...knownTech };
  setup.attacker.stack.units = units.map((unit, stackPos) => ({ stack: "A", stackPos, unit }));
  feedNextFilePicker(JSON.stringify(setup), "setup.json");
  document.querySelector('button[title="Load setup"]').click();
  await new Promise(resolve => setTimeout(resolve, 300));
  document.querySelector('input[aria-label="Number of batch combats"]').value = String(battles);
  document.querySelector('button[title="Run batch"]').click();
  return JSON.parse(document.querySelector(".combat-batch-card[data-batch-latest]").dataset.batchSummary);
}

// Example: two elite Eskatonic Heavy Mechs (row id 1281) on Normal orders, Theurgy known.
await runCandidate([slot(1281, "Esk", 2), slot(1281, "Esk", 2)], { knownTech: { theurgy: true } });
```

- The summary has `outcomes`, `meanRounds`, `attacker.deadPerBattle`, `attacker.healthLostPerBattle`, and a row per slot (5.3).
- Stances: `Neutral` (Normal orders), `Offensive` (Assault), `Defensive` (Feint). Sects: `Ort`, `SAe`, `Esk`, `Inc`, `Ave`, `Bro`, `Men`, `Sym`, `Vau`. A relic slot is `slot("r_<relic id>", "Ort", 0)` (5.2).
- Units that already moved this turn need `extra: { movePoints: n }`, or the movement warning cannot see them (5.2).
- Set the attacker's six known-tech flags yourself: the base setup has whatever the defender's import left (4.8).

## Combat in one screen

- **Hit:** `roll(0..100) + accuracy − agility >= 50`. The defender's terrain adds agility; attackers get none (4.2).
- **Damage:** attack number = health × strength (sect) × experience × orders × psychology tech × Rod. Defense number = health × armor or psy defense (sect) × experience × orders × structure armor factor (not against Psy) × defense relics. The ratio picks a `damage.dat` column, and 1-5 or worse does nothing in Hyperion (4.3).
- **Phases:** Hyperion land combat fires Psy ×2, Air ×2, Indirect ×2, Water ×4, Direct ×3, Close ×4 each round. A unit killed early never fires its later weapons (4.1).
- **Ending:** a side loses when it has no active combat units. A ship-led stack makes the fight one round long (4.1). Units withdraw on damage unless loyalty holds; a noble and an officer make that rare (4.6).
- **Who fights:** jump and space units surrender in land combat, and non-combat units don't fight (4.5).
