<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rowan Vale</title>
    <description>The latest articles on DEV Community by Rowan Vale (@questdataforge).</description>
    <link>https://dev.to/questdataforge</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4052998%2F10e295b6-e2ea-4131-81db-5dfdf704eb4f.png</url>
      <title>DEV Community: Rowan Vale</title>
      <link>https://dev.to/questdataforge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/questdataforge"/>
    <language>en</language>
    <item>
      <title>Representing Roguelite Build Effects Without Hard-Coding Every Combination</title>
      <dc:creator>Rowan Vale</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:58:19 +0000</pubDate>
      <link>https://dev.to/questdataforge/representing-roguelite-build-effects-without-hard-coding-every-combination-2e89</link>
      <guid>https://dev.to/questdataforge/representing-roguelite-build-effects-without-hard-coding-every-combination-2e89</guid>
      <description>&lt;p&gt;Build planners for roguelites become fragile when every effect is encoded as a special case. Pass the Fear is a useful example: characters, weapons, relics, tarot cards, battle scars, skill nodes, and weapon parts can all change a run. The combinatorial space grows too quickly for a long chain of &lt;code&gt;if&lt;/code&gt; statements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe effects as data
&lt;/h2&gt;

&lt;p&gt;Start with stable IDs and a small vocabulary of operations. A relic effect could be represented as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sourceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"relic-example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trigger"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"on_hit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"multiply"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"damage"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conditions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"target_burning"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public &lt;a href="https://passthefear.wiki/relics" rel="noopener noreferrer"&gt;relic directory&lt;/a&gt; remains readable for players, while the structured record can be evaluated by tools. The same approach works for the &lt;a href="https://passthefear.wiki/weapons" rel="noopener noreferrer"&gt;weapon database&lt;/a&gt;, where base stats and upgrade effects should be stored separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define an evaluation order
&lt;/h2&gt;

&lt;p&gt;The hard part is not storing a 15% bonus. It is deciding when that bonus applies. A deterministic pipeline might be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;load character base stats;&lt;/li&gt;
&lt;li&gt;apply permanent skill-tree modifiers;&lt;/li&gt;
&lt;li&gt;apply weapon and part modifiers;&lt;/li&gt;
&lt;li&gt;apply relic and tarot modifiers;&lt;/li&gt;
&lt;li&gt;evaluate battle-scar tradeoffs;&lt;/li&gt;
&lt;li&gt;calculate conditional effects for the selected scenario.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Writing the order down prevents two UI screens from producing different totals. It also makes balance patches easier to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep provenance with every fact
&lt;/h2&gt;

&lt;p&gt;Every value should include a source version or verification note. When a patch changes a weapon, the system can flag builds that still depend on an older value instead of silently returning a plausible but incorrect result.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://passthefear.wiki/characters" rel="noopener noreferrer"&gt;character directory&lt;/a&gt; can expose role and starting-kit data, while the &lt;a href="https://passthefear.wiki/skill-tree" rel="noopener noreferrer"&gt;skill tree&lt;/a&gt; supplies node-level modifiers. The records stay independent but join through stable IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the planner explain itself
&lt;/h2&gt;

&lt;p&gt;A useful &lt;a href="https://passthefear.wiki/build-planner" rel="noopener noreferrer"&gt;build planner&lt;/a&gt; should show more than a final score. It should list which sources changed damage, cooldown, survivability, or status effects and in what order. Explainable calculations help players notice mutually exclusive choices and assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for discovery
&lt;/h2&gt;

&lt;p&gt;The main &lt;a href="https://passthefear.wiki/" rel="noopener noreferrer"&gt;Pass the Fear Wiki&lt;/a&gt; ties the systems together, but each tool and directory should link back to the underlying entities. That creates a navigable knowledge graph for players and keeps the site maintainable.&lt;/p&gt;

&lt;p&gt;The general lesson is to treat game effects as composable data, define evaluation order explicitly, and preserve provenance. Then adding a new relic or weapon becomes a content update instead of another hard-coded branch.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>architecture</category>
      <category>webdev</category>
      <category>database</category>
    </item>
    <item>
      <title>Modeling Crafting Dependencies for a Scrap Mechanic Planner</title>
      <dc:creator>Rowan Vale</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:55:31 +0000</pubDate>
      <link>https://dev.to/questdataforge/modeling-crafting-dependencies-for-a-scrap-mechanic-planner-178i</link>
      <guid>https://dev.to/questdataforge/modeling-crafting-dependencies-for-a-scrap-mechanic-planner-178i</guid>
      <description>&lt;p&gt;Crafting-heavy games look simple until a player asks a practical question: “What do I need to build this machine, including every intermediate part?” At that point a recipe list becomes a dependency-graph problem. Scrap Mechanic is a useful case study because its parts, Craftbot recipes, upgrade chains, crops, and survival raids all affect planning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model recipes as directed edges
&lt;/h2&gt;

&lt;p&gt;Each craftable output should point to its ingredient requirements, while every ingredient should have a stable item ID. A compact representation might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"controller-level-3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"station"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"craftbot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ingredients"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"itemId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"component-kit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"itemId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"circuit-board"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A public &lt;a href="https://scrapmech.wiki/database" rel="noopener noreferrer"&gt;parts database&lt;/a&gt; can render the readable item pages, but the same records should power a &lt;a href="https://scrapmech.wiki/crafting" rel="noopener noreferrer"&gt;crafting reference&lt;/a&gt; and any planning tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expand dependencies recursively
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://scrapmech.wiki/crafting-planner" rel="noopener noreferrer"&gt;crafting planner&lt;/a&gt; can walk the graph until it reaches raw resources. The important details are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiply ingredient quantities by the requested output count;&lt;/li&gt;
&lt;li&gt;merge repeated materials after expansion;&lt;/li&gt;
&lt;li&gt;detect cycles or invalid recipes;&lt;/li&gt;
&lt;li&gt;preserve station requirements;&lt;/li&gt;
&lt;li&gt;let players mark inventory they already own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Memoizing each expanded recipe keeps large plans responsive. It also makes the calculation deterministic enough to test with fixtures whenever a game update changes recipe data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep blueprint analysis separate
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://scrapmech.wiki/blueprint-analyzer" rel="noopener noreferrer"&gt;blueprint analyzer&lt;/a&gt; has a different input grain. It starts with a serialized creation, counts parts by UUID, and joins those IDs back to the item database. Keeping this parser separate from the recipe graph makes both systems easier to validate.&lt;/p&gt;

&lt;p&gt;The useful output is not only total block count. Mass, component categories, controller usage, bearings, pistons, and missing or modded UUIDs help a builder understand why a creation behaves the way it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat raids as another calculator
&lt;/h2&gt;

&lt;p&gt;Survival planning also depends on farm value and timing. A &lt;a href="https://scrapmech.wiki/raid-calculator" rel="noopener noreferrer"&gt;raid calculator&lt;/a&gt; can translate planted crops into an expected raid level, then explain the thresholds instead of presenting a mystery number.&lt;/p&gt;

&lt;h2&gt;
  
  
  One source, several interfaces
&lt;/h2&gt;

&lt;p&gt;The main &lt;a href="https://scrapmech.wiki/" rel="noopener noreferrer"&gt;Scrap Mechanic Wiki&lt;/a&gt; connects these views, but the architecture works because the tools do not maintain competing copies of the same facts. Normalize item IDs, version the source data, validate every relationship, and generate player-facing tools from that shared model.&lt;/p&gt;

&lt;p&gt;That pattern applies well beyond one game: dependency graphs are the right abstraction whenever crafting, upgrades, and inventories overlap.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>webdev</category>
      <category>database</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Designing a Data-First Companion Wiki for DragonSword: Awakening</title>
      <dc:creator>Rowan Vale</dc:creator>
      <pubDate>Wed, 29 Jul 2026 10:46:36 +0000</pubDate>
      <link>https://dev.to/questdataforge/designing-a-data-first-companion-wiki-for-dragonsword-awakening-5540</link>
      <guid>https://dev.to/questdataforge/designing-a-data-first-companion-wiki-for-dragonsword-awakening-5540</guid>
      <description>&lt;p&gt;A game wiki becomes much more useful when it is treated as a small data product instead of a stack of prose pages. DragonSword: Awakening is a good example because its 19 playable heroes, Tag-team interactions, upgrade materials, and exploration data all overlap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a normalized content model
&lt;/h2&gt;

&lt;p&gt;I would keep heroes, roles, elements, skills, materials, recipes, and map locations as separate entities, then connect them through stable IDs. That avoids copying the same material or skill data into multiple pages and makes later balance updates less error-prone.&lt;/p&gt;

&lt;p&gt;A hero record can stay small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hero-slug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"breaker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fire"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tagTriggers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stun"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"materialIds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mat-a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mat-b"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The public &lt;a href="https://dsawakening.wiki/heroes" rel="noopener noreferrer"&gt;hero directory&lt;/a&gt; is the human-readable view, while the same relationships can drive filters and comparison tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build tools from the same source
&lt;/h2&gt;

&lt;p&gt;Once the content is normalized, a &lt;a href="https://dsawakening.wiki/team-build" rel="noopener noreferrer"&gt;team builder&lt;/a&gt; should not need a second hand-maintained dataset. It can derive role coverage, Tag-chain compatibility, and missing support functions from the hero records.&lt;/p&gt;

&lt;p&gt;The same principle applies to a &lt;a href="https://dsawakening.wiki/material-planner" rel="noopener noreferrer"&gt;material planner&lt;/a&gt;: let users select upgrade goals, aggregate required items by ID, and show which requirements are shared across heroes. This is more useful than forcing people to maintain a spreadsheet beside the wiki.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat maps as structured data
&lt;/h2&gt;

&lt;p&gt;An &lt;a href="https://dsawakening.wiki/map" rel="noopener noreferrer"&gt;interactive map&lt;/a&gt; works best when each marker stores a type, region, coordinates, prerequisites, and source notes. That makes filtering and later validation much easier than embedding coordinates directly in presentation code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for discovery
&lt;/h2&gt;

&lt;p&gt;The main &lt;a href="https://dsawakening.wiki/" rel="noopener noreferrer"&gt;DragonSword: Awakening Wiki&lt;/a&gt; links the datasets and tools together, but every entity page should also provide contextual links back to related heroes, items, bosses, or locations. This helps both players and crawlers discover the graph naturally.&lt;/p&gt;

&lt;p&gt;The broader lesson is simple: store facts once, derive tools from the same model, and make every useful record reachable from navigation and search. A companion site built this way can survive patches without turning each update into a manual rewrite.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>database</category>
      <category>gamedev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
