- JavaScript 87.3%
- HTML 7%
- CSS 3.5%
- Python 2.1%
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> |
||
|---|---|---|
| logs | ||
| server | ||
| src | ||
| tests | ||
| .gitignore | ||
| deploy.py | ||
| DIALOGUE.md | ||
| docker-compose.yml | ||
| index.html | ||
| README.md | ||
| RULES-NOTES.md | ||
| smoke-test.js | ||
| TOTM-GUIDE.md | ||
3d6 vs TN Combat Analyzer
A static HTML/JS Monte Carlo combat simulator for the gi7b 3d6-vs-target-number tabletop RPG system.
- Repo: https://git.gi7b.org/gi7b/3d6vsTN-combat-analyzer
- Pages: https://pages.gi7b.org/gi7b/3d6vsTN-combat-analyzer/
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.
- Melee (current) — close-quarters baseline.
- 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.
- Magic — spell resolution, Toll of Magic, and magical conditions.
- 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.
- Early firearms — arquebussiers and gunpowder skirmishers.
- 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
Docker on PC03 (recommended for live development)
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,curlreturns200). HTTPS returns Cloudflare525(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.org → 192.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 exec → INSERT INTO proxy_host → restart) and its worked example for this exact host.
Remaining:
Add a Cloudflare DNS A record for— done, DNS already resolves via Cloudflare anycast IPs.3d6sim.gi7b.org- HTTPS/SSL still pending —
certificate_id=0,ssl_forced=0on 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 runningcertbotby 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.