Public 3d6vsTN combat encounter analyzer with editable fighters, rule toggles, and Monte Carlo simulation. https://pages.gi7b.org/gi7b/3d6vsTN-combat-analyzer/
  • JavaScript 87.3%
  • HTML 7%
  • CSS 3.5%
  • Python 2.1%
Find a file
Justin c2f5e42861 docs: split DIALOGUE.md into per-day dated logs, dedupe backlog
Migrated all 15 existing entries verbatim into
logs/260710-3d6vsTN-combat-analyzer-log.md and
logs/260711-3d6vsTN-combat-analyzer-log.md (nothing deleted, only
relocated) per the updated Dialogue Method convention. DIALOGUE.md
now holds only a deduplicated current-state backlog table and a
changelog index - previously the same "done" items were copy-pasted
into all 15 entries' backlog blocks, which is exactly the kind of
repetition that biases a model re-reading the file every session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 00:27:49 +08:00
logs docs: split DIALOGUE.md into per-day dated logs, dedupe backlog 2026-07-12 00:27:49 +08:00
server fix(server): move /api/health before catch-all route 2026-07-11 19:17:29 +08:00
src feat(totm,ui): implement situation modifiers + widen 2x2 layout 2026-07-11 23:53:46 +08:00
tests feat(totm,ui): implement situation modifiers + widen 2x2 layout 2026-07-11 23:53:46 +08:00
.gitignore feat(server): add Express backend, auth, AI playtester, snapshots 2026-07-11 19:14:53 +08:00
deploy.py fix(deploy): use temp directory outside repo root for build artifacts 2026-07-10 00:05:00 +08:00
DIALOGUE.md docs: split DIALOGUE.md into per-day dated logs, dedupe backlog 2026-07-12 00:27:49 +08:00
docker-compose.yml feat(server): add Express backend, auth, AI playtester, snapshots 2026-07-11 19:14:53 +08:00
index.html docs: split DIALOGUE.md into per-day dated logs, dedupe backlog 2026-07-12 00:27:49 +08:00
README.md docs: correct public deployment status and log verification pass 2026-07-11 20:51:03 +08:00
RULES-NOTES.md feat(ui): frame encounter as PC Team vs NPC Team 2026-07-11 21:21:34 +08:00
smoke-test.js feat(terrain): add Random and Random (2d6) options to terrain dropdown 2026-07-11 21:39:08 +08:00
TOTM-GUIDE.md feat(grammar-of-position): apply movement vocabulary 2026-07-11 18:35:14 +08:00

3d6 vs TN Combat Analyzer

A static HTML/JS Monte Carlo combat simulator for the gi7b 3d6-vs-target-number tabletop RPG system.

Features

  • 3d6 vs TN attack, defense, damage, and toughness resolution
  • Condition box tracking with spillover and injury penalties
  • Equipment tables for weapons, armor, and shields
  • Sample fighters and adversary templates
  • Rule toggle modules
  • Action Economy modes: Standard (one attack/round), Simple AE (2 actions: 1 attack + 1 defense), and Complex AE (4 actions: 2 attacks + 2 defenses)
  • Three-column simulator layout with fighter editor, encounter builder, and results
  • Chart.js visualizations and JSON/CSV export

Action Economy Modes

The simulator supports three encounter-resolution modes, selected from the Mode dropdown. Simple AE is the default: it turns superiority aggregation on, so multiple incoming attacks against one defender are resolved with a single Defense Save. Switch to Standard to turn aggregation off and use the canonical per-attack loop.

Mode Actions per round Default split Aggregation Resolution
Standard Off One resolveAttack per fighter per round (canonical baseline).
Simple AE 2 1 attack + 1 defense On Each fighter attacks once and has one built-in defense reaction. All incoming attacks on the same defender are aggregated into one Defense Save.
Complex AE 4 2 attacks + 2 defenses On Each fighter has four actions; the simplified default is two attacks and two defenses, with aggregation.

In AE modes, all attacks directed at the same defender in a round are aggregated into a single Defense Save. The exchange uses the highest attack TN among the incoming attackers, then applies a superiority modifier based on the ratio of incoming attack actions to the defender's allocated defense actions:

Ratio Modifier
1:1 0
1.5:1 / 3:2 ±1
2:1 ±2
3:1 ±3
4:1 ±4
5:1 ±5
10:1 ±10

The bonus goes to the side with more actions (defender if more defenses, attacker if more attacks), and the penalty applies to the side with fewer. Intermediate ratios are rounded to the nearest listed ratio using threshold rounding (e.g., 1.5→1, 2.0→2, 2.4→2, 4.0→4, 7.0→5, 10+→10). If the aggregated Defense Save succeeds, every incoming attack misses; if it fails, each successful attack is resolved for damage and Toughness separately.

Use the Compare Modes button to run the current encounter in both Standard and Simple AE with the same iteration count and see a side-by-side table of win rates, average rounds, and average rolls per combat. This makes the statistical impact of aggregation visible so you can decide whether the trade-off (fewer rolls, different swing) is acceptable.

Results in AE modes report average rolls per combat (attack checks, defense saves, damage checks, toughness saves) so the roll-reduction hypothesis can be observed.

Combat roadmap (Theater of the Mind)

The analyzer follows a phased rollout. Melee is the current baseline. Each later phase layers on the Theater of the Mind (TOTM) distance abstraction from ICRPG: distances are measured in move actions, and movement is resolved as a Maneuver check that can grant advantage or impose disadvantage based on terrain and circumstances.

  1. Melee (current) — close-quarters baseline.
  2. Ranged — add TOTM ranges and movement-to-engage. Terrain shapes the fight: broken ground breaks up assault forces that rely on concentration, narrowing ground negates superior numbers and rewards quality fighters, etc.
  3. Magic — spell resolution, Toll of Magic, and magical conditions.
  4. Mounted — expand TOTM language for mount superiority. In EAS, the Persian/mounted tradition means fighting on foot is less heroic; the same Maneuver represents very different things on horseback versus on foot.
  5. Early firearms — arquebussiers and gunpowder skirmishers.
  6. Modern warfare — muskets through industrial and contemporary eras, eventually drones.

Local development

No build tools are required. Open index.html in any modern web browser.

cd /path/to/3d6vsTN-combat-analyzer
# Serve with any static server, or simply open index.html
python3 -m http.server 8000

Deployment

The analyzer runs as a Docker Compose stack on PC03 with an Express backend that serves the static app, handles authentication, and proxies AI requests to DeepSeek and PC00 Ollama.

# On PC03
cd /home/user/3d6vstn-analyzer/repo
docker compose up -d
  • Local URL: http://192.168.254.156:27082/
  • ZeroTier URL: http://192.168.196.76:27082/
  • Public URL: http://3d6sim.gi7b.org/live, verified 2026-07-11 (DNS resolves via Cloudflare, curl returns 200). HTTPS returns Cloudflare 525 (origin has no cert yet) — see below.

A cron job runs every minute at /home/user/3d6vstn-analyzer/sync.sh to git pull and restart the container when the repo changes, so committed/pushed updates appear almost instantly.

Public access setup

The Nginx Proxy Manager proxy host is a real DB-backed row (proxy_host.id = 44 in the nginx-pm260202-db-1 MariaDB container), routing 3d6sim.gi7b.org192.168.196.76:27082. The generated nginx config lives at /data/compose/3/data/nginx/proxy_host/44.conf on PC03 — that file is regenerated from the DB every time nginx-pm260202-app-1 restarts, so don't hand-edit it. An earlier session created a manual .conf file directly (bypassing the DB) as a workaround for not having NPM admin-panel credentials; that file did not survive an app restart and was replaced by the proper DB-driven config (backup kept as 99.conf.manual-backup-20260711-204425 on PC03). See tools/IT-knowledge/skills/npm-direct-db-modification/SKILL.md for the reusable technique (BRC creds → SSH → docker execINSERT INTO proxy_host → restart) and its worked example for this exact host.

Remaining:

  1. Add a Cloudflare DNS A record for 3d6sim.gi7b.org — done, DNS already resolves via Cloudflare anycast IPs.
  2. HTTPS/SSL still pendingcertificate_id=0, ssl_forced=0 on proxy host 44. Issuing a real Let's Encrypt cert needs the NPM app to actually run an ACME challenge (DNS-01 via Cloudflare), which isn't a DB-only operation — it needs either the NPM Admin UI (http://192.168.196.76:81, no stored login in BRC) or running certbot by hand inside the NPM container. Until then, set Cloudflare's SSL/TLS mode to "Flexible" for this domain as a stopgap, or leave HTTP-only.

Environment variables

Create a .env file in the repo root:

DEEPSEEK_API_KEY=sk-...
OLLAMA_HOST=http://192.168.196.75:11434
APP_USERNAME=justin
APP_PASSWORD=...
SESSION_SECRET=...

Forgejo Pages (static-only fallback)

Run the deployment script to inline all CSS/JS into index.html and push to the static-pages branch:

python3 deploy.py

The script reads Forgejo credentials from ~/.brc/forgejo or the workspace credential store and pushes via SSH.

AI Playtester

After running a simulation, log in to the AI panel and click Ask Playtester. The backend sends the combat log and current rule configuration to DeepSeek (or PC00 Ollama) using a prompt template. Responses are saved to analysis-logs/ for later review. You can also save project snapshots to snapshots/ to preserve rule states and recover broken functions.

File layout

index.html              Main page
docker-compose.yml      Docker Compose stack
server/                 Express backend (auth, AI proxy, snapshots)
  package.json
  Dockerfile
  index.js
  prompts/              AI prompt templates
analysis-logs/          Saved AI analyses (runtime)
snapshots/              Saved project snapshots (runtime)
src/engine.js           Dice and combat resolution
src/rules.js            Rule toggle registry
src/simulator.js        Monte Carlo runner
src/data.js             Fighters and equipment
src/fighter-editor.js   Fighter browser/editor UI
src/charts.js           Chart.js wrappers
src/app.js              Main UI wiring
src/ai-analyzer.js      AI playtester client
src/style.css           Dark theme styles
deploy.py               Static-site deployment script
README.md               This file

Verification targets

With canonical rules in Standard mode and 1,000 duels:

  • Kaveh vs Bandit: Kaveh ~100%, avg rounds ~2.4
  • Roshan vs Bandit: Roshan ~99.9%, avg rounds ~2.5
  • Kaveh vs Roshan: Kaveh ~69%, avg rounds ~4.9
  • Vahram vs Kaveh: Vahram ~91%, avg rounds ~3.8

These targets are for the non-aggregated canonical loop. Simple AE and Complex AE use superiority aggregation and will produce different numbers; use Compare Modes to observe the deltas.