Skip to content

Commit fcbf595

Browse files
committed
feat(viz): implement noise reduction, hub gating, and simulation milestones
1 parent 6f42fe3 commit fcbf595

12 files changed

Lines changed: 312 additions & 162 deletions

docs/roadmap.md

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
# Mythril Project Roadmap: Simulation & Architecture Maturity
1+
# Visualization Noise Reduction Roadmap
22

3-
This roadmap focuses on refining the game's economic balance, optimizing the core simulation engines, and enhancing the UI with predictive data.
3+
## Goal
4+
Transform the Mythril Lattice visualization from a cluttered "hairball" into a clear progression and economic map by gating high-frequency resource nodes and highlighting simulation milestones.
45

5-
## Phase 1: Economic & Simulation Hardening
6-
- [x] **Economic Equilibrium Refinement**: Achieve 100% sustainability for all reachable recurring activities.
7-
- [x] **Dependency-Tracked Fixpoint Solver**: Implement a worklist-based `LatticeSimulator` for $O(I \cdot \text{avg\_out\_degree})$ performance.
8-
- [x] **Simulation-Driven Regression Assertions**: Integrate end-game pacing checks into `check_health.py`.
6+
## Phase 1: Data Architecture & Gating
7+
- [ ] Implement hub detection in `data_processor.py` (Frequency thresholding).
8+
- [ ] Categorize edges into `Core` (Progression) vs `Auxiliary` (Economy).
9+
- [ ] Integrate simulation results for `Sustainability` and `Milestone` flagging.
910

10-
## Phase 2: UI & UX Predictive Intelligence
11-
- [x] **Eliminate "Magic String" Metadata**: Refactor ability/quest effects into a typed `EffectDefinition` schema.
12-
- [x] **Predictive Resource Highlighting**: Visual indicators for unaffordable quest requirements in real-time.
13-
- [x] **Effect-Driven UI Logic**: Passive stat boosts and magic capacity now driven by typed effects.
14-
- [x] **Predictive Dependency Overlay**: Implement "Prerequisite Path" highlighting in the `CadenceTree` and `QuestPanel`.
11+
## Phase 2: Visual Logic & Interactivity
12+
- [ ] Add filtering logic to `lattice_data.js`.
13+
- [ ] Implement milestone-specific rendering styles in `lattice_rendering.js`.
14+
- [ ] Add hover-driven edge visibility for auxiliary links.
1515

16-
## Phase 3: State & Runtime Consolidation
17-
- [x] **Unified Deterministic State Store**: Implement a central Reducer-based `GameStateStore` for full simulation/runtime parity.
18-
19-
---
20-
*Last Updated: 2026-04-08*
16+
## Phase 3: UI/UX Controls
17+
- [ ] Add Dashboard controls for "Progression Only" and "Hide Resource Hubs".
18+
- [ ] Implement sustainability overlays based on health check metrics.
19+
- [ ] Final validation with `scripts/check_health.py`.

docs/roadmap_detailed.md

Lines changed: 31 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,31 @@
1-
# Detailed Technical Roadmap: Simulation & Architecture Maturity
2-
3-
Detailed breakdown of current roadmap initiatives.
4-
5-
## 1. Economic Equilibrium Refinement
6-
Achieving 100% sustainability for all reachable recurring content.
7-
8-
- [x] **Data Audit**: Identify all "Unsustainable Activities" using `FlowSimulator` report.
9-
- [x] **Logistics/Resource Injection**: Update `content_graph.json` to ensure bottleneck resources like `Log`, `Mana Leaf`, and `Ancient Bark` have sustainable production loops.
10-
- [x] **Cost-Benefit Balance**: Adjust refinement costs and rewards for late-game content to prevent resource depletion.
11-
- [x] **Verification**: Run `check_health.py` and confirm `shield_sustainability` reaches 100%.
12-
13-
## 2. Dependency-Tracked Fixpoint Solver
14-
Optimizing `LatticeSimulator` performance for large-scale content expansion.
15-
16-
- [x] **Worklist Architecture**: Implement a queue-based `Solve()` loop in `LatticeSimulator.cs`.
17-
- [x] **Dependency Graph Build**: Create an internal map of `Node -> [DependentNodes]` during initialization.
18-
- [x] **Delta Propagation**: Only add nodes to the worklist when their inputs (in-edges) change state.
19-
- [x] **Validation**: Ensure results are bit-for-bit identical to the exhaustive solver.
20-
21-
## 3. Simulation-Driven Regression Assertions
22-
Automated pacing and balance checks for CI.
23-
24-
- [x] **Baseline Establishment**: Record current `Routed Completion Time` as the performance baseline.
25-
- [x] **Pacing Logic**: Add logic to `check_health.py` to compare current sim results with baseline.
26-
- [x] **Regression Thresholds**: Define acceptable variance (e.g., <15% increase without new content).
27-
- [x] **CI Integration**: Fail the health check if pacing regressions are detected.
28-
29-
## 4. UI: Predictive Dependency Overlay
30-
Graph-based navigation assistance for the player.
31-
32-
- [ ] **Pathfinding Logic**: Implement BFS/DFS on the client-side content graph to find the shortest path to a locked node.
33-
- [ ] **UI State Integration**: Update `CadenceTree` and `QuestPanel` to accept a `HighightedNodes` parameter.
34-
- [ ] **Hover Interactions**: Implement hover-triggers that activate the pathfinding and highlight prerequisite nodes.
35-
36-
## 5. Eliminate "Magic String" Metadata
37-
Type-safe ability and quest effects.
38-
39-
- [ ] **Schema Definition**: Create a JSON schema/C# record for `EffectDefinition` (e.g., `StatBoost`, `MagicCapacity`, `AutoQuest`).
40-
- [ ] **Content Migration**: Update all `Ability` and `Quest` nodes in `content_graph.json` to use the new `effects` field.
41-
- [ ] **Loader Refactor**: Update `ContentLoader.cs` to deserialize the typed effects.
42-
- [ ] **Code Cleanup**: Remove `Metadata` dictionary lookups and replace with typed property checks.
43-
44-
## 6. Unified Deterministic State Store
45-
Full parity between simulation and Blazor runtime.
46-
47-
- [x] **State Record**: Create a single `GameStoreState` immutable record.
48-
- [x] **Actions & Reducers**: Define formal `Actions` (e.g., `CompleteQuest`, `SpendResource`) and a pure `Reducer` function.
49-
- [x] **Manager Refactor**: Convert `ResourceManager`, `JunctionManager`, and `InventoryManager` to be view-only subscribers to the `GameStateStore`.
50-
- [x] **Snapshot Support**: Implement a one-click snapshot/restore feature for testing and save-games.
51-
52-
---
53-
*Last Updated: 2026-04-06*
1+
# Detailed Roadmap: Visualization Noise Reduction
2+
3+
## 1. Data Processor Gating (`modules/visualization/data_processor.py`)
4+
- **Hub Detection:** Any node with > 10 incoming or outgoing edges will be marked as a `ResourceHub`.
5+
- **Edge Metadata:** Each edge in the JSON will be tagged with a `category`:
6+
- `progression`: `unlocks_cadence`, `requires_quest`, `contains`.
7+
- `economy`: `consumes`, `produces`, `rewards`.
8+
- **Milestone Identification:**
9+
- Quests that unlock locations or cadences.
10+
- Items that are "Sustainable" per `simulation_report.md`.
11+
12+
## 2. Visual Filtering (`modules/visualization/lattice_rendering.js` & `lattice_data.js`)
13+
- **Filtered Edge Loading:** `lattice_data.js` will maintain two sets of edges: `activeEdges` and `allEdges`.
14+
- **Style Overrides:**
15+
- Milestones: Gold stroke/glow, larger scale.
16+
- Hubs: Dimmed by default, no labels unless hovered.
17+
- Progression Edges: Thicker lines with distinct colors.
18+
19+
## 3. UI/UX Enhancements (`modules/visualization/template_engine.py`)
20+
- **Control Panel:**
21+
- Toggle: `Show Resource Hubs` (Default: Off)
22+
- Toggle: `Progression Only` (Default: On)
23+
- Toggle: `Simulation Overlay` (Default: Off)
24+
- **Tooltip Upgrades:** Show net flow rates (from simulation) in item tooltips.
25+
26+
## 4. Simulation Integration
27+
- **Parser:** A new utility to scrape `simulation_report.md` for:
28+
- Sustainable vs Unsustainable list.
29+
- Completion times.
30+
- Critical bottlenecks.
31+
- **Visual Link:** Nodes in the graph will color-code based on their sustainability status.

modules/visualization/data_processor.py

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import os
33
import sys
4+
import re
45
from collections import deque
56
from .constants import GRAPH_FILE
67

@@ -16,24 +17,81 @@ def load_graph():
1617
with open(GRAPH_FILE, 'r', encoding='utf-8') as f:
1718
return json.load(f)
1819

20+
def parse_simulation_report():
21+
report_path = "simulation_report.md"
22+
if not os.path.exists(report_path):
23+
return {}
24+
25+
data = {
26+
"sustainable": set(),
27+
"unsustainable": set(),
28+
"rates": {}
29+
}
30+
31+
try:
32+
with open(report_path, "r", encoding="utf-8") as f:
33+
content = f.read()
34+
35+
# Parse sustainable
36+
sust_match = re.search(r"### Sustainable Recurring Activities\n(.*?)\n\n", content, re.DOTALL)
37+
if sust_match:
38+
for line in sust_match.group(1).split("\n"):
39+
if line.strip().startswith("- "):
40+
data["sustainable"].add(line.strip()[2:])
41+
42+
# Parse unsustainable
43+
unsust_match = re.search(r"### ⚠️ Unsustainable Activities.*?\n(.*?)\n\n", content, re.DOTALL)
44+
if unsust_match:
45+
for line in unsust_match.group(1).split("\n"):
46+
if line.strip().startswith("- "):
47+
data["unsustainable"].add(line.strip()[2:])
48+
49+
# Parse rates
50+
rates_match = re.search(r"### Net Resource Rates \(per second\)\n(.*?)\n\n", content, re.DOTALL)
51+
if rates_match:
52+
for line in rates_match.group(1).split("\n"):
53+
m = re.match(r"- \*\*(.*?)\*\*: ([\d.]+)/s", line.strip())
54+
if m:
55+
data["rates"][m.group(1)] = float(m.group(2))
56+
except Exception as e:
57+
print(f"Warning: Failed to parse simulation report: {e}")
58+
59+
return data
60+
61+
def _matches_activity(node_name, activity_names):
62+
if node_name in activity_names:
63+
return True
64+
65+
# Try normalization for refinements
66+
# node: "Refine Wood - Herb"
67+
# activity: "Refine Wood:Log->Herb"
68+
norm_node = node_name.lower().replace(" ", "").replace("-", "")
69+
for act in activity_names:
70+
norm_act = act.lower().replace(" ", "").replace(":", "").replace("->", "")
71+
if norm_node in norm_act or norm_act in norm_node:
72+
return True
73+
return False
74+
1975
def enrich_data(nodes):
2076
"""Calculate tiers and clusters in Python for better performance."""
2177
node_map = {n["id"]: n for n in nodes}
78+
sim_data = parse_simulation_report()
2279

23-
# 1. Build Adjacency
80+
# 1. Build Adjacency and Edge Counts
2481
adj = {n["id"]: [] for n in nodes}
82+
edge_counts = {n["id"]: 0 for n in nodes}
83+
2584
for n in nodes:
26-
if "out_edges" in n:
27-
for target_list in n["out_edges"].values():
28-
for target in target_list:
29-
target_id = target if isinstance(target, str) else target.get("targetId")
30-
if target_id in adj:
31-
adj[n["id"]].append(target_id)
32-
if "in_edges" in n:
33-
for source_list in n["in_edges"].values():
34-
for source_id in source_list:
35-
if source_id in adj:
36-
adj[source_id].append(n["id"])
85+
for direction in ["out_edges", "in_edges"]:
86+
if direction in n:
87+
for rel_type, targets in n[direction].items():
88+
for target in targets:
89+
target_id = target if isinstance(target, str) else target.get("targetId")
90+
if target_id in node_map:
91+
edge_counts[n["id"]] += 1
92+
edge_counts[target_id] += 1
93+
if direction == "out_edges":
94+
adj[n["id"]].append(target_id)
3795

3896
# 2. BFS for Tiers
3997
tiers = {n["id"]: 0 for n in nodes}
@@ -61,9 +119,34 @@ def enrich_data(nodes):
61119
# 3. Clusters
62120
clusters, cluster_names = _identify_clusters(nodes)
63121

122+
# Hub Detection Threshold
123+
HUB_THRESHOLD = 10
124+
125+
sust_names = sim_data.get("sustainable", set())
126+
unsust_names = sim_data.get("unsustainable", set())
127+
64128
for n in nodes:
65129
n["tier"] = tiers.get(n["id"], 0)
66130
n["cluster_id"] = clusters.get(n["id"], "cluster_none")
131+
n["is_hub"] = edge_counts[n["id"]] > HUB_THRESHOLD
132+
133+
# Milestone Detection
134+
is_milestone = False
135+
if n["type"] == "Quest":
136+
# Unlocks something important
137+
out = n.get("out_edges", {})
138+
if "unlocks_cadence" in out or "unlocks_location" in out:
139+
is_milestone = True
140+
if n["id"] == "quest_prologue":
141+
is_milestone = True
142+
n["is_milestone"] = is_milestone
143+
144+
# Simulation Integration
145+
n["simulation"] = {
146+
"sustainable": _matches_activity(n["name"], sust_names),
147+
"unsustainable": _matches_activity(n["name"], unsust_names),
148+
"net_rate": sim_data.get("rates", {}).get(n["name"], 0)
149+
}
67150

68151
return nodes, cluster_names
69152

modules/visualization/lattice_data.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,21 @@ function processData() {
77
}));
88

99
nodeMap = new Map(nodes.map(n => [n.id, n]));
10+
allEdges = [];
11+
12+
const progressionTypes = ['unlocks_cadence', 'unlocks_location', 'requires_quest', 'contains', 'provides_ability', 'requires_ability'];
1013

1114
nodes.forEach(node => {
1215
if (node.out_edges) {
1316
Object.entries(node.out_edges).forEach(([type, targetList]) => {
1417
targetList.forEach(target => {
1518
const targetId = typeof target === 'string' ? target : target.targetId;
1619
if (nodeMap.has(targetId)) {
17-
edges.push({
20+
allEdges.push({
1821
id: `edge-${node.id}-${targetId}`,
19-
source: node.id, target: targetId, type: type
22+
source: node.id, target: targetId,
23+
type: type,
24+
category: progressionTypes.includes(type) ? 'progression' : 'economy'
2025
});
2126
}
2227
});
@@ -26,9 +31,11 @@ function processData() {
2631
Object.entries(node.in_edges).forEach(([type, sourceList]) => {
2732
sourceList.forEach(sourceId => {
2833
if (nodeMap.has(sourceId)) {
29-
edges.push({
34+
allEdges.push({
3035
id: `edge-${sourceId}-${node.id}`,
31-
source: sourceId, target: node.id, type: type
36+
source: sourceId, target: node.id,
37+
type: type,
38+
category: progressionTypes.includes(type) ? 'progression' : 'economy'
3239
});
3340
}
3441
});
@@ -37,14 +44,41 @@ function processData() {
3744
});
3845

3946
const seenEdges = new Set();
40-
edges = edges.filter(e => {
41-
const key = `${e.source}-${e.target}`;
47+
allEdges = allEdges.filter(e => {
48+
const key = `${e.source}-${e.target}-${e.type}`;
4249
if (seenEdges.has(key)) return false;
4350
seenEdges.add(key);
4451
return true;
4552
});
53+
54+
filterEdges();
55+
}
56+
57+
function filterEdges() {
58+
edges = allEdges.filter(e => {
59+
const sourceNode = nodeMap.get(e.source);
60+
const targetNode = nodeMap.get(e.target);
61+
62+
// Hub filtering
63+
if (!showHubs) {
64+
if (sourceNode.is_hub || targetNode.is_hub) return false;
65+
}
66+
67+
// Progression Only filtering
68+
if (showProgressionOnly) {
69+
if (e.category !== 'progression') return false;
70+
}
71+
72+
return true;
73+
});
74+
75+
// Handle node visibility (dimming)
76+
nodes.forEach(n => {
77+
n.visible = true;
78+
if (!showHubs && n.is_hub) n.visible = false;
79+
});
4680
}
4781

4882
function updateStats() {
49-
document.getElementById('stats').innerText = `NODES: ${nodes.length} | EDGES: ${edges.length} | TIERS: ${Math.max(...nodes.map(n=>n.tier))+1}`;
83+
document.getElementById('stats').innerText = `NODES: ${nodes.filter(n=>n.visible).length}/${nodes.length} | EDGES: ${edges.length} | TIERS: ${Math.max(...nodes.map(n=>n.tier))+1}`;
5084
}

0 commit comments

Comments
 (0)