GM Tools: Encounter Forge
Encounter Forge is a Foundry VTT (v12-v14) module for D&D 5e that builds brand-new, balanced NPCs on demand, sized to your party, not a generic XP table or basic CR.
Instead of pulling a stat block from a compendium and hoping the CR math works out, Encounter Forge assembles a creature from a chassis, traits, actions, and (optionally) spells, then tunes its HP, AC, and damage toward the difficulty you asked for. The estimate is built from party HP and damage per round, so it can't account for tactics, terrain, control effects, or what your party has left in the tank. Read the creature before you run it.
Status: Actively developed. Foundry v12-v14, dnd5e system 4.0+ (verified on 5.3.3 and 6.0.5).
Why Encounter Forge is different
Most CR generators (and most homebrew stat blocks) work backwards from the Dungeon Master's Guide CR/XP tables: pick a CR, look up its "expected" HP and damage, and build (or pull) a creature around those numbers. The problem is that those tables assume a "standard" party, and most parties aren't standard. A CR 5 monster can be a joke or a TPK depending on who's sitting at the table.
Encounter Forge flips that process around:
- It estimates your party's actual damage output and durability, either generically from player count/level, or (with Calibrate to Party) from the real PCs' HP, AC, saves, and levels.
- It uses your chosen difficulty (Easy/Medium/Hard/Deadly) to compute an encounter envelope, the total HP and DPR (Damage Per Round) the enemy side needs, as a whole, to make that fight play out the way that difficulty implies.
- It splits that envelope across however many enemies you asked for (or amplifies it for a Solo Boss).
- Then it picks a CR, but only to decide what kind of creature this is (its chassis, traits, actions, spell tier, AC baseline). The CR does not set the creature's final HP or damage.
- Finally, it tunes the assembled creature's HP, AC, and damage output to hit its slice of the envelope exactly, so the finished NPC actually performs the way the difficulty setting promised (give or take).
In short: a "Hard" fight with two generated enemies and a "Hard" fight with four generated enemies should both feel like a Hard fight for your specific table, just split up differently. That's not something CR-table-driven generators do well enough, because they're matching a number on a chart, not your party, and not by output.
Not an AI generator - and works fully offline
Encounter Forge contains/requires no AI and no language model. Every creature is assembled procedurally from local JSON content pools (chassis archetypes, traits, actions, spells, names) using deterministic math you can read in plain JavaScript. There's nothing to configure, no API key, no tokens, and nothing about your world or your party is ever sent anywhere. Generating a creature needs no internet connection at all (other than being online to play obviously). The only network request is the update check, which asks GitHub if there's a newer release. It sends nothing else, and there's no setting to turn it off yet. The difficulty targets are deterministic from the same inputs, and the creature built to hit them varies (actions, traits, etc are randomizes via the pool of said items, so that differs or fights would be boring).
Features
- Procedural creature assembly - chassis (combat role), traits (defensive/offensive/passive/movement/senses/reactions/legendary), actions (melee/ranged/special), and spells are drawn from filterable (through code) content pools based on theme, CR tier, and chassis.
- Encounter envelope balancing - the whole encounter is sized to the party and difficulty first, then split across enemies. See The Math below.
- Calibrate to Party - pull real HP/AC/saves/level from your players' actors instead of using generic class-average estimates.
- Solo Boss mode - a single creature gets HP x1.5, DPR x1.3, AC +2, three guaranteed actions, and a draw from the legendary trait pool (legendary actions, lair actions, legendary resistance) so it can stand up to a full party alone.
- Live pre-generation readout - as you adjust player count, level, enemy count, and difficulty, the dialog shows the target CR, projected enemy/party DPR and HP, rounds-to-defeat/threaten, and a color-coded outlook (Easy/Manageable/Risky/Dangerous) - before you generate anything.
- Combat Intensity Calibration - a GM-only setting (Module Settings -> Combat Intensity Calibration) that shifts how aggressively enemies are sized across all difficulties, on a -3 to +3 scale. The live-preview chart in the calibration dialog shows how each step affects drain time across Easy/Medium/Hard/Deadly for a reference party. Intended for playtesting; leave at 0 for the baseline math.
- Precision Damage Scaling (Playtest) - a GM only module setting that changes how action dice counts are assigned during creature assembly. Standard fixed dice is on by default (original v1.0 implementation). When enabled, dice counts are calculated from each creature's actual DPR target rather than a fixed tier table, which tightens the ceiling on high-variance attacks like multi-hit melee actions whose tier-based dice would otherwise spike above the intended damage budget. Enable from Module Settings to compare balance outcomes against the v1.0 approach.
- Post-generation results summary - after generation, see each creature's actual rolled HP/AC/DPR and CR, plus the group's combined stats versus the party and the same outlook label, now based on real numbers.
- 10 themes / creature types - beast, undead, aberration, humanoid, elemental, fey, fiend, dragon, construct, monstrosity, or "any".
- 6 chassis archetypes - brute, lurker, skirmisher, controller, artillery, leader; each with its own stat spread, size progression, guaranteed skills, and action/spell affinities.
- Full Foundry actor integration - generated NPCs are written as dnd5e actors with stats, skills, senses, resistances/immunities, items (traits, actions, multiattack, spells), and legendary resources fully configured and ready to drop on a scene. Actions that apply conditions (grapple, prone, poisoned, frightened, etc.) include wired Foundry ActiveEffects so you can apply them after rolling.
- Disposition, folder, and naming controls - set token disposition, target an actor folder, and override the generated name/portrait/token image.
- Public API + Hooks - generate creatures from macros or other modules, open a pre-configured dialog for "generate me an NPC for this" workflows, and listen for generation lifecycle hooks. See the Wiki for full API docs and code samples.
- Remember last used - the dialog remembers your last settings (per-client), with a one-click reset from module settings.
- Custom content - export any feat, weapon, or spell from its item sheet into the trait/action/spell randomization pools, with a 2-column dialog covering CR range, theme tags, chassis affinity, damage, and (for spells) school and tier. ReSync re-pulls the latest stats and description from the original item with one click. A Custom Content manager (Actors sidebar or module settings) lets you enable/disable, ReSync, or delete entries, and export/import your whole custom content pool as a JSON file to share homebrew between worlds. See the Wiki for the full walkthrough.
The Math
This section is a summary, see the Wiki for the full breakdown with worked examples.
1. Party estimate
A party estimate is { dpr, hp }:
- Generic: derived from player count and average level using per-class DPR curves and a flat HP-per-level model.
- Calibrated: derived from the actual selected player actors' HP/AC/saves/levels.
2. The encounter envelope
Each difficulty maps to a target "rounds to defeat the enemy" and "rounds the enemy needs to threaten the party":
| Difficulty | Rounds to Defeat | Rounds to Threaten | Outlook |
|---|---|---|---|
| Easy | 2 | 6 | Easy |
| Medium | 3 | 5.4 | Manageable |
| Hard | 4 | 4.4 | Risky |
| Deadly | 5 | 3.5 | Dangerous |
groupHP = party.dpr * roundsToDefeat
groupDPR = (party.hp / roundsToThreaten) * economyFactor
economyFactor is a small multiplier (1 + 0.04 * (enemyCount - 1), capped at 1.2) that accounts for more attackers contributing slightly more total damage per round. Both totals are then divided by the enemy count to get each creature's HP/DPR target. Solo Boss further multiplies its single creature's targets by HP x1.5 / DPR x1.3.
3. Picking a flavor CR
The per-creature HP/DPR target is matched against a CR baseline table to find the closest CR. That CR drives only the chassis stat tier, trait/action/spell availability, and AC/save-DC baseline, it is not the creature's final HP or DPR.
4. Calibration pass
Every generated creature is then tuned to hit its envelope target exactly:
- HP is set directly to the target.
- AC is nudged by up to +/-2 based on how far the HP/DPR targets deviate from the chassis baseline.
- Damage is tuned in two passes: first an action may be swapped for a same-tier alternative closer to the target DPR, then any remaining gap is closed with a flat damage bonus/penalty spread across the creature's actions. It's solved so that, for Solo creatures, legendary "extra swings" land on target together with the base action damage rather than stacking on top of it.
The result: the pre-generation readout and the post-generation results should track closely, regardless of party size, level, enemy count, or Solo Boss status.
5. Precision Damage Scaling (optional)
A world setting (dprFirstDamage, Boolean, default off) changes how action dice counts are assigned during creature assembly. In the default tier-based path, each action uses a fixed dice formula per tier (example, a multi-hit melee action always uses its tier 3 formula at CR 5-7, regardless of the creature's actual DPR target). When enabled, dice counts are instead derived from the per-creature DPR target values: the engine extracts the die size from the action's damage_fallback and computes how many dice of that size are needed to hit the target DPR. Calibration (step 4) still runs afterward and closes any gap. The effect is a closer ceiling on high-variance attacks. An action at tier 3 uses the number of dice appropriate for that creature's budget, not a tier-baked maximum. Intended for playtesting balance comparisons so I can get input on the better approach; leave off for the standard v 1.0 path if you prefer.
6. Combat Intensity Calibration (optional)
A world setting (combatIntensity, integer -3 to +3, default 0) scales the roundsToThreaten target before the envelope is computed:
adjustedRoundsToThreaten = roundsToThreaten / (1 + offset * 0.12)
Positive offset = shorter drain time = enemies built to hit harder. Negative = longer drain = softer enemies. roundsToDefeat is never changed. Set from Module Settings -> Combat Intensity Calibration; changes take effect immediately for the next generation.
Installation
-
In Foundry's Add-on Modules tab, click Install Module.
-
Paste the manifest URL:
https://github.com/ElemorSeru/encounter-forge/releases/latest/download/module.json -
Enable GM Tools: Encounter Forge in your world's module list.
Or find it on the Foundry package list: GM Tools: Encounter Forge
Quick Start
- Open your Actors sidebar tab, you'll see a new Generate button (wizard hat icon) in the header.
- Set your party size/level (or enable Calibrate to Party and pick actors), pick a difficulty, theme, and number of enemies.
- Watch the live readout to see the projected outlook before you commit.
- Click Generate. New NPC actor(s) appear in your Actors directory, ready to drag onto a scene. Dumped into the root, or into a specific folder if you set one in the module settings.
- Review the results summary to see how the actual creature(s) compare to the initial projection.
Custom Content
Add your own homebrew feats, weapons, and spells to the randomization pools without editing JSON:
- Open a feat, weapon, or spell item's sheet and click the export icon (file with an arrow icon) in the window header (nex to the uuid, close, etc buttons).
- Fill in the export dialog information: category/type, CR range or spell tier, theme tags, chassis affinity, damage, etc. Hover the info icons next to each field for an explanation of what it affects.
- Click Export. The item is now part of your randomization pools.
- Edit the original item any time from the Custom Content manager, then use ReSync (from the same export dialog, or the Custom Content manager) to pull its latest description and stats into the entry.
- Open the Custom Content manager from the Actors sidebar (list icon) or Module Settings -> Custom Content to enable/disable, ReSync, or delete entries (and to Export/Import your whole custom content pool as a JSON file for sharing homebrew between worlds).
See the Wiki for the full field-by-field reference.
For Developers / Module Integrations
Encounter Forge exposes game.modules.get("encounter-forge").api with generate(), openDialog(), and openDialogFor(overrides) for generating creatures from your own module, plus encounterForge.generationStarted, encounterForge.creatureCreated, and encounterForge.encounterComplete hooks for reacting to generation.
The API also exposes exportCustomContent() and importCustomContent(payload), the same machinery behind the Custom Content manager's Export/Import buttons. If your module ships its own bestiary, item pack, or homebrew content, you can bundle an Encounter Forge content pack (JSON) alongside it and call importCustomContent() to drop those traits, actions, and spells straight into the randomization pools, no manual setup for the GM. This works well both ways: a lore/compendium module can feed Encounter Forge new content, and Encounter Forge's openDialogFor() can hand a freshly generated NPC straight back to that module's UI.
If you're building a module that could pair well with Encounter Forge, feel free to reach out, I'd be happy to help with integration or cross-promote.
Full API reference, options tables, hook payloads, and code samples live in the Wiki.
Screenshots
Roadmap
- Additional themes, chassis, and content pool expansions.
- Additional system support beyond dnd5e, contingent on interest and feasibility.
Have a feature request or found a balance edge case? Open an issue on GitHub or drop a note on Patreon. See links below.
About
Built and maintained by Elemor.
If you find this useful and want to support continued development, the Patreon link above is the best way to do that.
Bug reports and feature requests are welcome via the Issues tab.








