Skip to content

Commit 7fc4138

Browse files
committed
update to visualization
1 parent fefad51 commit 7fc4138

13 files changed

Lines changed: 302 additions & 154 deletions

Mythril.Blazor/wwwroot/data/content_graph.json

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2601,7 +2601,14 @@
26012601
"data": {
26022602
"description": "A speed-based specialist focused on exploring deep waters."
26032603
},
2604-
"in_edges": {},
2604+
"in_edges": {
2605+
"requires_stat": [
2606+
{
2607+
"targetId": "stat_speed",
2608+
"quantity": 60
2609+
}
2610+
]
2611+
},
26052612
"out_edges": {
26062613
"provides_ability": [
26072614
{
@@ -2653,7 +2660,18 @@
26532660
"data": {
26542661
"description": "An elite combatant unlocked through mastery of both strength and speed."
26552662
},
2656-
"in_edges": {},
2663+
"in_edges": {
2664+
"requires_stat": [
2665+
{
2666+
"targetId": "stat_strength",
2667+
"quantity": 100
2668+
},
2669+
{
2670+
"targetId": "stat_speed",
2671+
"quantity": 100
2672+
}
2673+
]
2674+
},
26572675
"out_edges": {
26582676
"provides_ability": [
26592677
{

docs/Playtest_Feedback.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
- Need multiple sources of junction strength. Possibly new cadences. Or consider renaming existing ones so it's more flavor acceptable
1+
## Issues
22

3+
- Need multiple sources of junction strength. Possibly new cadences. Or consider renaming existing ones so it's more flavor acceptable
34

45
- The "new content" indicator is still a bit unreliable. Consider a full refactor and possible service manager to single-source the interactions and fully unit test.
56
- Add identifiers to the workshop refinement tasks UI so it's easy to know who has access to a given refinement.
67

7-
88
- Some of the basic refinement abilities need multiple sources: Refine Scrap, Alchemy I.
99
- Rework lightning. Currently it's only unlockable very late in the game and the magic itself is vastly outclassed by other options by that point, making it a chain of orphaned content. => Fire and Ice shards both being part of this chain and also pointless.
1010
- Review and rework the entire stats system related to task performance. Right now, it's relatively random what requires vitality or speed, etc. I need to map things out and make larger design choices with intention. Just map for now.
@@ -44,7 +44,24 @@
4444

4545
- Consider a stats page that includes predicted production rates
4646

47-
**In processes**
47+
## Concepts
48+
- I want to locate characters. And include travel time between locations. Allow for cadence unlocks that reduce travel time, like flight or teleportation. Speed stat does not affect travel time in any way.
49+
50+
- I'd like to create a visual representation of the world map. With locations and character travel visuals.
51+
52+
- Some cadence abilities should affect the entire location. Allowing multiple characters to come together and benefit. Essence Harvest is a good first candidate.
53+
54+
- I'd like to have quests that require multiple characters to be assigned together. "Party Quests" that require everyone regardless of Logistics. And gate content as major milestones. The Treant and current end boss are good candidates for early testing.
55+
56+
- I'd like to have some cadence abilities that are actively used but aren't a refinement ability. Eg. Mass Recall the party.
57+
58+
- I'd like to introduce a new type of quest. Currently there are one-shot and recurring. I'd like quests with a cooldown or can move from location to location. Eg. Hunting slimes has a 3/3 count. Once one is consumed, a cooldown timer begins that fully replenishes the count. At 0/3, it's temporarily unavabile.
59+
60+
- I'd like some quests to be affected by multiple stats, requiring a fundamental restructuring of the stat modification formula.
61+
62+
- Instead of copying the quest percentage speed increase progression, refinement and unlock tasks will have stat break points. For example: When strength is 30+, strength related refinement tasks get a flat 25% time reduction. More of a bracket system.
63+
64+
## In processes
4865

4966
- Refine Wood and Mixology are on the same Cadence, making it difficult to mass produce potions automatically. Consider giving Mixology to another cadence also.
5067

docs/pacing_baseline.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"routed_completion_time_minutes": 87,
3-
"reachable_quests": 34,
4-
"total_quests": 34,
3+
"reachable_quests": 33,
4+
"total_quests": 33,
55
"sustainability_percent": 100.0
66
}

modules/visualization/constants.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
OUTPUT_FILE = "visual_dashboard.html"
77

88
# Simulation Constants
9-
MAX_SIM_FRAMES = 300
9+
MAX_SIM_FRAMES = 0 # Disabled for static layout
1010
TIER_WIDTH = 800
1111
NODE_RADIUS = 15
12-
MIN_NODE_SEPARATION = 60 # Vertical
12+
MIN_NODE_SEPARATION = 80 # Vertical spacing

modules/visualization/data_processor.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,24 @@ def enrich_data(nodes):
8181
adj = {n["id"]: [] for n in nodes}
8282
edge_counts = {n["id"]: 0 for n in nodes}
8383
for n in nodes:
84+
# Forward edges
8485
if "out_edges" in n:
8586
for rel_type, targets in n["out_edges"].items():
8687
for target in targets:
8788
target_id = target if isinstance(target, str) else target.get("targetId")
8889
if target_id in node_map:
8990
edge_counts[n["id"]] += 1
9091
edge_counts[target_id] += 1
91-
if rel_type in ["consumes", "requires_quest", "requires_ability"]:
92+
93+
if rel_type in ["consumes", "requires_quest", "requires_ability", "requires_stat"]:
9294
adj[target_id].append(n["id"])
9395
else:
9496
adj[n["id"]].append(target_id)
97+
9598
if "in_edges" in n:
9699
for rel_type, sources in n["in_edges"].items():
97-
for source_id in sources:
100+
for source in sources:
101+
source_id = source if isinstance(source, str) else source.get("targetId")
98102
if source_id in node_map:
99103
edge_counts[n["id"]] += 1
100104
edge_counts[source_id] += 1
@@ -163,19 +167,38 @@ def enrich_data(nodes):
163167
clusters, cluster_names = _identify_clusters(nodes)
164168
nodes.sort(key=lambda x: (x.get("tier", 0), x["type"], x["name"]))
165169

170+
# Constants for static layout
171+
TIER_WIDTH = 800
172+
VERTICAL_SPACING = 100
173+
166174
type_counts = {}
167175
HUB_THRESHOLD = 10
168176
hubs = {n["id"] for n in nodes if edge_counts.get(n.get("original_id", n["id"]), 0) > HUB_THRESHOLD}
169177

178+
# Track how many nodes are in each tier to center them
179+
tier_counts = {}
180+
for n in nodes:
181+
t = n.get("tier", 0)
182+
tier_counts[t] = tier_counts.get(t, 0) + 1
183+
184+
current_tier_indices = {}
185+
170186
for n in nodes:
171187
t = n.get("tier", 0)
172188
n["cluster_id"] = clusters.get(n["id"], "cluster_none")
173189
n["is_hub"] = n["id"] in hubs or n.get("original_id") in hubs
174190

175-
tier_type = (t, n["type"])
176-
type_counts[tier_type] = type_counts.get(tier_type, 0) + 1
177-
n["tier_index"] = type_counts[tier_type]
178-
191+
# Deterministic Grid Position
192+
idx = current_tier_indices.get(t, 0)
193+
current_tier_indices[t] = idx + 1
194+
195+
# Calculate Y to center the tier column
196+
total_in_tier = tier_counts[t]
197+
start_y = - (total_in_tier * VERTICAL_SPACING) / 2
198+
199+
n["x"] = t * TIER_WIDTH
200+
n["y"] = start_y + (idx * VERTICAL_SPACING)
201+
179202
n["simulation"] = {
180203
"sustainable": _matches_activity(n["name"], sust_names) or n.get("is_sustainable_instance"),
181204
"unsustainable": _matches_activity(n["name"], sim_data.get("unsustainable", set())),
@@ -197,13 +220,6 @@ def _identify_clusters(nodes):
197220
for target in n["out_edges"]["contains"]:
198221
clusters[target["targetId"]] = c_id
199222
clusters[n["id"]] = c_id
200-
elif n["type"] == "Cadence":
201-
c_id = f"cluster_cad_{n['id']}"
202-
cluster_names[c_id] = n["name"]
203-
if "out_edges" in n and "provides_ability" in n["out_edges"]:
204-
for target in n["out_edges"]["provides_ability"]:
205-
clusters[target["targetId"]] = c_id
206-
clusters[n["id"]] = c_id
207223
elif n["type"] == "Refinement":
208224
is_magic = n.get("data", {}).get("primary_stat") in ["Magic", "Speed"]
209225
c_id = "cluster_ref_magic" if is_magic else "cluster_ref_material"

modules/visualization/lattice_data.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
function processData() {
22
nodes = nodesData.map(d => ({
3-
...d,
4-
x: d.tier * TIER_WIDTH + (Math.random() - 0.5) * 50,
5-
y: (window.innerHeight / 2) + (Math.random() - 0.5) * 100,
6-
vx: 0, vy: 0
3+
...d
74
}));
85

96
nodeMap = new Map(nodes.map(n => [n.id, n]));

modules/visualization/lattice_interactions.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,17 @@ function setupInteractions() {
7373
document.getElementById('btn-hierarchy').classList.remove('active');
7474
document.getElementById('graph-svg').style.display = 'block';
7575
document.getElementById('hierarchy-view').style.display = 'none';
76-
requestAnimationFrame(simulationStep);
76+
updateLayout();
77+
});
78+
79+
document.getElementById('btn-quest-flow').addEventListener('click', () => {
80+
currentView = 'quest-flow';
81+
document.getElementById('btn-lattice').classList.remove('active');
82+
document.getElementById('btn-quest-flow').classList.add('active');
83+
document.getElementById('btn-hierarchy').classList.remove('active');
84+
document.getElementById('graph-svg').style.display = 'block';
85+
document.getElementById('hierarchy-view').style.display = 'none';
86+
renderQuestFlow();
7787
});
7888

7989
document.getElementById('btn-hierarchy').addEventListener('click', () => {
@@ -86,11 +96,9 @@ function setupInteractions() {
8696
});
8797

8898
document.getElementById('btn-reset').addEventListener('click', () => {
89-
nodes.forEach(n => {
90-
n.x = n.tier * TIER_WIDTH + (Math.random() - 0.5) * 100;
91-
n.y = window.innerHeight / 2 + (Math.random() - 0.5) * 400;
92-
n.vx = 0; n.vy = 0;
93-
});
99+
// Reload original coordinates from data
100+
processData();
101+
renderLattice();
94102
transform = { x: 50, y: 50, k: 0.6 }; updateTransform();
95103
});
96104

@@ -107,6 +115,7 @@ function setupInteractions() {
107115
const r = svg.getBoundingClientRect();
108116
draggedNode.x = (e.clientX - r.left - transform.x) / transform.k;
109117
draggedNode.y = (e.clientY - r.top - transform.y) / transform.k;
118+
updateLayout();
110119
} else if (isDragging) {
111120
transform.x = e.clientX - startPos.x; transform.y = e.clientY - startPos.y;
112121
updateTransform();

0 commit comments

Comments
 (0)