<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://www.no-way-out.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dubhleohan</id>
	<title>No Way Out Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://www.no-way-out.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dubhleohan"/>
	<link rel="alternate" type="text/html" href="https://www.no-way-out.com/wiki/Special:Contributions/Dubhleohan"/>
	<updated>2026-04-16T18:34:48Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.44.0</generator>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=504</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=504"/>
		<updated>2025-11-17T03:04:24Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller (WORK IN PROGRESS) &amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
      // ADD: Half of Nimble level to Melee Attack and Melee Defence&lt;br /&gt;
    var halfNimble = Math.floor(level / 2);&lt;br /&gt;
    ensureMetric(breakdown, &#039;Melee Attack&#039;); breakdown[&#039;Melee Attack&#039;].skills += halfNimble;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Melee Defence&#039;); breakdown[&#039;Melee Defence&#039;].skills += halfNimble;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // All skills add half their level to their own check&lt;br /&gt;
  ensureMetric(breakdown, skillName);&lt;br /&gt;
  breakdown[skillName].skills += mod;&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
      // Add Strength modifier to weapon skills&lt;br /&gt;
      var weaponSkills = [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Axe&#039;, &#039;Spear&#039;];&lt;br /&gt;
      for (i = 0; i &amp;lt; weaponSkills.length; i++) {&lt;br /&gt;
        var ws = weaponSkills[i];&lt;br /&gt;
        ensureMetric(breakdown, ws);&lt;br /&gt;
        if (str &amp;lt;= 4)       breakdown[ws].derived += -1;&lt;br /&gt;
        else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
        else if (str &amp;lt;= 8)  breakdown[ws].derived += 1;&lt;br /&gt;
        else if (str &amp;gt;= 10) breakdown[ws].derived += 2;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=436</id>
		<title>Template:Character Creation</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=436"/>
		<updated>2025-11-10T21:15:14Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* 📖 Vanilla Positive Traits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Character Creation==&lt;br /&gt;
Characters on NWO are supposed to feel like real people, and each character build will first need to be approved before the character can Spawn.&lt;br /&gt;
Using this guide, at the end of a Character Application thread, post what kind of Occupation and Traits the character should have. Every Trait will need to be justified properly in the Background. Both Negatives, and Positives.&lt;br /&gt;
&lt;br /&gt;
==Skill Caps &amp;amp; Traits==&lt;br /&gt;
On NWO, every skill has a max cap level. Caps are influenced by the starting level in character creation, and will be 4, 6, 8 and 10. A skill that starts with at least one level will already be at its maximum possible level.&lt;br /&gt;
&lt;br /&gt;
===Gaining Levels &amp;amp; XP===&lt;br /&gt;
If a character starts with 0 levels in a skill, they will be able to level it up to Level 4. Leveling these skills happens through normal PZ Mechanical Leveling up.&lt;br /&gt;
&lt;br /&gt;
===Skill Traits===&lt;br /&gt;
Since Backgrounds will not give any starting level in Crafting, Agility, Survival and Combat skills, a character build will mostly come from the selection of traits. Our custom Skill Traits are organized in three different tiers that have different costs and Starting levels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Examples&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Point Cost !! Points Added !! Starting Level !! Skill Cap !! Limit&lt;br /&gt;
|-&lt;br /&gt;
| None || 0 || +0 Levels || Level 0 || Level 4 || None &lt;br /&gt;
|-&lt;br /&gt;
| 🥉 Amateur || 1 || +1 Levels || Level 6 || Level 6 || None&lt;br /&gt;
|-&lt;br /&gt;
| 🥈 Experienced || 2 || +2 Levels || Level 8 || Level 8 || Two Per Character&lt;br /&gt;
|-&lt;br /&gt;
| 🥇 Expert || 3 || +3 Levels || Level 10 || Level 10 || One Per Character&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A Character can&#039;t also have same Skill Traits of different Tiers. (For example: Both Amateur Carpenter *and* Experienced Carpenter).&lt;br /&gt;
&lt;br /&gt;
==Traits List &amp;amp; Costs==&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Positive Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Speed Demon || 1 || +1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Brewer || 2 || Level 8 Brewing&lt;br /&gt;
|-&lt;br /&gt;
| Low Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Low Hunger || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Herbalist || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Iron Gut || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Keen Hearing || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Dexterous || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Organized || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Cats Eyes || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Adrenaline Junkie || 2 || +1 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Graceful || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Brave || 2 || +2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Outdoorsman || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Inconspicuous || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Fast Healer || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fast Learner || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Fast Reader || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Resilient || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Thick Skinned || 2 || +2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fit || 2 || Level 8 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Strong || 2 || Level 8 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Fit || 4 || Level 10 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Very Strong || 4 || Level 10 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Eagle Eyed || 3 || +1 Ranged Attack, +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Desensitized || 3 || +3 Resolve&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Negative Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Prone to Illness || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Smoker || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Sunday Driver || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Learner || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Reader || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Healer || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Weak Stomach || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Fear of Blood || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Agoraphobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| All Thumbs || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Claustrophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Cowardly || 2 || -2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Pacifist || 3 || -2 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Short Sighted || 2 || -1 Ranged Attack, -1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Thin Skinned || 2 || -2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Conspicuous || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Clumsy || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Hard of Hearing || 2 || -2 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Disorganized || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Hearty Appetite || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| High Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak || 2 || 4 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Unfit || 2 || 4 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Illiterate || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Deaf || 4 || -4 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Very Weak || 4 || 2 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Unfit || 4 || 2 Fitness&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥉 Amateur Skill Traits===&lt;br /&gt;
Cost: 1 Point each. &#039;&#039;&#039;No Limit.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Tailor || +1 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Doctor || +1 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Mechanic|| +1 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Electrician || +1 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Metalworker || +1 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Homesteader || +1 Cooking, +1 Farming, +1 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Shooter || +1 Aiming, +1 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Outdoorsman || +1 Fishing, +1 Foraging, +1 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Athlete || +1 Nimble, +1 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Rogue || +1 Sneaking, +1 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Survival) || +1 Axe, +1 Spear, +1 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blunt) || +1 Long Blunt, +1 Short Blunt, +1 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blade) || +1 Long Blade, +1 Short Blade, +1 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥈  Experienced Skill Traits===&lt;br /&gt;
Cost: 2 Points each. &#039;&#039;&#039;Max 2.&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Tailor || +2 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Doctor || +2 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Mechanic || +2 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Electrician || +2 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Metalworker || +2 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Homesteader || +2 Cooking, +2 Farming, +2 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Shooter || +2 Aiming, +2 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Outdoorsman || +2 Fishing, +2 Foraging, +2 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Athlete || +2 Nimble, +2 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Rogue || +2 Sneaking, +2 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Survival) || +2 Axe, +2 Spear, +2 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blunt) || +2 Long Blunt, +2 Short Blunt, +2 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blade) || +2 Long Blade, +2 Short Blade, +2 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥇  Expert Skill Traits===&lt;br /&gt;
Cost: 3 Points each. &#039;&#039;&#039;Max 1.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Expert Tailor || +3 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Expert Doctor || +3 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Expert Mechanic || +3 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Expert Electrician || +3 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Expert Metalworker || +3 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Expert Homesteader || +3 Cooking, +3 Farming, +3 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Expert Shooter || +3 Aiming, +3 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Expert Outdoorsman || +3 Fishing, +3 Foraging, +3 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Expert Athlete || +3 Nimble, +3 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Expert Rogue || +3 Sneaking, +3 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Survival) || +3 Axe, +3 Spear, +3 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blunt) || +3 Long Blunt, +3 Short Blunt, +3 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blade) || +3 Long Blade, +3 Short Blade, +3 Maintenance&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=435</id>
		<title>Template:Character Creation</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=435"/>
		<updated>2025-11-10T21:14:59Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* 📖 Vanilla Positive Traits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Character Creation==&lt;br /&gt;
Characters on NWO are supposed to feel like real people, and each character build will first need to be approved before the character can Spawn.&lt;br /&gt;
Using this guide, at the end of a Character Application thread, post what kind of Occupation and Traits the character should have. Every Trait will need to be justified properly in the Background. Both Negatives, and Positives.&lt;br /&gt;
&lt;br /&gt;
==Skill Caps &amp;amp; Traits==&lt;br /&gt;
On NWO, every skill has a max cap level. Caps are influenced by the starting level in character creation, and will be 4, 6, 8 and 10. A skill that starts with at least one level will already be at its maximum possible level.&lt;br /&gt;
&lt;br /&gt;
===Gaining Levels &amp;amp; XP===&lt;br /&gt;
If a character starts with 0 levels in a skill, they will be able to level it up to Level 4. Leveling these skills happens through normal PZ Mechanical Leveling up.&lt;br /&gt;
&lt;br /&gt;
===Skill Traits===&lt;br /&gt;
Since Backgrounds will not give any starting level in Crafting, Agility, Survival and Combat skills, a character build will mostly come from the selection of traits. Our custom Skill Traits are organized in three different tiers that have different costs and Starting levels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Examples&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Point Cost !! Points Added !! Starting Level !! Skill Cap !! Limit&lt;br /&gt;
|-&lt;br /&gt;
| None || 0 || +0 Levels || Level 0 || Level 4 || None &lt;br /&gt;
|-&lt;br /&gt;
| 🥉 Amateur || 1 || +1 Levels || Level 6 || Level 6 || None&lt;br /&gt;
|-&lt;br /&gt;
| 🥈 Experienced || 2 || +2 Levels || Level 8 || Level 8 || Two Per Character&lt;br /&gt;
|-&lt;br /&gt;
| 🥇 Expert || 3 || +3 Levels || Level 10 || Level 10 || One Per Character&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A Character can&#039;t also have same Skill Traits of different Tiers. (For example: Both Amateur Carpenter *and* Experienced Carpenter).&lt;br /&gt;
&lt;br /&gt;
==Traits List &amp;amp; Costs==&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Positive Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Speed Demon || 1 || +1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Brewer || 2 || 8 Brewing&lt;br /&gt;
|-&lt;br /&gt;
| Low Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Low Hunger || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Herbalist || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Iron Gut || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Keen Hearing || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Dexterous || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Organized || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Cats Eyes || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Adrenaline Junkie || 2 || +1 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Graceful || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Brave || 2 || +2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Outdoorsman || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Inconspicuous || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Fast Healer || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fast Learner || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Fast Reader || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Resilient || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Thick Skinned || 2 || +2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fit || 2 || Level 8 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Strong || 2 || Level 8 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Fit || 4 || Level 10 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Very Strong || 4 || Level 10 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Eagle Eyed || 3 || +1 Ranged Attack, +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Desensitized || 3 || +3 Resolve&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Negative Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Prone to Illness || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Smoker || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Sunday Driver || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Learner || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Reader || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Healer || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Weak Stomach || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Fear of Blood || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Agoraphobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| All Thumbs || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Claustrophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Cowardly || 2 || -2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Pacifist || 3 || -2 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Short Sighted || 2 || -1 Ranged Attack, -1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Thin Skinned || 2 || -2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Conspicuous || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Clumsy || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Hard of Hearing || 2 || -2 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Disorganized || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Hearty Appetite || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| High Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak || 2 || 4 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Unfit || 2 || 4 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Illiterate || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Deaf || 4 || -4 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Very Weak || 4 || 2 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Unfit || 4 || 2 Fitness&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥉 Amateur Skill Traits===&lt;br /&gt;
Cost: 1 Point each. &#039;&#039;&#039;No Limit.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Tailor || +1 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Doctor || +1 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Mechanic|| +1 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Electrician || +1 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Metalworker || +1 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Homesteader || +1 Cooking, +1 Farming, +1 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Shooter || +1 Aiming, +1 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Outdoorsman || +1 Fishing, +1 Foraging, +1 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Athlete || +1 Nimble, +1 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Rogue || +1 Sneaking, +1 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Survival) || +1 Axe, +1 Spear, +1 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blunt) || +1 Long Blunt, +1 Short Blunt, +1 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blade) || +1 Long Blade, +1 Short Blade, +1 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥈  Experienced Skill Traits===&lt;br /&gt;
Cost: 2 Points each. &#039;&#039;&#039;Max 2.&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Tailor || +2 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Doctor || +2 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Mechanic || +2 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Electrician || +2 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Metalworker || +2 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Homesteader || +2 Cooking, +2 Farming, +2 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Shooter || +2 Aiming, +2 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Outdoorsman || +2 Fishing, +2 Foraging, +2 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Athlete || +2 Nimble, +2 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Rogue || +2 Sneaking, +2 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Survival) || +2 Axe, +2 Spear, +2 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blunt) || +2 Long Blunt, +2 Short Blunt, +2 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blade) || +2 Long Blade, +2 Short Blade, +2 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥇  Expert Skill Traits===&lt;br /&gt;
Cost: 3 Points each. &#039;&#039;&#039;Max 1.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Expert Tailor || +3 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Expert Doctor || +3 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Expert Mechanic || +3 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Expert Electrician || +3 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Expert Metalworker || +3 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Expert Homesteader || +3 Cooking, +3 Farming, +3 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Expert Shooter || +3 Aiming, +3 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Expert Outdoorsman || +3 Fishing, +3 Foraging, +3 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Expert Athlete || +3 Nimble, +3 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Expert Rogue || +3 Sneaking, +3 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Survival) || +3 Axe, +3 Spear, +3 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blunt) || +3 Long Blunt, +3 Short Blunt, +3 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blade) || +3 Long Blade, +3 Short Blade, +3 Maintenance&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=427</id>
		<title>Template:Combat Rolls</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=427"/>
		<updated>2025-11-10T19:49:18Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* Melee Combat */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Combat =&lt;br /&gt;
Here at NWO, we pride ourselves on the sense of danger and risk ever-present around our characters and plots. Every combat encounter can be a heroic victory or a crushing defeat, and very often brings with it lethal consequences. Every fight should be entered with the understanding that this...could be it.&lt;br /&gt;
&lt;br /&gt;
While all players are expected to adhere to these rules when engaging in combat between characters on their own, we try to be more open and rewarding towards creativity when interacting with our Staff team in events and encounters. While the rules listed below are the usual standard used by our Storytellers and players during these Staff-hosted scenes, our NPC creations and environmental hazards sometimes require us to make adjustments to the mechanics of a scene to properly simulate the threats or conditions our players are facing. &lt;br /&gt;
&lt;br /&gt;
Not to worry, though, as this same flexibility extends to you as a player! If you want to perform actions not easily expressed through these rules, talk to the Staff running the scene you are within in the OoC chat tab, and they will very often work with you to try and actualize your imagined plan. This is no guarantee that it will work, but player creativity is something we always attempt to reward.&lt;br /&gt;
=== Actions &amp;amp; Movement ===&lt;br /&gt;
A combat round is typically comprised of an &#039;&#039;&#039;Action&#039;&#039;&#039; and &#039;&#039;&#039;Movement&#039;&#039;&#039;. &#039;&#039;&#039;Reactions&#039;&#039;&#039; can sometimes be taken in special circumstances, such as &#039;&#039;&#039;Attacks of Opportunity&#039;&#039;&#039; or while &#039;&#039;&#039;Grappling&#039;&#039;&#039; someone. Exceptions will be noted in the relevant sections.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Movement&#039;&#039;&#039; is self-explanatory—It is the way a character “moves” around a space. Characters must toggle their movement range on their Dice Panel and may only move in one path to a point within their movement range. Movement imposes a -2 shooting penalty, regardless of the order of one’s Action or Movement.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Actions&#039;&#039;&#039; include a variety of things such as &#039;&#039;&#039;Attacking&#039;&#039;&#039;, &#039;&#039;&#039;Grappling&#039;&#039;&#039;, &#039;&#039;&#039;Shoving&#039;&#039;&#039;, &#039;&#039;&#039;Throwing&#039;&#039;&#039;, &#039;&#039;&#039;Helping&#039;&#039;&#039;, or virtually anything that is not movement. &lt;br /&gt;
&lt;br /&gt;
Your combat roll in a scenario is determined by the weapon a character has equipped, but will generally be &#039;&#039;&#039;Melee&#039;&#039;&#039;, &#039;&#039;&#039;Ranged&#039;&#039;&#039;, or &#039;&#039;&#039;Unarmed&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Those who &#039;&#039;&#039;apply statuses (AoE, DoT, Suppressive Fire, etc.) or wish to utilize special rulings and mechanics are responsible for enforcing them&#039;&#039;&#039;. If you forget to apply your statuses or recall your rulings when using them on others, you lose that turn of said status. This rule applies to NPCs and PCs.&lt;br /&gt;
=== Engagement ===&lt;br /&gt;
Engagement occurs when one character enters another’s melee range. &#039;&#039;&#039;Engagement does not occur if the weapon being held is a ranged weapon&#039;&#039;&#039;. By default, this is one tile for all melee weapons except for spears, which have a two-tile range. &lt;br /&gt;
&lt;br /&gt;
While Engaged, characters cannot shoot with two-handed ranged weapons and cannot make any movement without triggering an AOO, including movement around an attacker.&lt;br /&gt;
=== Dashing ===&lt;br /&gt;
As an action, a character can double their movement speed from 6 to 12 tiles. Dashing is not the same as Disengaging, and can still provoke Engagement or Attacks of Opportunity.&lt;br /&gt;
=== Disarming ===&lt;br /&gt;
As an action, a character may choose to Disarm their target so long as they have at least one hand free. An attacker with only one hand free attempts their Disarm at disadvantage. An attacker with two hands free attempts their Disarm with no penalty. &lt;br /&gt;
&lt;br /&gt;
Disarms are made with a normal one-handed or unarmed attack roll. On success, the target is disarmed, dropping their weapon on the ground or in the hands of their attacker if the attacker has enough hands free (Attacker’s choice). The disarming character also deals damage equal to the weapon in their hand (1 damage if unarmed). &lt;br /&gt;
=== Swapping Weapons ===&lt;br /&gt;
Swapping weapons in combat is risky, but often necessary. Swapping weapons takes an action in most scenarios. However, if the weapon you are attempting to swap to is a one-handed weapon that is readily available, such as a holstered sidearm or a sheathed knife, you can choose to drop your currently equipped weapon on the ground to Quick Draw your one-handed weapon within the same turn. Your dropped weapon will require an action to pick up and equip once more. Please note that attachments, equipment, or other factors such as backstory or experience do not affect this rule.&lt;br /&gt;
=== Disengaging ===&lt;br /&gt;
As an action, a character may choose to Disengage from combat. This allows them to make any movement, barring any conditions which may prevent said movement, without proccing AOOs. &lt;br /&gt;
&lt;br /&gt;
If you are Engaged by two or more people, Disengage actions require a Resolve (DC 12) roll to be attempted. &lt;br /&gt;
=== Helping ===&lt;br /&gt;
As an action, any character can Help another character by rolling a Resolve (DC 10) roll. On success, the target gains advantage on their next roll, depending on the nature of the help. Helping must be done with a specific intention that is laid out clearly and concisely by the helping player on their turn. In other words, you cannot blanket Help on every possible roll that another character might do. It is recommended to have a brief OOC discussion about what your Helping action will look like with the target. &lt;br /&gt;
&lt;br /&gt;
Some examples of Help rolls might include:&lt;br /&gt;
* Attempting to restrain someone who is already grappled to give the attacking player an easier strike (Melee or Ranged Attack—While Grappling)&lt;br /&gt;
* Assisting a player in moving a heavy object (Strength)&lt;br /&gt;
* Providing an extra pair of hands during a medical emergency (First Aid)&lt;br /&gt;
* Providing an extra pair of hands while making dinner (Cooking)&lt;br /&gt;
&lt;br /&gt;
This list is, of course, non-exhaustive. Be creative!&lt;br /&gt;
=== Stealth &amp;amp; Ambushing ===&lt;br /&gt;
Ambushing a combatant gives several advantages. &lt;br /&gt;
&lt;br /&gt;
To attempt an Ambush, the attacker must succeed on a Hiding roll against the defender’s Perception roll. If the defender’s Perception roll beats the attacker’s Hiding roll, combat initiates as per usual, and no bonuses are obtained.&lt;br /&gt;
&lt;br /&gt;
Combatants who are ambushed are Surprised and cannot act for the first round of combat. Conversely, attackers who have Ambushed a combatant or combatants have advantage on attack rolls made during this Surprise round.&lt;br /&gt;
=== Overwatch ===&lt;br /&gt;
While using a weapon, players can use their turn to go on Overwatch. Overwatch allows a player to “delay” their attack until certain events happen. Players must define these events clearly and concisely on their turn.&lt;br /&gt;
&lt;br /&gt;
Examples of these events might include:&lt;br /&gt;
* When a specific character moves&lt;br /&gt;
* When any non-ally character enters a certain area&lt;br /&gt;
* Before or after another character acts&lt;br /&gt;
* If another character takes or deals damage&lt;br /&gt;
&lt;br /&gt;
When Overwatch is triggered, typical range and rolling rules still apply. If Overwatch is not used, Overwatch ends on the player’s following turn unless used again.&lt;br /&gt;
=== Throwables, Areas of Effect (AoE), and Conditions ===&lt;br /&gt;
Various weapons, such as throwables, may leave an Area of Affect (AOE). AOEs can apply various Conditions to a target. Some examples of these Conditions include:&lt;br /&gt;
* Burning (Fire)&lt;br /&gt;
* Burning (Acid)&lt;br /&gt;
* Electrocution&lt;br /&gt;
* Smoke&lt;br /&gt;
* Poison&lt;br /&gt;
&lt;br /&gt;
All of these Conditions apply the same mechanical effect. Regardless of the type, Conditions apply disadvantage to the afflicted character. &lt;br /&gt;
&lt;br /&gt;
As a rule of thumb, AOEs affect a 3x3 tile area and linger on the ground for 3 turns unless specified by the item or Storyteller. A character moving into an existing AOE requires passing a DC 10 Robustness check or else risk taking on the Condition imposed by it. &lt;br /&gt;
&lt;br /&gt;
An action can be spent by an afflicted character to remove a Condition unless attempted while within an AOE that applies it. Conditions can be stacked up to two times.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Name !! Damage On-Hit !! Area of Effect&lt;br /&gt;
|-&lt;br /&gt;
| Molotov || 2 || 3x3&lt;br /&gt;
|-&lt;br /&gt;
| Smoke Bomb || - || 5x5&lt;br /&gt;
|-&lt;br /&gt;
| Acid Attack || 3 || Single Target&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Throwables can be tossed onto a tile, affecting the adjacent 3x3 area, by making a Ranged Attack (DC 12) roll. On failure, the throwable is placed in a random location within 2 tiles of the intended location.&lt;br /&gt;
=== Attacks of Opportunity ===&lt;br /&gt;
Attacks of Opportunity (AOO) or Opportunity Attacks can occur when a target chooses to leave an attacker’s Engagement range. Please note that moving while staying within range of an attacker does not trigger an AOO. By default, all Human characters can use one AOO per round of combat, resetting on that character’s turn. Zombies, infected, and other creatures may have more attacks of opportunity, dependent on their individual skill sets and Storyteller preferences.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ranged weapons cannot utilize Attacks of Opportunity to deal a Ranged Attack&#039;&#039;&#039;. A person with a ranged weapon may make an &#039;&#039;&#039;Improvised Weapon&#039;&#039;&#039; roll to hit someone with a stock or “pistol whip,” but they cannot fire at a retreating opponent. &lt;br /&gt;
=== Going Prone ===&lt;br /&gt;
Prone refers to the act of lying on one’s back or stomach on the ground. In NWO, one can take a prone stance or be made to take a prone stance willingly or unwillingly. The Prone condition comes with several advantages and disadvantages:&lt;br /&gt;
* Prone characters can crawl to an adjacent tile while prone. If a character is prone and behind cover, they cannot make an attack unless their target is adjacent and not on the opposite side of said cover. &lt;br /&gt;
* Ranged attacks against prone characters are at a disadvantage.&lt;br /&gt;
** Ranged attacks have advantage against prone targets within CQC/point-blank range (1 tile). If Engaged, typical Engagement rules apply. &lt;br /&gt;
* Melee attacks against prone characters are at advantage.&lt;br /&gt;
* Prone characters may use their movement or action to stand from being prone.&lt;br /&gt;
* Prone characters cannot Engage characters within melee range.&lt;br /&gt;
=== Shoving ===&lt;br /&gt;
Players may also choose to shove another player to displace or knock them down.&lt;br /&gt;
&lt;br /&gt;
One player can shove another by engaging in a contested STR vs. STR or FIT (Defender’s choice) roll. Defenders can choose to automatically fail this roll if desired.&lt;br /&gt;
&lt;br /&gt;
Shoving players can knock the target Prone or knock them away—Attacker’s choice. &lt;br /&gt;
* If knocked Prone, Prone rules apply.&lt;br /&gt;
* If knocked away, the target gets shoved back [Attacker’s ½ STR] tiles in the direction of the Attacker’s choice.&lt;br /&gt;
=== Grappling ===&lt;br /&gt;
Characters may choose to Grapple a target to prevent escape or otherwise impose various disadvantages.&lt;br /&gt;
&lt;br /&gt;
One character may grapple another by engaging in a STR vs STR contest, so long as one of the Attacker’s hands is free. Attackers can drop a two-handed weapon same turn to attempt to Grapple a target, leaving their weapon on the ground. This weapon would need an action to be retrieved once more.&lt;br /&gt;
&lt;br /&gt;
A Grappled character has the following disadvantages imposed upon them until the Grapple is broken:&lt;br /&gt;
* Movement is reduced to 0. &lt;br /&gt;
** Grappled characters can rotate around the grappler, but cannot disengage or exit the tile adjacent. &lt;br /&gt;
* All attacks from the target are made at disadvantage&lt;br /&gt;
* Grappled characters do not provoke AOOs&lt;br /&gt;
&lt;br /&gt;
On the Grappled character’s turn, they may use their action to attempt to break free with another STR vs STR contest. On fail, the Grappled character remains Grappled. &lt;br /&gt;
&lt;br /&gt;
Characters grappling with a target can force that target to a prone state as an action with no additional contest. Both characters are considered Prone. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grappled targets are not restrained or disarmed&#039;&#039;&#039;. Grappled characters may still attack any target that would normally be capable of with the weapon they may (or may not) have equipped, including the grappler, at disadvantage. A successful attack on the grappler from the target forces a reroll of the contest. &lt;br /&gt;
&lt;br /&gt;
Grapplers may attack any character with a one-handed weapon at disadvantage. Disadvantage does not apply if their target is &#039;&#039;&#039;Cooperative&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Targets under duress, such as hostages, are considered &#039;&#039;&#039;Uncooperative&#039;&#039;&#039;, even if they are not attempting to actively fight their Grappler. Targets who are unconscious or otherwise incapacitated are considered &#039;&#039;&#039;Cooperative&#039;&#039;&#039;.&lt;br /&gt;
=== Human Shield ===&lt;br /&gt;
As an action or reaction, a Grappler may choose to use their target or themselves as a &#039;&#039;&#039;Human Shield&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When using a Human Shield, the Grappler gains a Full Cover bonus. When the Grappler is acting as a Human Shield, the target gains a Full Cover bonus. &lt;br /&gt;
&lt;br /&gt;
Any Attacks made against the &#039;&#039;&#039;Shielded&#039;&#039;&#039; character will require a roll from the Human Shield as well. Regardless of the Shielded character’s success or failure, if the Human Shield fails their Defense roll, they take the attack’s damage as well.&lt;br /&gt;
=== Focusing ===&lt;br /&gt;
Focusing for a turn or starting combat while Focused gives Advantage to a Ranged Attack made on the next turn. If you do not fire after aiming, this bonus is lost unless Aiming is maintained.&lt;br /&gt;
&lt;br /&gt;
Focusing can be done from cover. Becoming engaged or taking melee damage while Focused “breaks” your focus. If a ranged attack is made against a Focused character, that character must make a DC 12 Resolve roll to remain focused.&lt;br /&gt;
&lt;br /&gt;
Focus is maintained so long as the character does not move or otherwise have their Focus broken. Once broken, Focus needs to be done once more.&lt;br /&gt;
=== Reloading ===&lt;br /&gt;
Once your available ammunition (clip, magazine, etc.) is spent, players using a ranged weapon must make a roll to Reload (DC 10). On success, their Reload is instant, allowing them to take a normal action. On failure, a turn must be dedicated to Reloading their weapon. &lt;br /&gt;
=== Suppressive Fire ===&lt;br /&gt;
With a fully automatic weapon aimed at a player behind cover, attacking players can lay down Suppressive Fire. More than half of the weapon’s magazine must remain.&lt;br /&gt;
&lt;br /&gt;
Suppressive Fire requires a typical Ranged ATK vs. Ranged DEF roll. On the attacker’s success, typical damage applies. &lt;br /&gt;
&lt;br /&gt;
Regardless of success or failure, the attacking player burns their magazine of their automatic weapon to attempt to negate movement or action from the defending player. If a defender attempts to move or act in a way that would expose them to Suppressive Fire, they must make a Resolve (DC 12) roll. On success, they may take their action as normal. On failure, they are startled in place. A target under Suppressive Fire may take any other action or reaction that would not expose them to suppressive fire. &lt;br /&gt;
&lt;br /&gt;
Suppressive Fire ends at the beginning of the attacker’s next turn. At the end of Suppressive Fire, the attacking player must reload to continue using their currently equipped weapon.&lt;br /&gt;
= Armor =&lt;br /&gt;
Armor is applied as Light, Medium, or Heavy armor. Each type of armor has its own Armor Stacks and Drawbacks. &lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Armor Rules &lt;br /&gt;
|-&lt;br /&gt;
| Type || Stacks || Drawbacks&lt;br /&gt;
|-&lt;br /&gt;
| Light || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Medium || 2 || -1 Initiative, -1 Movement&lt;br /&gt;
|-&lt;br /&gt;
| Heavy || 3 || -2 Initiative, -2 Movement&lt;br /&gt;
|}&lt;br /&gt;
If an attack is taken while wearing armor, a stack is consumed to reduce the damage of that attack to 1. Critical attacks deal 2 stacks of damage to armor and deal full damage if only one Armor Stack is remaining.&lt;br /&gt;
= Ranged Combat =&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
| Ranged Defense || -&lt;br /&gt;
|-&lt;br /&gt;
| Ranged Attack || Aiming/2&lt;br /&gt;
|}&lt;br /&gt;
Several aspects of the mechanical environment affect ranged rolls, including the lighting of an area, whether or not a weapon is scoped, and more. Please note that Sniping a character from a distance, while an option, is still subject to appropriate combat rules, CK rules, and ticketing. The range of a ranged weapon is dictated by in-game line-of-sight (LoS). Consider the following before making an attack roll:&lt;br /&gt;
* Whether the target is ‘visible’ &lt;br /&gt;
* Whether there is a clear line of fire from attacker to defender&lt;br /&gt;
* Whether any allies are engaged in any actions that may put them at risk of being hit (i.e. grappling)&lt;br /&gt;
The above factors determine whether a target can be hit. In the special case of the flamethrower, its range is 15 in-game tiles.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Ranged Weapons&lt;br /&gt;
! Weapon Type !! Damage !! Crit Requirement !! Crit Dmg&lt;br /&gt;
|-&lt;br /&gt;
| Assault Weapons || 4 || Double 5/Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| M60/LMG&#039;s|| 5 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Rifles &amp;amp; Shotguns || 4 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Pistols || 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Flamethrowers &amp;lt;ref&amp;gt;Flamethrower Crit Rule: If a character holding a Flamethrower is hit by a Critical Hit, there&#039;s a 50% chance (1-2-3 on a d6 roll) that the fuel canister explodes. Dealing 2 Damage to the wielder.&amp;lt;/ref&amp;gt; &amp;lt;ref&amp;gt;Flamethrower Cover Rules: Engaged Condition and a 50% body coverage behind a tile don&#039;t offer Cover from a Flamethrower attack. If a Flamethrower attack targets a character engaged with other characters, all of them will be hit by the attack and roll Ranged Defence. Only a coverage of at least 80% behind a Tile grants the Cover modifier from a Flamethrower Attack. (ex: Only a limb/head visible)&amp;lt;/ref&amp;gt; || 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Crossbows || 4 || Double 6 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Melee Combat =&lt;br /&gt;
Melee combat occurs with contested rolls. The success of a melee attack roll is dependent on the following values:&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Melee Examples&lt;br /&gt;
! Melee Attack !! ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|-&lt;br /&gt;
| Melee Defense|| ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|}&lt;br /&gt;
Each Melee weapon has its own damage and crit requirements.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Melee Examples&lt;br /&gt;
! Weapon Type || Damage || Crit Requirement || Crit Damage&lt;br /&gt;
|-&lt;br /&gt;
| Two-Handed|| 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| One-Handed &amp;lt;ref&amp;gt;One-Handed Double-Tap: Characters utilizing one-handed weapons can forgo their movement to make a second attack at disadvantage. This attack cannot crit.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Spears &amp;lt;ref&amp;gt;Spear Range Rule: A character holding a spear engages hostile characters within 2 Tiles instead of 1.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 3&lt;br /&gt;
|-&lt;br /&gt;
| One-Handed Machete/Handaxe/Kukri|| 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Bare Hands, Improvised, Weapon Stocks|| 1 || - || -&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=426</id>
		<title>Template:Combat Rolls</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=426"/>
		<updated>2025-11-10T19:48:00Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* Melee Combat */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Combat =&lt;br /&gt;
Here at NWO, we pride ourselves on the sense of danger and risk ever-present around our characters and plots. Every combat encounter can be a heroic victory or a crushing defeat, and very often brings with it lethal consequences. Every fight should be entered with the understanding that this...could be it.&lt;br /&gt;
&lt;br /&gt;
While all players are expected to adhere to these rules when engaging in combat between characters on their own, we try to be more open and rewarding towards creativity when interacting with our Staff team in events and encounters. While the rules listed below are the usual standard used by our Storytellers and players during these Staff-hosted scenes, our NPC creations and environmental hazards sometimes require us to make adjustments to the mechanics of a scene to properly simulate the threats or conditions our players are facing. &lt;br /&gt;
&lt;br /&gt;
Not to worry, though, as this same flexibility extends to you as a player! If you want to perform actions not easily expressed through these rules, talk to the Staff running the scene you are within in the OoC chat tab, and they will very often work with you to try and actualize your imagined plan. This is no guarantee that it will work, but player creativity is something we always attempt to reward.&lt;br /&gt;
=== Actions &amp;amp; Movement ===&lt;br /&gt;
A combat round is typically comprised of an &#039;&#039;&#039;Action&#039;&#039;&#039; and &#039;&#039;&#039;Movement&#039;&#039;&#039;. &#039;&#039;&#039;Reactions&#039;&#039;&#039; can sometimes be taken in special circumstances, such as &#039;&#039;&#039;Attacks of Opportunity&#039;&#039;&#039; or while &#039;&#039;&#039;Grappling&#039;&#039;&#039; someone. Exceptions will be noted in the relevant sections.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Movement&#039;&#039;&#039; is self-explanatory—It is the way a character “moves” around a space. Characters must toggle their movement range on their Dice Panel and may only move in one path to a point within their movement range. Movement imposes a -2 shooting penalty, regardless of the order of one’s Action or Movement.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Actions&#039;&#039;&#039; include a variety of things such as &#039;&#039;&#039;Attacking&#039;&#039;&#039;, &#039;&#039;&#039;Grappling&#039;&#039;&#039;, &#039;&#039;&#039;Shoving&#039;&#039;&#039;, &#039;&#039;&#039;Throwing&#039;&#039;&#039;, &#039;&#039;&#039;Helping&#039;&#039;&#039;, or virtually anything that is not movement. &lt;br /&gt;
&lt;br /&gt;
Your combat roll in a scenario is determined by the weapon a character has equipped, but will generally be &#039;&#039;&#039;Melee&#039;&#039;&#039;, &#039;&#039;&#039;Ranged&#039;&#039;&#039;, or &#039;&#039;&#039;Unarmed&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Those who &#039;&#039;&#039;apply statuses (AoE, DoT, Suppressive Fire, etc.) or wish to utilize special rulings and mechanics are responsible for enforcing them&#039;&#039;&#039;. If you forget to apply your statuses or recall your rulings when using them on others, you lose that turn of said status. This rule applies to NPCs and PCs.&lt;br /&gt;
=== Engagement ===&lt;br /&gt;
Engagement occurs when one character enters another’s melee range. &#039;&#039;&#039;Engagement does not occur if the weapon being held is a ranged weapon&#039;&#039;&#039;. By default, this is one tile for all melee weapons except for spears, which have a two-tile range. &lt;br /&gt;
&lt;br /&gt;
While Engaged, characters cannot shoot with two-handed ranged weapons and cannot make any movement without triggering an AOO, including movement around an attacker.&lt;br /&gt;
=== Dashing ===&lt;br /&gt;
As an action, a character can double their movement speed from 6 to 12 tiles. Dashing is not the same as Disengaging, and can still provoke Engagement or Attacks of Opportunity.&lt;br /&gt;
=== Disarming ===&lt;br /&gt;
As an action, a character may choose to Disarm their target so long as they have at least one hand free. An attacker with only one hand free attempts their Disarm at disadvantage. An attacker with two hands free attempts their Disarm with no penalty. &lt;br /&gt;
&lt;br /&gt;
Disarms are made with a normal one-handed or unarmed attack roll. On success, the target is disarmed, dropping their weapon on the ground or in the hands of their attacker if the attacker has enough hands free (Attacker’s choice). The disarming character also deals damage equal to the weapon in their hand (1 damage if unarmed). &lt;br /&gt;
=== Swapping Weapons ===&lt;br /&gt;
Swapping weapons in combat is risky, but often necessary. Swapping weapons takes an action in most scenarios. However, if the weapon you are attempting to swap to is a one-handed weapon that is readily available, such as a holstered sidearm or a sheathed knife, you can choose to drop your currently equipped weapon on the ground to Quick Draw your one-handed weapon within the same turn. Your dropped weapon will require an action to pick up and equip once more. Please note that attachments, equipment, or other factors such as backstory or experience do not affect this rule.&lt;br /&gt;
=== Disengaging ===&lt;br /&gt;
As an action, a character may choose to Disengage from combat. This allows them to make any movement, barring any conditions which may prevent said movement, without proccing AOOs. &lt;br /&gt;
&lt;br /&gt;
If you are Engaged by two or more people, Disengage actions require a Resolve (DC 12) roll to be attempted. &lt;br /&gt;
=== Helping ===&lt;br /&gt;
As an action, any character can Help another character by rolling a Resolve (DC 10) roll. On success, the target gains advantage on their next roll, depending on the nature of the help. Helping must be done with a specific intention that is laid out clearly and concisely by the helping player on their turn. In other words, you cannot blanket Help on every possible roll that another character might do. It is recommended to have a brief OOC discussion about what your Helping action will look like with the target. &lt;br /&gt;
&lt;br /&gt;
Some examples of Help rolls might include:&lt;br /&gt;
* Attempting to restrain someone who is already grappled to give the attacking player an easier strike (Melee or Ranged Attack—While Grappling)&lt;br /&gt;
* Assisting a player in moving a heavy object (Strength)&lt;br /&gt;
* Providing an extra pair of hands during a medical emergency (First Aid)&lt;br /&gt;
* Providing an extra pair of hands while making dinner (Cooking)&lt;br /&gt;
&lt;br /&gt;
This list is, of course, non-exhaustive. Be creative!&lt;br /&gt;
=== Stealth &amp;amp; Ambushing ===&lt;br /&gt;
Ambushing a combatant gives several advantages. &lt;br /&gt;
&lt;br /&gt;
To attempt an Ambush, the attacker must succeed on a Hiding roll against the defender’s Perception roll. If the defender’s Perception roll beats the attacker’s Hiding roll, combat initiates as per usual, and no bonuses are obtained.&lt;br /&gt;
&lt;br /&gt;
Combatants who are ambushed are Surprised and cannot act for the first round of combat. Conversely, attackers who have Ambushed a combatant or combatants have advantage on attack rolls made during this Surprise round.&lt;br /&gt;
=== Overwatch ===&lt;br /&gt;
While using a weapon, players can use their turn to go on Overwatch. Overwatch allows a player to “delay” their attack until certain events happen. Players must define these events clearly and concisely on their turn.&lt;br /&gt;
&lt;br /&gt;
Examples of these events might include:&lt;br /&gt;
* When a specific character moves&lt;br /&gt;
* When any non-ally character enters a certain area&lt;br /&gt;
* Before or after another character acts&lt;br /&gt;
* If another character takes or deals damage&lt;br /&gt;
&lt;br /&gt;
When Overwatch is triggered, typical range and rolling rules still apply. If Overwatch is not used, Overwatch ends on the player’s following turn unless used again.&lt;br /&gt;
=== Throwables, Areas of Effect (AoE), and Conditions ===&lt;br /&gt;
Various weapons, such as throwables, may leave an Area of Affect (AOE). AOEs can apply various Conditions to a target. Some examples of these Conditions include:&lt;br /&gt;
* Burning (Fire)&lt;br /&gt;
* Burning (Acid)&lt;br /&gt;
* Electrocution&lt;br /&gt;
* Smoke&lt;br /&gt;
* Poison&lt;br /&gt;
&lt;br /&gt;
All of these Conditions apply the same mechanical effect. Regardless of the type, Conditions apply disadvantage to the afflicted character. &lt;br /&gt;
&lt;br /&gt;
As a rule of thumb, AOEs affect a 3x3 tile area and linger on the ground for 3 turns unless specified by the item or Storyteller. A character moving into an existing AOE requires passing a DC 10 Robustness check or else risk taking on the Condition imposed by it. &lt;br /&gt;
&lt;br /&gt;
An action can be spent by an afflicted character to remove a Condition unless attempted while within an AOE that applies it. Conditions can be stacked up to two times.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Name !! Damage On-Hit !! Area of Effect&lt;br /&gt;
|-&lt;br /&gt;
| Molotov || 2 || 3x3&lt;br /&gt;
|-&lt;br /&gt;
| Smoke Bomb || - || 5x5&lt;br /&gt;
|-&lt;br /&gt;
| Acid Attack || 3 || Single Target&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Throwables can be tossed onto a tile, affecting the adjacent 3x3 area, by making a Ranged Attack (DC 12) roll. On failure, the throwable is placed in a random location within 2 tiles of the intended location.&lt;br /&gt;
=== Attacks of Opportunity ===&lt;br /&gt;
Attacks of Opportunity (AOO) or Opportunity Attacks can occur when a target chooses to leave an attacker’s Engagement range. Please note that moving while staying within range of an attacker does not trigger an AOO. By default, all Human characters can use one AOO per round of combat, resetting on that character’s turn. Zombies, infected, and other creatures may have more attacks of opportunity, dependent on their individual skill sets and Storyteller preferences.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ranged weapons cannot utilize Attacks of Opportunity to deal a Ranged Attack&#039;&#039;&#039;. A person with a ranged weapon may make an &#039;&#039;&#039;Improvised Weapon&#039;&#039;&#039; roll to hit someone with a stock or “pistol whip,” but they cannot fire at a retreating opponent. &lt;br /&gt;
=== Going Prone ===&lt;br /&gt;
Prone refers to the act of lying on one’s back or stomach on the ground. In NWO, one can take a prone stance or be made to take a prone stance willingly or unwillingly. The Prone condition comes with several advantages and disadvantages:&lt;br /&gt;
* Prone characters can crawl to an adjacent tile while prone. If a character is prone and behind cover, they cannot make an attack unless their target is adjacent and not on the opposite side of said cover. &lt;br /&gt;
* Ranged attacks against prone characters are at a disadvantage.&lt;br /&gt;
** Ranged attacks have advantage against prone targets within CQC/point-blank range (1 tile). If Engaged, typical Engagement rules apply. &lt;br /&gt;
* Melee attacks against prone characters are at advantage.&lt;br /&gt;
* Prone characters may use their movement or action to stand from being prone.&lt;br /&gt;
* Prone characters cannot Engage characters within melee range.&lt;br /&gt;
=== Shoving ===&lt;br /&gt;
Players may also choose to shove another player to displace or knock them down.&lt;br /&gt;
&lt;br /&gt;
One player can shove another by engaging in a contested STR vs. STR or FIT (Defender’s choice) roll. Defenders can choose to automatically fail this roll if desired.&lt;br /&gt;
&lt;br /&gt;
Shoving players can knock the target Prone or knock them away—Attacker’s choice. &lt;br /&gt;
* If knocked Prone, Prone rules apply.&lt;br /&gt;
* If knocked away, the target gets shoved back [Attacker’s ½ STR] tiles in the direction of the Attacker’s choice.&lt;br /&gt;
=== Grappling ===&lt;br /&gt;
Characters may choose to Grapple a target to prevent escape or otherwise impose various disadvantages.&lt;br /&gt;
&lt;br /&gt;
One character may grapple another by engaging in a STR vs STR contest, so long as one of the Attacker’s hands is free. Attackers can drop a two-handed weapon same turn to attempt to Grapple a target, leaving their weapon on the ground. This weapon would need an action to be retrieved once more.&lt;br /&gt;
&lt;br /&gt;
A Grappled character has the following disadvantages imposed upon them until the Grapple is broken:&lt;br /&gt;
* Movement is reduced to 0. &lt;br /&gt;
** Grappled characters can rotate around the grappler, but cannot disengage or exit the tile adjacent. &lt;br /&gt;
* All attacks from the target are made at disadvantage&lt;br /&gt;
* Grappled characters do not provoke AOOs&lt;br /&gt;
&lt;br /&gt;
On the Grappled character’s turn, they may use their action to attempt to break free with another STR vs STR contest. On fail, the Grappled character remains Grappled. &lt;br /&gt;
&lt;br /&gt;
Characters grappling with a target can force that target to a prone state as an action with no additional contest. Both characters are considered Prone. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grappled targets are not restrained or disarmed&#039;&#039;&#039;. Grappled characters may still attack any target that would normally be capable of with the weapon they may (or may not) have equipped, including the grappler, at disadvantage. A successful attack on the grappler from the target forces a reroll of the contest. &lt;br /&gt;
&lt;br /&gt;
Grapplers may attack any character with a one-handed weapon at disadvantage. Disadvantage does not apply if their target is &#039;&#039;&#039;Cooperative&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Targets under duress, such as hostages, are considered &#039;&#039;&#039;Uncooperative&#039;&#039;&#039;, even if they are not attempting to actively fight their Grappler. Targets who are unconscious or otherwise incapacitated are considered &#039;&#039;&#039;Cooperative&#039;&#039;&#039;.&lt;br /&gt;
=== Human Shield ===&lt;br /&gt;
As an action or reaction, a Grappler may choose to use their target or themselves as a &#039;&#039;&#039;Human Shield&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When using a Human Shield, the Grappler gains a Full Cover bonus. When the Grappler is acting as a Human Shield, the target gains a Full Cover bonus. &lt;br /&gt;
&lt;br /&gt;
Any Attacks made against the &#039;&#039;&#039;Shielded&#039;&#039;&#039; character will require a roll from the Human Shield as well. Regardless of the Shielded character’s success or failure, if the Human Shield fails their Defense roll, they take the attack’s damage as well.&lt;br /&gt;
=== Focusing ===&lt;br /&gt;
Focusing for a turn or starting combat while Focused gives Advantage to a Ranged Attack made on the next turn. If you do not fire after aiming, this bonus is lost unless Aiming is maintained.&lt;br /&gt;
&lt;br /&gt;
Focusing can be done from cover. Becoming engaged or taking melee damage while Focused “breaks” your focus. If a ranged attack is made against a Focused character, that character must make a DC 12 Resolve roll to remain focused.&lt;br /&gt;
&lt;br /&gt;
Focus is maintained so long as the character does not move or otherwise have their Focus broken. Once broken, Focus needs to be done once more.&lt;br /&gt;
=== Reloading ===&lt;br /&gt;
Once your available ammunition (clip, magazine, etc.) is spent, players using a ranged weapon must make a roll to Reload (DC 10). On success, their Reload is instant, allowing them to take a normal action. On failure, a turn must be dedicated to Reloading their weapon. &lt;br /&gt;
=== Suppressive Fire ===&lt;br /&gt;
With a fully automatic weapon aimed at a player behind cover, attacking players can lay down Suppressive Fire. More than half of the weapon’s magazine must remain.&lt;br /&gt;
&lt;br /&gt;
Suppressive Fire requires a typical Ranged ATK vs. Ranged DEF roll. On the attacker’s success, typical damage applies. &lt;br /&gt;
&lt;br /&gt;
Regardless of success or failure, the attacking player burns their magazine of their automatic weapon to attempt to negate movement or action from the defending player. If a defender attempts to move or act in a way that would expose them to Suppressive Fire, they must make a Resolve (DC 12) roll. On success, they may take their action as normal. On failure, they are startled in place. A target under Suppressive Fire may take any other action or reaction that would not expose them to suppressive fire. &lt;br /&gt;
&lt;br /&gt;
Suppressive Fire ends at the beginning of the attacker’s next turn. At the end of Suppressive Fire, the attacking player must reload to continue using their currently equipped weapon.&lt;br /&gt;
= Armor =&lt;br /&gt;
Armor is applied as Light, Medium, or Heavy armor. Each type of armor has its own Armor Stacks and Drawbacks. &lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Armor Rules &lt;br /&gt;
|-&lt;br /&gt;
| Type || Stacks || Drawbacks&lt;br /&gt;
|-&lt;br /&gt;
| Light || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Medium || 2 || -1 Initiative, -1 Movement&lt;br /&gt;
|-&lt;br /&gt;
| Heavy || 3 || -2 Initiative, -2 Movement&lt;br /&gt;
|}&lt;br /&gt;
If an attack is taken while wearing armor, a stack is consumed to reduce the damage of that attack to 1. Critical attacks deal 2 stacks of damage to armor and deal full damage if only one Armor Stack is remaining.&lt;br /&gt;
= Ranged Combat =&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
| Ranged Defense || -&lt;br /&gt;
|-&lt;br /&gt;
| Ranged Attack || Aiming/2&lt;br /&gt;
|}&lt;br /&gt;
Several aspects of the mechanical environment affect ranged rolls, including the lighting of an area, whether or not a weapon is scoped, and more. Please note that Sniping a character from a distance, while an option, is still subject to appropriate combat rules, CK rules, and ticketing. The range of a ranged weapon is dictated by in-game line-of-sight (LoS). Consider the following before making an attack roll:&lt;br /&gt;
* Whether the target is ‘visible’ &lt;br /&gt;
* Whether there is a clear line of fire from attacker to defender&lt;br /&gt;
* Whether any allies are engaged in any actions that may put them at risk of being hit (i.e. grappling)&lt;br /&gt;
The above factors determine whether a target can be hit. In the special case of the flamethrower, its range is 15 in-game tiles.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Ranged Weapons&lt;br /&gt;
! Weapon Type !! Damage !! Crit Requirement !! Crit Dmg&lt;br /&gt;
|-&lt;br /&gt;
| Assault Weapons || 4 || Double 5/Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| M60/LMG&#039;s|| 5 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Rifles &amp;amp; Shotguns || 4 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Pistols || 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Flamethrowers &amp;lt;ref&amp;gt;Flamethrower Crit Rule: If a character holding a Flamethrower is hit by a Critical Hit, there&#039;s a 50% chance (1-2-3 on a d6 roll) that the fuel canister explodes. Dealing 2 Damage to the wielder.&amp;lt;/ref&amp;gt; &amp;lt;ref&amp;gt;Flamethrower Cover Rules: Engaged Condition and a 50% body coverage behind a tile don&#039;t offer Cover from a Flamethrower attack. If a Flamethrower attack targets a character engaged with other characters, all of them will be hit by the attack and roll Ranged Defence. Only a coverage of at least 80% behind a Tile grants the Cover modifier from a Flamethrower Attack. (ex: Only a limb/head visible)&amp;lt;/ref&amp;gt; || 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Crossbows || 4 || Double 6 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Melee Combat =&lt;br /&gt;
Melee combat occurs with contested rolls. The success of a melee attack roll is dependent on the following values:&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Melee Examples&lt;br /&gt;
! Melee Attack !! ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|-&lt;br /&gt;
| Melee Defense|| ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|}&lt;br /&gt;
Each Melee weapon has its own damage and crit requirements.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Melee Examples&lt;br /&gt;
! Weapon Type || Damage || Crit Requirement || Crit Damage&lt;br /&gt;
|-&lt;br /&gt;
| Two-Handed|| 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| One-Handed &amp;lt;ref&amp;gt;One-Handed Double-Tap: Characters utilizing one-handed weapons can forgo their movement to make a second attack at disadvantage. This attack cannot crit.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Spears &amp;lt;ref&amp;gt;Spear Range Rule: A character holding a spear engages hostile characters within 2 Tiles instead of 1.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 3&lt;br /&gt;
|-&lt;br /&gt;
| Machete/Handaxe/Kukri|| 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Bare Hands, Improvised, Weapon Stocks|| 1 || - || -&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=425</id>
		<title>Template:Combat Rolls</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=425"/>
		<updated>2025-11-10T19:38:32Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* Melee Combat */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Combat =&lt;br /&gt;
Here at NWO, we pride ourselves on the sense of danger and risk ever-present around our characters and plots. Every combat encounter can be a heroic victory or a crushing defeat, and very often brings with it lethal consequences. Every fight should be entered with the understanding that this...could be it.&lt;br /&gt;
&lt;br /&gt;
While all players are expected to adhere to these rules when engaging in combat between characters on their own, we try to be more open and rewarding towards creativity when interacting with our Staff team in events and encounters. While the rules listed below are the usual standard used by our Storytellers and players during these Staff-hosted scenes, our NPC creations and environmental hazards sometimes require us to make adjustments to the mechanics of a scene to properly simulate the threats or conditions our players are facing. &lt;br /&gt;
&lt;br /&gt;
Not to worry, though, as this same flexibility extends to you as a player! If you want to perform actions not easily expressed through these rules, talk to the Staff running the scene you are within in the OoC chat tab, and they will very often work with you to try and actualize your imagined plan. This is no guarantee that it will work, but player creativity is something we always attempt to reward.&lt;br /&gt;
=== Actions &amp;amp; Movement ===&lt;br /&gt;
A combat round is typically comprised of an &#039;&#039;&#039;Action&#039;&#039;&#039; and &#039;&#039;&#039;Movement&#039;&#039;&#039;. &#039;&#039;&#039;Reactions&#039;&#039;&#039; can sometimes be taken in special circumstances, such as &#039;&#039;&#039;Attacks of Opportunity&#039;&#039;&#039; or while &#039;&#039;&#039;Grappling&#039;&#039;&#039; someone. Exceptions will be noted in the relevant sections.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Movement&#039;&#039;&#039; is self-explanatory—It is the way a character “moves” around a space. Characters must toggle their movement range on their Dice Panel and may only move in one path to a point within their movement range. Movement imposes a -2 shooting penalty, regardless of the order of one’s Action or Movement.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Actions&#039;&#039;&#039; include a variety of things such as &#039;&#039;&#039;Attacking&#039;&#039;&#039;, &#039;&#039;&#039;Grappling&#039;&#039;&#039;, &#039;&#039;&#039;Shoving&#039;&#039;&#039;, &#039;&#039;&#039;Throwing&#039;&#039;&#039;, &#039;&#039;&#039;Helping&#039;&#039;&#039;, or virtually anything that is not movement. &lt;br /&gt;
&lt;br /&gt;
Your combat roll in a scenario is determined by the weapon a character has equipped, but will generally be &#039;&#039;&#039;Melee&#039;&#039;&#039;, &#039;&#039;&#039;Ranged&#039;&#039;&#039;, or &#039;&#039;&#039;Unarmed&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Those who &#039;&#039;&#039;apply statuses (AoE, DoT, Suppressive Fire, etc.) or wish to utilize special rulings and mechanics are responsible for enforcing them&#039;&#039;&#039;. If you forget to apply your statuses or recall your rulings when using them on others, you lose that turn of said status. This rule applies to NPCs and PCs.&lt;br /&gt;
=== Engagement ===&lt;br /&gt;
Engagement occurs when one character enters another’s melee range. &#039;&#039;&#039;Engagement does not occur if the weapon being held is a ranged weapon&#039;&#039;&#039;. By default, this is one tile for all melee weapons except for spears, which have a two-tile range. &lt;br /&gt;
&lt;br /&gt;
While Engaged, characters cannot shoot with two-handed ranged weapons and cannot make any movement without triggering an AOO, including movement around an attacker.&lt;br /&gt;
=== Dashing ===&lt;br /&gt;
As an action, a character can double their movement speed from 6 to 12 tiles. Dashing is not the same as Disengaging, and can still provoke Engagement or Attacks of Opportunity.&lt;br /&gt;
=== Disarming ===&lt;br /&gt;
As an action, a character may choose to Disarm their target so long as they have at least one hand free. An attacker with only one hand free attempts their Disarm at disadvantage. An attacker with two hands free attempts their Disarm with no penalty. &lt;br /&gt;
&lt;br /&gt;
Disarms are made with a normal one-handed or unarmed attack roll. On success, the target is disarmed, dropping their weapon on the ground or in the hands of their attacker if the attacker has enough hands free (Attacker’s choice). The disarming character also deals damage equal to the weapon in their hand (1 damage if unarmed). &lt;br /&gt;
=== Swapping Weapons ===&lt;br /&gt;
Swapping weapons in combat is risky, but often necessary. Swapping weapons takes an action in most scenarios. However, if the weapon you are attempting to swap to is a one-handed weapon that is readily available, such as a holstered sidearm or a sheathed knife, you can choose to drop your currently equipped weapon on the ground to Quick Draw your one-handed weapon within the same turn. Your dropped weapon will require an action to pick up and equip once more. Please note that attachments, equipment, or other factors such as backstory or experience do not affect this rule.&lt;br /&gt;
=== Disengaging ===&lt;br /&gt;
As an action, a character may choose to Disengage from combat. This allows them to make any movement, barring any conditions which may prevent said movement, without proccing AOOs. &lt;br /&gt;
&lt;br /&gt;
If you are Engaged by two or more people, Disengage actions require a Resolve (DC 12) roll to be attempted. &lt;br /&gt;
=== Helping ===&lt;br /&gt;
As an action, any character can Help another character by rolling a Resolve (DC 10) roll. On success, the target gains advantage on their next roll, depending on the nature of the help. Helping must be done with a specific intention that is laid out clearly and concisely by the helping player on their turn. In other words, you cannot blanket Help on every possible roll that another character might do. It is recommended to have a brief OOC discussion about what your Helping action will look like with the target. &lt;br /&gt;
&lt;br /&gt;
Some examples of Help rolls might include:&lt;br /&gt;
* Attempting to restrain someone who is already grappled to give the attacking player an easier strike (Melee or Ranged Attack—While Grappling)&lt;br /&gt;
* Assisting a player in moving a heavy object (Strength)&lt;br /&gt;
* Providing an extra pair of hands during a medical emergency (First Aid)&lt;br /&gt;
* Providing an extra pair of hands while making dinner (Cooking)&lt;br /&gt;
&lt;br /&gt;
This list is, of course, non-exhaustive. Be creative!&lt;br /&gt;
=== Stealth &amp;amp; Ambushing ===&lt;br /&gt;
Ambushing a combatant gives several advantages. &lt;br /&gt;
&lt;br /&gt;
To attempt an Ambush, the attacker must succeed on a Hiding roll against the defender’s Perception roll. If the defender’s Perception roll beats the attacker’s Hiding roll, combat initiates as per usual, and no bonuses are obtained.&lt;br /&gt;
&lt;br /&gt;
Combatants who are ambushed are Surprised and cannot act for the first round of combat. Conversely, attackers who have Ambushed a combatant or combatants have advantage on attack rolls made during this Surprise round.&lt;br /&gt;
=== Overwatch ===&lt;br /&gt;
While using a weapon, players can use their turn to go on Overwatch. Overwatch allows a player to “delay” their attack until certain events happen. Players must define these events clearly and concisely on their turn.&lt;br /&gt;
&lt;br /&gt;
Examples of these events might include:&lt;br /&gt;
* When a specific character moves&lt;br /&gt;
* When any non-ally character enters a certain area&lt;br /&gt;
* Before or after another character acts&lt;br /&gt;
* If another character takes or deals damage&lt;br /&gt;
&lt;br /&gt;
When Overwatch is triggered, typical range and rolling rules still apply. If Overwatch is not used, Overwatch ends on the player’s following turn unless used again.&lt;br /&gt;
=== Throwables, Areas of Effect (AoE), and Conditions ===&lt;br /&gt;
Various weapons, such as throwables, may leave an Area of Affect (AOE). AOEs can apply various Conditions to a target. Some examples of these Conditions include:&lt;br /&gt;
* Burning (Fire)&lt;br /&gt;
* Burning (Acid)&lt;br /&gt;
* Electrocution&lt;br /&gt;
* Smoke&lt;br /&gt;
* Poison&lt;br /&gt;
&lt;br /&gt;
All of these Conditions apply the same mechanical effect. Regardless of the type, Conditions apply disadvantage to the afflicted character. &lt;br /&gt;
&lt;br /&gt;
As a rule of thumb, AOEs affect a 3x3 tile area and linger on the ground for 3 turns unless specified by the item or Storyteller. A character moving into an existing AOE requires passing a DC 10 Robustness check or else risk taking on the Condition imposed by it. &lt;br /&gt;
&lt;br /&gt;
An action can be spent by an afflicted character to remove a Condition unless attempted while within an AOE that applies it. Conditions can be stacked up to two times.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Name !! Damage On-Hit !! Area of Effect&lt;br /&gt;
|-&lt;br /&gt;
| Molotov || 2 || 3x3&lt;br /&gt;
|-&lt;br /&gt;
| Smoke Bomb || - || 5x5&lt;br /&gt;
|-&lt;br /&gt;
| Acid Attack || 3 || Single Target&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Throwables can be tossed onto a tile, affecting the adjacent 3x3 area, by making a Ranged Attack (DC 12) roll. On failure, the throwable is placed in a random location within 2 tiles of the intended location.&lt;br /&gt;
=== Attacks of Opportunity ===&lt;br /&gt;
Attacks of Opportunity (AOO) or Opportunity Attacks can occur when a target chooses to leave an attacker’s Engagement range. Please note that moving while staying within range of an attacker does not trigger an AOO. By default, all Human characters can use one AOO per round of combat, resetting on that character’s turn. Zombies, infected, and other creatures may have more attacks of opportunity, dependent on their individual skill sets and Storyteller preferences.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ranged weapons cannot utilize Attacks of Opportunity to deal a Ranged Attack&#039;&#039;&#039;. A person with a ranged weapon may make an &#039;&#039;&#039;Improvised Weapon&#039;&#039;&#039; roll to hit someone with a stock or “pistol whip,” but they cannot fire at a retreating opponent. &lt;br /&gt;
=== Going Prone ===&lt;br /&gt;
Prone refers to the act of lying on one’s back or stomach on the ground. In NWO, one can take a prone stance or be made to take a prone stance willingly or unwillingly. The Prone condition comes with several advantages and disadvantages:&lt;br /&gt;
* Prone characters can crawl to an adjacent tile while prone. If a character is prone and behind cover, they cannot make an attack unless their target is adjacent and not on the opposite side of said cover. &lt;br /&gt;
* Ranged attacks against prone characters are at a disadvantage.&lt;br /&gt;
** Ranged attacks have advantage against prone targets within CQC/point-blank range (1 tile). If Engaged, typical Engagement rules apply. &lt;br /&gt;
* Melee attacks against prone characters are at advantage.&lt;br /&gt;
* Prone characters may use their movement or action to stand from being prone.&lt;br /&gt;
* Prone characters cannot Engage characters within melee range.&lt;br /&gt;
=== Shoving ===&lt;br /&gt;
Players may also choose to shove another player to displace or knock them down.&lt;br /&gt;
&lt;br /&gt;
One player can shove another by engaging in a contested STR vs. STR or FIT (Defender’s choice) roll. Defenders can choose to automatically fail this roll if desired.&lt;br /&gt;
&lt;br /&gt;
Shoving players can knock the target Prone or knock them away—Attacker’s choice. &lt;br /&gt;
* If knocked Prone, Prone rules apply.&lt;br /&gt;
* If knocked away, the target gets shoved back [Attacker’s ½ STR] tiles in the direction of the Attacker’s choice.&lt;br /&gt;
=== Grappling ===&lt;br /&gt;
Characters may choose to Grapple a target to prevent escape or otherwise impose various disadvantages.&lt;br /&gt;
&lt;br /&gt;
One character may grapple another by engaging in a STR vs STR contest, so long as one of the Attacker’s hands is free. Attackers can drop a two-handed weapon same turn to attempt to Grapple a target, leaving their weapon on the ground. This weapon would need an action to be retrieved once more.&lt;br /&gt;
&lt;br /&gt;
A Grappled character has the following disadvantages imposed upon them until the Grapple is broken:&lt;br /&gt;
* Movement is reduced to 0. &lt;br /&gt;
** Grappled characters can rotate around the grappler, but cannot disengage or exit the tile adjacent. &lt;br /&gt;
* All attacks from the target are made at disadvantage&lt;br /&gt;
* Grappled characters do not provoke AOOs&lt;br /&gt;
&lt;br /&gt;
On the Grappled character’s turn, they may use their action to attempt to break free with another STR vs STR contest. On fail, the Grappled character remains Grappled. &lt;br /&gt;
&lt;br /&gt;
Characters grappling with a target can force that target to a prone state as an action with no additional contest. Both characters are considered Prone. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grappled targets are not restrained or disarmed&#039;&#039;&#039;. Grappled characters may still attack any target that would normally be capable of with the weapon they may (or may not) have equipped, including the grappler, at disadvantage. A successful attack on the grappler from the target forces a reroll of the contest. &lt;br /&gt;
&lt;br /&gt;
Grapplers may attack any character with a one-handed weapon at disadvantage. Disadvantage does not apply if their target is &#039;&#039;&#039;Cooperative&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Targets under duress, such as hostages, are considered &#039;&#039;&#039;Uncooperative&#039;&#039;&#039;, even if they are not attempting to actively fight their Grappler. Targets who are unconscious or otherwise incapacitated are considered &#039;&#039;&#039;Cooperative&#039;&#039;&#039;.&lt;br /&gt;
=== Human Shield ===&lt;br /&gt;
As an action or reaction, a Grappler may choose to use their target or themselves as a &#039;&#039;&#039;Human Shield&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When using a Human Shield, the Grappler gains a Full Cover bonus. When the Grappler is acting as a Human Shield, the target gains a Full Cover bonus. &lt;br /&gt;
&lt;br /&gt;
Any Attacks made against the &#039;&#039;&#039;Shielded&#039;&#039;&#039; character will require a roll from the Human Shield as well. Regardless of the Shielded character’s success or failure, if the Human Shield fails their Defense roll, they take the attack’s damage as well.&lt;br /&gt;
=== Focusing ===&lt;br /&gt;
Focusing for a turn or starting combat while Focused gives Advantage to a Ranged Attack made on the next turn. If you do not fire after aiming, this bonus is lost unless Aiming is maintained.&lt;br /&gt;
&lt;br /&gt;
Focusing can be done from cover. Becoming engaged or taking melee damage while Focused “breaks” your focus. If a ranged attack is made against a Focused character, that character must make a DC 12 Resolve roll to remain focused.&lt;br /&gt;
&lt;br /&gt;
Focus is maintained so long as the character does not move or otherwise have their Focus broken. Once broken, Focus needs to be done once more.&lt;br /&gt;
=== Reloading ===&lt;br /&gt;
Once your available ammunition (clip, magazine, etc.) is spent, players using a ranged weapon must make a roll to Reload (DC 10). On success, their Reload is instant, allowing them to take a normal action. On failure, a turn must be dedicated to Reloading their weapon. &lt;br /&gt;
=== Suppressive Fire ===&lt;br /&gt;
With a fully automatic weapon aimed at a player behind cover, attacking players can lay down Suppressive Fire. More than half of the weapon’s magazine must remain.&lt;br /&gt;
&lt;br /&gt;
Suppressive Fire requires a typical Ranged ATK vs. Ranged DEF roll. On the attacker’s success, typical damage applies. &lt;br /&gt;
&lt;br /&gt;
Regardless of success or failure, the attacking player burns their magazine of their automatic weapon to attempt to negate movement or action from the defending player. If a defender attempts to move or act in a way that would expose them to Suppressive Fire, they must make a Resolve (DC 12) roll. On success, they may take their action as normal. On failure, they are startled in place. A target under Suppressive Fire may take any other action or reaction that would not expose them to suppressive fire. &lt;br /&gt;
&lt;br /&gt;
Suppressive Fire ends at the beginning of the attacker’s next turn. At the end of Suppressive Fire, the attacking player must reload to continue using their currently equipped weapon.&lt;br /&gt;
= Armor =&lt;br /&gt;
Armor is applied as Light, Medium, or Heavy armor. Each type of armor has its own Armor Stacks and Drawbacks. &lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Armor Rules &lt;br /&gt;
|-&lt;br /&gt;
| Type || Stacks || Drawbacks&lt;br /&gt;
|-&lt;br /&gt;
| Light || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Medium || 2 || -1 Initiative, -1 Movement&lt;br /&gt;
|-&lt;br /&gt;
| Heavy || 3 || -2 Initiative, -2 Movement&lt;br /&gt;
|}&lt;br /&gt;
If an attack is taken while wearing armor, a stack is consumed to reduce the damage of that attack to 1. Critical attacks deal 2 stacks of damage to armor and deal full damage if only one Armor Stack is remaining.&lt;br /&gt;
= Ranged Combat =&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
| Ranged Defense || -&lt;br /&gt;
|-&lt;br /&gt;
| Ranged Attack || Aiming/2&lt;br /&gt;
|}&lt;br /&gt;
Several aspects of the mechanical environment affect ranged rolls, including the lighting of an area, whether or not a weapon is scoped, and more. Please note that Sniping a character from a distance, while an option, is still subject to appropriate combat rules, CK rules, and ticketing. The range of a ranged weapon is dictated by in-game line-of-sight (LoS). Consider the following before making an attack roll:&lt;br /&gt;
* Whether the target is ‘visible’ &lt;br /&gt;
* Whether there is a clear line of fire from attacker to defender&lt;br /&gt;
* Whether any allies are engaged in any actions that may put them at risk of being hit (i.e. grappling)&lt;br /&gt;
The above factors determine whether a target can be hit. In the special case of the flamethrower, its range is 15 in-game tiles.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Ranged Weapons&lt;br /&gt;
! Weapon Type !! Damage !! Crit Requirement !! Crit Dmg&lt;br /&gt;
|-&lt;br /&gt;
| Assault Weapons || 4 || Double 5/Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| M60/LMG&#039;s|| 5 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Rifles &amp;amp; Shotguns || 4 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Pistols || 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Flamethrowers &amp;lt;ref&amp;gt;Flamethrower Crit Rule: If a character holding a Flamethrower is hit by a Critical Hit, there&#039;s a 50% chance (1-2-3 on a d6 roll) that the fuel canister explodes. Dealing 2 Damage to the wielder.&amp;lt;/ref&amp;gt; &amp;lt;ref&amp;gt;Flamethrower Cover Rules: Engaged Condition and a 50% body coverage behind a tile don&#039;t offer Cover from a Flamethrower attack. If a Flamethrower attack targets a character engaged with other characters, all of them will be hit by the attack and roll Ranged Defence. Only a coverage of at least 80% behind a Tile grants the Cover modifier from a Flamethrower Attack. (ex: Only a limb/head visible)&amp;lt;/ref&amp;gt; || 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Crossbows || 4 || Double 6 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Melee Combat =&lt;br /&gt;
Melee combat occurs with contested rolls. The success of a melee attack roll is dependent on the following values:&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Melee Examples&lt;br /&gt;
! Melee Attack !! ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|-&lt;br /&gt;
| Melee Defense|| ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|}&lt;br /&gt;
Each Melee weapon has its own damage and crit requirements.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Melee Examples&lt;br /&gt;
! Weapon Type || Damage || Crit Requirement || Crit Damage&lt;br /&gt;
|-&lt;br /&gt;
| Two-Handed|| 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| One-Handed &amp;lt;ref&amp;gt;One-Handed Double-Tap: Characters utilizing one-handed weapons can forgo their movement to make a second attack at disadvantage. This attack cannot crit.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Spears &amp;lt;ref&amp;gt;Spear Range Rule: A character holding a spear engages hostile characters within 2 Tiles instead of 1.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 3&lt;br /&gt;
|-&lt;br /&gt;
| Machete/Handaxe/Kukri|| 3 || Double 6 || 5&lt;br /&gt;
|-&lt;br /&gt;
| Bare Hands, Improvised, Weapon Stocks|| 1 || - || -&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=424</id>
		<title>Template:Combat Rolls</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=424"/>
		<updated>2025-11-10T19:36:50Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* Melee Combat */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Combat =&lt;br /&gt;
Here at NWO, we pride ourselves on the sense of danger and risk ever-present around our characters and plots. Every combat encounter can be a heroic victory or a crushing defeat, and very often brings with it lethal consequences. Every fight should be entered with the understanding that this...could be it.&lt;br /&gt;
&lt;br /&gt;
While all players are expected to adhere to these rules when engaging in combat between characters on their own, we try to be more open and rewarding towards creativity when interacting with our Staff team in events and encounters. While the rules listed below are the usual standard used by our Storytellers and players during these Staff-hosted scenes, our NPC creations and environmental hazards sometimes require us to make adjustments to the mechanics of a scene to properly simulate the threats or conditions our players are facing. &lt;br /&gt;
&lt;br /&gt;
Not to worry, though, as this same flexibility extends to you as a player! If you want to perform actions not easily expressed through these rules, talk to the Staff running the scene you are within in the OoC chat tab, and they will very often work with you to try and actualize your imagined plan. This is no guarantee that it will work, but player creativity is something we always attempt to reward.&lt;br /&gt;
=== Actions &amp;amp; Movement ===&lt;br /&gt;
A combat round is typically comprised of an &#039;&#039;&#039;Action&#039;&#039;&#039; and &#039;&#039;&#039;Movement&#039;&#039;&#039;. &#039;&#039;&#039;Reactions&#039;&#039;&#039; can sometimes be taken in special circumstances, such as &#039;&#039;&#039;Attacks of Opportunity&#039;&#039;&#039; or while &#039;&#039;&#039;Grappling&#039;&#039;&#039; someone. Exceptions will be noted in the relevant sections.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Movement&#039;&#039;&#039; is self-explanatory—It is the way a character “moves” around a space. Characters must toggle their movement range on their Dice Panel and may only move in one path to a point within their movement range. Movement imposes a -2 shooting penalty, regardless of the order of one’s Action or Movement.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Actions&#039;&#039;&#039; include a variety of things such as &#039;&#039;&#039;Attacking&#039;&#039;&#039;, &#039;&#039;&#039;Grappling&#039;&#039;&#039;, &#039;&#039;&#039;Shoving&#039;&#039;&#039;, &#039;&#039;&#039;Throwing&#039;&#039;&#039;, &#039;&#039;&#039;Helping&#039;&#039;&#039;, or virtually anything that is not movement. &lt;br /&gt;
&lt;br /&gt;
Your combat roll in a scenario is determined by the weapon a character has equipped, but will generally be &#039;&#039;&#039;Melee&#039;&#039;&#039;, &#039;&#039;&#039;Ranged&#039;&#039;&#039;, or &#039;&#039;&#039;Unarmed&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Those who &#039;&#039;&#039;apply statuses (AoE, DoT, Suppressive Fire, etc.) or wish to utilize special rulings and mechanics are responsible for enforcing them&#039;&#039;&#039;. If you forget to apply your statuses or recall your rulings when using them on others, you lose that turn of said status. This rule applies to NPCs and PCs.&lt;br /&gt;
=== Engagement ===&lt;br /&gt;
Engagement occurs when one character enters another’s melee range. &#039;&#039;&#039;Engagement does not occur if the weapon being held is a ranged weapon&#039;&#039;&#039;. By default, this is one tile for all melee weapons except for spears, which have a two-tile range. &lt;br /&gt;
&lt;br /&gt;
While Engaged, characters cannot shoot with two-handed ranged weapons and cannot make any movement without triggering an AOO, including movement around an attacker.&lt;br /&gt;
=== Dashing ===&lt;br /&gt;
As an action, a character can double their movement speed from 6 to 12 tiles. Dashing is not the same as Disengaging, and can still provoke Engagement or Attacks of Opportunity.&lt;br /&gt;
=== Disarming ===&lt;br /&gt;
As an action, a character may choose to Disarm their target so long as they have at least one hand free. An attacker with only one hand free attempts their Disarm at disadvantage. An attacker with two hands free attempts their Disarm with no penalty. &lt;br /&gt;
&lt;br /&gt;
Disarms are made with a normal one-handed or unarmed attack roll. On success, the target is disarmed, dropping their weapon on the ground or in the hands of their attacker if the attacker has enough hands free (Attacker’s choice). The disarming character also deals damage equal to the weapon in their hand (1 damage if unarmed). &lt;br /&gt;
=== Swapping Weapons ===&lt;br /&gt;
Swapping weapons in combat is risky, but often necessary. Swapping weapons takes an action in most scenarios. However, if the weapon you are attempting to swap to is a one-handed weapon that is readily available, such as a holstered sidearm or a sheathed knife, you can choose to drop your currently equipped weapon on the ground to Quick Draw your one-handed weapon within the same turn. Your dropped weapon will require an action to pick up and equip once more. Please note that attachments, equipment, or other factors such as backstory or experience do not affect this rule.&lt;br /&gt;
=== Disengaging ===&lt;br /&gt;
As an action, a character may choose to Disengage from combat. This allows them to make any movement, barring any conditions which may prevent said movement, without proccing AOOs. &lt;br /&gt;
&lt;br /&gt;
If you are Engaged by two or more people, Disengage actions require a Resolve (DC 12) roll to be attempted. &lt;br /&gt;
=== Helping ===&lt;br /&gt;
As an action, any character can Help another character by rolling a Resolve (DC 10) roll. On success, the target gains advantage on their next roll, depending on the nature of the help. Helping must be done with a specific intention that is laid out clearly and concisely by the helping player on their turn. In other words, you cannot blanket Help on every possible roll that another character might do. It is recommended to have a brief OOC discussion about what your Helping action will look like with the target. &lt;br /&gt;
&lt;br /&gt;
Some examples of Help rolls might include:&lt;br /&gt;
* Attempting to restrain someone who is already grappled to give the attacking player an easier strike (Melee or Ranged Attack—While Grappling)&lt;br /&gt;
* Assisting a player in moving a heavy object (Strength)&lt;br /&gt;
* Providing an extra pair of hands during a medical emergency (First Aid)&lt;br /&gt;
* Providing an extra pair of hands while making dinner (Cooking)&lt;br /&gt;
&lt;br /&gt;
This list is, of course, non-exhaustive. Be creative!&lt;br /&gt;
=== Stealth &amp;amp; Ambushing ===&lt;br /&gt;
Ambushing a combatant gives several advantages. &lt;br /&gt;
&lt;br /&gt;
To attempt an Ambush, the attacker must succeed on a Hiding roll against the defender’s Perception roll. If the defender’s Perception roll beats the attacker’s Hiding roll, combat initiates as per usual, and no bonuses are obtained.&lt;br /&gt;
&lt;br /&gt;
Combatants who are ambushed are Surprised and cannot act for the first round of combat. Conversely, attackers who have Ambushed a combatant or combatants have advantage on attack rolls made during this Surprise round.&lt;br /&gt;
=== Overwatch ===&lt;br /&gt;
While using a weapon, players can use their turn to go on Overwatch. Overwatch allows a player to “delay” their attack until certain events happen. Players must define these events clearly and concisely on their turn.&lt;br /&gt;
&lt;br /&gt;
Examples of these events might include:&lt;br /&gt;
* When a specific character moves&lt;br /&gt;
* When any non-ally character enters a certain area&lt;br /&gt;
* Before or after another character acts&lt;br /&gt;
* If another character takes or deals damage&lt;br /&gt;
&lt;br /&gt;
When Overwatch is triggered, typical range and rolling rules still apply. If Overwatch is not used, Overwatch ends on the player’s following turn unless used again.&lt;br /&gt;
=== Throwables, Areas of Effect (AoE), and Conditions ===&lt;br /&gt;
Various weapons, such as throwables, may leave an Area of Affect (AOE). AOEs can apply various Conditions to a target. Some examples of these Conditions include:&lt;br /&gt;
* Burning (Fire)&lt;br /&gt;
* Burning (Acid)&lt;br /&gt;
* Electrocution&lt;br /&gt;
* Smoke&lt;br /&gt;
* Poison&lt;br /&gt;
&lt;br /&gt;
All of these Conditions apply the same mechanical effect. Regardless of the type, Conditions apply disadvantage to the afflicted character. &lt;br /&gt;
&lt;br /&gt;
As a rule of thumb, AOEs affect a 3x3 tile area and linger on the ground for 3 turns unless specified by the item or Storyteller. A character moving into an existing AOE requires passing a DC 10 Robustness check or else risk taking on the Condition imposed by it. &lt;br /&gt;
&lt;br /&gt;
An action can be spent by an afflicted character to remove a Condition unless attempted while within an AOE that applies it. Conditions can be stacked up to two times.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Name !! Damage On-Hit !! Area of Effect&lt;br /&gt;
|-&lt;br /&gt;
| Molotov || 2 || 3x3&lt;br /&gt;
|-&lt;br /&gt;
| Smoke Bomb || - || 5x5&lt;br /&gt;
|-&lt;br /&gt;
| Acid Attack || 3 || Single Target&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Throwables can be tossed onto a tile, affecting the adjacent 3x3 area, by making a Ranged Attack (DC 12) roll. On failure, the throwable is placed in a random location within 2 tiles of the intended location.&lt;br /&gt;
=== Attacks of Opportunity ===&lt;br /&gt;
Attacks of Opportunity (AOO) or Opportunity Attacks can occur when a target chooses to leave an attacker’s Engagement range. Please note that moving while staying within range of an attacker does not trigger an AOO. By default, all Human characters can use one AOO per round of combat, resetting on that character’s turn. Zombies, infected, and other creatures may have more attacks of opportunity, dependent on their individual skill sets and Storyteller preferences.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ranged weapons cannot utilize Attacks of Opportunity to deal a Ranged Attack&#039;&#039;&#039;. A person with a ranged weapon may make an &#039;&#039;&#039;Improvised Weapon&#039;&#039;&#039; roll to hit someone with a stock or “pistol whip,” but they cannot fire at a retreating opponent. &lt;br /&gt;
=== Going Prone ===&lt;br /&gt;
Prone refers to the act of lying on one’s back or stomach on the ground. In NWO, one can take a prone stance or be made to take a prone stance willingly or unwillingly. The Prone condition comes with several advantages and disadvantages:&lt;br /&gt;
* Prone characters can crawl to an adjacent tile while prone. If a character is prone and behind cover, they cannot make an attack unless their target is adjacent and not on the opposite side of said cover. &lt;br /&gt;
* Ranged attacks against prone characters are at a disadvantage.&lt;br /&gt;
** Ranged attacks have advantage against prone targets within CQC/point-blank range (1 tile). If Engaged, typical Engagement rules apply. &lt;br /&gt;
* Melee attacks against prone characters are at advantage.&lt;br /&gt;
* Prone characters may use their movement or action to stand from being prone.&lt;br /&gt;
* Prone characters cannot Engage characters within melee range.&lt;br /&gt;
=== Shoving ===&lt;br /&gt;
Players may also choose to shove another player to displace or knock them down.&lt;br /&gt;
&lt;br /&gt;
One player can shove another by engaging in a contested STR vs. STR or FIT (Defender’s choice) roll. Defenders can choose to automatically fail this roll if desired.&lt;br /&gt;
&lt;br /&gt;
Shoving players can knock the target Prone or knock them away—Attacker’s choice. &lt;br /&gt;
* If knocked Prone, Prone rules apply.&lt;br /&gt;
* If knocked away, the target gets shoved back [Attacker’s ½ STR] tiles in the direction of the Attacker’s choice.&lt;br /&gt;
=== Grappling ===&lt;br /&gt;
Characters may choose to Grapple a target to prevent escape or otherwise impose various disadvantages.&lt;br /&gt;
&lt;br /&gt;
One character may grapple another by engaging in a STR vs STR contest, so long as one of the Attacker’s hands is free. Attackers can drop a two-handed weapon same turn to attempt to Grapple a target, leaving their weapon on the ground. This weapon would need an action to be retrieved once more.&lt;br /&gt;
&lt;br /&gt;
A Grappled character has the following disadvantages imposed upon them until the Grapple is broken:&lt;br /&gt;
* Movement is reduced to 0. &lt;br /&gt;
** Grappled characters can rotate around the grappler, but cannot disengage or exit the tile adjacent. &lt;br /&gt;
* All attacks from the target are made at disadvantage&lt;br /&gt;
* Grappled characters do not provoke AOOs&lt;br /&gt;
&lt;br /&gt;
On the Grappled character’s turn, they may use their action to attempt to break free with another STR vs STR contest. On fail, the Grappled character remains Grappled. &lt;br /&gt;
&lt;br /&gt;
Characters grappling with a target can force that target to a prone state as an action with no additional contest. Both characters are considered Prone. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grappled targets are not restrained or disarmed&#039;&#039;&#039;. Grappled characters may still attack any target that would normally be capable of with the weapon they may (or may not) have equipped, including the grappler, at disadvantage. A successful attack on the grappler from the target forces a reroll of the contest. &lt;br /&gt;
&lt;br /&gt;
Grapplers may attack any character with a one-handed weapon at disadvantage. Disadvantage does not apply if their target is &#039;&#039;&#039;Cooperative&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Targets under duress, such as hostages, are considered &#039;&#039;&#039;Uncooperative&#039;&#039;&#039;, even if they are not attempting to actively fight their Grappler. Targets who are unconscious or otherwise incapacitated are considered &#039;&#039;&#039;Cooperative&#039;&#039;&#039;.&lt;br /&gt;
=== Human Shield ===&lt;br /&gt;
As an action or reaction, a Grappler may choose to use their target or themselves as a &#039;&#039;&#039;Human Shield&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When using a Human Shield, the Grappler gains a Full Cover bonus. When the Grappler is acting as a Human Shield, the target gains a Full Cover bonus. &lt;br /&gt;
&lt;br /&gt;
Any Attacks made against the &#039;&#039;&#039;Shielded&#039;&#039;&#039; character will require a roll from the Human Shield as well. Regardless of the Shielded character’s success or failure, if the Human Shield fails their Defense roll, they take the attack’s damage as well.&lt;br /&gt;
=== Focusing ===&lt;br /&gt;
Focusing for a turn or starting combat while Focused gives Advantage to a Ranged Attack made on the next turn. If you do not fire after aiming, this bonus is lost unless Aiming is maintained.&lt;br /&gt;
&lt;br /&gt;
Focusing can be done from cover. Becoming engaged or taking melee damage while Focused “breaks” your focus. If a ranged attack is made against a Focused character, that character must make a DC 12 Resolve roll to remain focused.&lt;br /&gt;
&lt;br /&gt;
Focus is maintained so long as the character does not move or otherwise have their Focus broken. Once broken, Focus needs to be done once more.&lt;br /&gt;
=== Reloading ===&lt;br /&gt;
Once your available ammunition (clip, magazine, etc.) is spent, players using a ranged weapon must make a roll to Reload (DC 10). On success, their Reload is instant, allowing them to take a normal action. On failure, a turn must be dedicated to Reloading their weapon. &lt;br /&gt;
=== Suppressive Fire ===&lt;br /&gt;
With a fully automatic weapon aimed at a player behind cover, attacking players can lay down Suppressive Fire. More than half of the weapon’s magazine must remain.&lt;br /&gt;
&lt;br /&gt;
Suppressive Fire requires a typical Ranged ATK vs. Ranged DEF roll. On the attacker’s success, typical damage applies. &lt;br /&gt;
&lt;br /&gt;
Regardless of success or failure, the attacking player burns their magazine of their automatic weapon to attempt to negate movement or action from the defending player. If a defender attempts to move or act in a way that would expose them to Suppressive Fire, they must make a Resolve (DC 12) roll. On success, they may take their action as normal. On failure, they are startled in place. A target under Suppressive Fire may take any other action or reaction that would not expose them to suppressive fire. &lt;br /&gt;
&lt;br /&gt;
Suppressive Fire ends at the beginning of the attacker’s next turn. At the end of Suppressive Fire, the attacking player must reload to continue using their currently equipped weapon.&lt;br /&gt;
= Armor =&lt;br /&gt;
Armor is applied as Light, Medium, or Heavy armor. Each type of armor has its own Armor Stacks and Drawbacks. &lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Armor Rules &lt;br /&gt;
|-&lt;br /&gt;
| Type || Stacks || Drawbacks&lt;br /&gt;
|-&lt;br /&gt;
| Light || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Medium || 2 || -1 Initiative, -1 Movement&lt;br /&gt;
|-&lt;br /&gt;
| Heavy || 3 || -2 Initiative, -2 Movement&lt;br /&gt;
|}&lt;br /&gt;
If an attack is taken while wearing armor, a stack is consumed to reduce the damage of that attack to 1. Critical attacks deal 2 stacks of damage to armor and deal full damage if only one Armor Stack is remaining.&lt;br /&gt;
= Ranged Combat =&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
| Ranged Defense || -&lt;br /&gt;
|-&lt;br /&gt;
| Ranged Attack || Aiming/2&lt;br /&gt;
|}&lt;br /&gt;
Several aspects of the mechanical environment affect ranged rolls, including the lighting of an area, whether or not a weapon is scoped, and more. Please note that Sniping a character from a distance, while an option, is still subject to appropriate combat rules, CK rules, and ticketing. The range of a ranged weapon is dictated by in-game line-of-sight (LoS). Consider the following before making an attack roll:&lt;br /&gt;
* Whether the target is ‘visible’ &lt;br /&gt;
* Whether there is a clear line of fire from attacker to defender&lt;br /&gt;
* Whether any allies are engaged in any actions that may put them at risk of being hit (i.e. grappling)&lt;br /&gt;
The above factors determine whether a target can be hit. In the special case of the flamethrower, its range is 15 in-game tiles.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Ranged Weapons&lt;br /&gt;
! Weapon Type !! Damage !! Crit Requirement !! Crit Dmg&lt;br /&gt;
|-&lt;br /&gt;
| Assault Weapons || 4 || Double 5/Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| M60/LMG&#039;s|| 5 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Rifles &amp;amp; Shotguns || 4 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Pistols || 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Flamethrowers &amp;lt;ref&amp;gt;Flamethrower Crit Rule: If a character holding a Flamethrower is hit by a Critical Hit, there&#039;s a 50% chance (1-2-3 on a d6 roll) that the fuel canister explodes. Dealing 2 Damage to the wielder.&amp;lt;/ref&amp;gt; &amp;lt;ref&amp;gt;Flamethrower Cover Rules: Engaged Condition and a 50% body coverage behind a tile don&#039;t offer Cover from a Flamethrower attack. If a Flamethrower attack targets a character engaged with other characters, all of them will be hit by the attack and roll Ranged Defence. Only a coverage of at least 80% behind a Tile grants the Cover modifier from a Flamethrower Attack. (ex: Only a limb/head visible)&amp;lt;/ref&amp;gt; || 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Crossbows || 4 || Double 6 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Melee Combat =&lt;br /&gt;
Melee combat occurs with contested rolls. The success of a melee attack roll is dependent on the following values:&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Melee Examples&lt;br /&gt;
! Melee Attack !! ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|-&lt;br /&gt;
| Melee Defense|| ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|}&lt;br /&gt;
Each Melee weapon has its own damage and crit requirements.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Melee Examples&lt;br /&gt;
! Weapon Type || Damage || Crit Requirement || Crit Damage&lt;br /&gt;
|-&lt;br /&gt;
| Two-Handed|| 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| One-Handed &amp;lt;ref&amp;gt;One-Handed Double-Tap: Characters utilizing one-handed weapons can forgo their movement to make a second attack at disadvantage. This attack cannot crit.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Spears &amp;lt;ref&amp;gt;Spear Range Rule: A character holding a spear engages hostile characters within 2 Tiles instead of 1.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 3&lt;br /&gt;
|-&lt;br /&gt;
| Bare Hands, Improvised, Weapon Stocks|| 1 || - || -&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=423</id>
		<title>Template:Combat Rolls</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=423"/>
		<updated>2025-11-10T19:20:15Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* Melee Combat */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Combat =&lt;br /&gt;
Here at NWO, we pride ourselves on the sense of danger and risk ever-present around our characters and plots. Every combat encounter can be a heroic victory or a crushing defeat, and very often brings with it lethal consequences. Every fight should be entered with the understanding that this...could be it.&lt;br /&gt;
&lt;br /&gt;
While all players are expected to adhere to these rules when engaging in combat between characters on their own, we try to be more open and rewarding towards creativity when interacting with our Staff team in events and encounters. While the rules listed below are the usual standard used by our Storytellers and players during these Staff-hosted scenes, our NPC creations and environmental hazards sometimes require us to make adjustments to the mechanics of a scene to properly simulate the threats or conditions our players are facing. &lt;br /&gt;
&lt;br /&gt;
Not to worry, though, as this same flexibility extends to you as a player! If you want to perform actions not easily expressed through these rules, talk to the Staff running the scene you are within in the OoC chat tab, and they will very often work with you to try and actualize your imagined plan. This is no guarantee that it will work, but player creativity is something we always attempt to reward.&lt;br /&gt;
=== Actions &amp;amp; Movement ===&lt;br /&gt;
A combat round is typically comprised of an &#039;&#039;&#039;Action&#039;&#039;&#039; and &#039;&#039;&#039;Movement&#039;&#039;&#039;. &#039;&#039;&#039;Reactions&#039;&#039;&#039; can sometimes be taken in special circumstances, such as &#039;&#039;&#039;Attacks of Opportunity&#039;&#039;&#039; or while &#039;&#039;&#039;Grappling&#039;&#039;&#039; someone. Exceptions will be noted in the relevant sections.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Movement&#039;&#039;&#039; is self-explanatory—It is the way a character “moves” around a space. Characters must toggle their movement range on their Dice Panel and may only move in one path to a point within their movement range. Movement imposes a -2 shooting penalty, regardless of the order of one’s Action or Movement.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Actions&#039;&#039;&#039; include a variety of things such as &#039;&#039;&#039;Attacking&#039;&#039;&#039;, &#039;&#039;&#039;Grappling&#039;&#039;&#039;, &#039;&#039;&#039;Shoving&#039;&#039;&#039;, &#039;&#039;&#039;Throwing&#039;&#039;&#039;, &#039;&#039;&#039;Helping&#039;&#039;&#039;, or virtually anything that is not movement. &lt;br /&gt;
&lt;br /&gt;
Your combat roll in a scenario is determined by the weapon a character has equipped, but will generally be &#039;&#039;&#039;Melee&#039;&#039;&#039;, &#039;&#039;&#039;Ranged&#039;&#039;&#039;, or &#039;&#039;&#039;Unarmed&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Those who &#039;&#039;&#039;apply statuses (AoE, DoT, Suppressive Fire, etc.) or wish to utilize special rulings and mechanics are responsible for enforcing them&#039;&#039;&#039;. If you forget to apply your statuses or recall your rulings when using them on others, you lose that turn of said status. This rule applies to NPCs and PCs.&lt;br /&gt;
=== Engagement ===&lt;br /&gt;
Engagement occurs when one character enters another’s melee range. &#039;&#039;&#039;Engagement does not occur if the weapon being held is a ranged weapon&#039;&#039;&#039;. By default, this is one tile for all melee weapons except for spears, which have a two-tile range. &lt;br /&gt;
&lt;br /&gt;
While Engaged, characters cannot shoot with two-handed ranged weapons and cannot make any movement without triggering an AOO, including movement around an attacker.&lt;br /&gt;
=== Dashing ===&lt;br /&gt;
As an action, a character can double their movement speed from 6 to 12 tiles. Dashing is not the same as Disengaging, and can still provoke Engagement or Attacks of Opportunity.&lt;br /&gt;
=== Disarming ===&lt;br /&gt;
As an action, a character may choose to Disarm their target so long as they have at least one hand free. An attacker with only one hand free attempts their Disarm at disadvantage. An attacker with two hands free attempts their Disarm with no penalty. &lt;br /&gt;
&lt;br /&gt;
Disarms are made with a normal one-handed or unarmed attack roll. On success, the target is disarmed, dropping their weapon on the ground or in the hands of their attacker if the attacker has enough hands free (Attacker’s choice). The disarming character also deals damage equal to the weapon in their hand (1 damage if unarmed). &lt;br /&gt;
=== Swapping Weapons ===&lt;br /&gt;
Swapping weapons in combat is risky, but often necessary. Swapping weapons takes an action in most scenarios. However, if the weapon you are attempting to swap to is a one-handed weapon that is readily available, such as a holstered sidearm or a sheathed knife, you can choose to drop your currently equipped weapon on the ground to Quick Draw your one-handed weapon within the same turn. Your dropped weapon will require an action to pick up and equip once more. Please note that attachments, equipment, or other factors such as backstory or experience do not affect this rule.&lt;br /&gt;
=== Disengaging ===&lt;br /&gt;
As an action, a character may choose to Disengage from combat. This allows them to make any movement, barring any conditions which may prevent said movement, without proccing AOOs. &lt;br /&gt;
&lt;br /&gt;
If you are Engaged by two or more people, Disengage actions require a Resolve (DC 12) roll to be attempted. &lt;br /&gt;
=== Helping ===&lt;br /&gt;
As an action, any character can Help another character by rolling a Resolve (DC 10) roll. On success, the target gains advantage on their next roll, depending on the nature of the help. Helping must be done with a specific intention that is laid out clearly and concisely by the helping player on their turn. In other words, you cannot blanket Help on every possible roll that another character might do. It is recommended to have a brief OOC discussion about what your Helping action will look like with the target. &lt;br /&gt;
&lt;br /&gt;
Some examples of Help rolls might include:&lt;br /&gt;
* Attempting to restrain someone who is already grappled to give the attacking player an easier strike (Melee or Ranged Attack—While Grappling)&lt;br /&gt;
* Assisting a player in moving a heavy object (Strength)&lt;br /&gt;
* Providing an extra pair of hands during a medical emergency (First Aid)&lt;br /&gt;
* Providing an extra pair of hands while making dinner (Cooking)&lt;br /&gt;
&lt;br /&gt;
This list is, of course, non-exhaustive. Be creative!&lt;br /&gt;
=== Stealth &amp;amp; Ambushing ===&lt;br /&gt;
Ambushing a combatant gives several advantages. &lt;br /&gt;
&lt;br /&gt;
To attempt an Ambush, the attacker must succeed on a Hiding roll against the defender’s Perception roll. If the defender’s Perception roll beats the attacker’s Hiding roll, combat initiates as per usual, and no bonuses are obtained.&lt;br /&gt;
&lt;br /&gt;
Combatants who are ambushed are Surprised and cannot act for the first round of combat. Conversely, attackers who have Ambushed a combatant or combatants have advantage on attack rolls made during this Surprise round.&lt;br /&gt;
=== Overwatch ===&lt;br /&gt;
While using a weapon, players can use their turn to go on Overwatch. Overwatch allows a player to “delay” their attack until certain events happen. Players must define these events clearly and concisely on their turn.&lt;br /&gt;
&lt;br /&gt;
Examples of these events might include:&lt;br /&gt;
* When a specific character moves&lt;br /&gt;
* When any non-ally character enters a certain area&lt;br /&gt;
* Before or after another character acts&lt;br /&gt;
* If another character takes or deals damage&lt;br /&gt;
&lt;br /&gt;
When Overwatch is triggered, typical range and rolling rules still apply. If Overwatch is not used, Overwatch ends on the player’s following turn unless used again.&lt;br /&gt;
=== Throwables, Areas of Effect (AoE), and Conditions ===&lt;br /&gt;
Various weapons, such as throwables, may leave an Area of Affect (AOE). AOEs can apply various Conditions to a target. Some examples of these Conditions include:&lt;br /&gt;
* Burning (Fire)&lt;br /&gt;
* Burning (Acid)&lt;br /&gt;
* Electrocution&lt;br /&gt;
* Smoke&lt;br /&gt;
* Poison&lt;br /&gt;
&lt;br /&gt;
All of these Conditions apply the same mechanical effect. Regardless of the type, Conditions apply disadvantage to the afflicted character. &lt;br /&gt;
&lt;br /&gt;
As a rule of thumb, AOEs affect a 3x3 tile area and linger on the ground for 3 turns unless specified by the item or Storyteller. A character moving into an existing AOE requires passing a DC 10 Robustness check or else risk taking on the Condition imposed by it. &lt;br /&gt;
&lt;br /&gt;
An action can be spent by an afflicted character to remove a Condition unless attempted while within an AOE that applies it. Conditions can be stacked up to two times.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Name !! Damage On-Hit !! Area of Effect&lt;br /&gt;
|-&lt;br /&gt;
| Molotov || 2 || 3x3&lt;br /&gt;
|-&lt;br /&gt;
| Smoke Bomb || - || 5x5&lt;br /&gt;
|-&lt;br /&gt;
| Acid Attack || 3 || Single Target&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Throwables can be tossed onto a tile, affecting the adjacent 3x3 area, by making a Ranged Attack (DC 12) roll. On failure, the throwable is placed in a random location within 2 tiles of the intended location.&lt;br /&gt;
=== Attacks of Opportunity ===&lt;br /&gt;
Attacks of Opportunity (AOO) or Opportunity Attacks can occur when a target chooses to leave an attacker’s Engagement range. Please note that moving while staying within range of an attacker does not trigger an AOO. By default, all Human characters can use one AOO per round of combat, resetting on that character’s turn. Zombies, infected, and other creatures may have more attacks of opportunity, dependent on their individual skill sets and Storyteller preferences.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ranged weapons cannot utilize Attacks of Opportunity to deal a Ranged Attack&#039;&#039;&#039;. A person with a ranged weapon may make an &#039;&#039;&#039;Improvised Weapon&#039;&#039;&#039; roll to hit someone with a stock or “pistol whip,” but they cannot fire at a retreating opponent. &lt;br /&gt;
=== Going Prone ===&lt;br /&gt;
Prone refers to the act of lying on one’s back or stomach on the ground. In NWO, one can take a prone stance or be made to take a prone stance willingly or unwillingly. The Prone condition comes with several advantages and disadvantages:&lt;br /&gt;
* Prone characters can crawl to an adjacent tile while prone. If a character is prone and behind cover, they cannot make an attack unless their target is adjacent and not on the opposite side of said cover. &lt;br /&gt;
* Ranged attacks against prone characters are at a disadvantage.&lt;br /&gt;
** Ranged attacks have advantage against prone targets within CQC/point-blank range (1 tile). If Engaged, typical Engagement rules apply. &lt;br /&gt;
* Melee attacks against prone characters are at advantage.&lt;br /&gt;
* Prone characters may use their movement or action to stand from being prone.&lt;br /&gt;
* Prone characters cannot Engage characters within melee range.&lt;br /&gt;
=== Shoving ===&lt;br /&gt;
Players may also choose to shove another player to displace or knock them down.&lt;br /&gt;
&lt;br /&gt;
One player can shove another by engaging in a contested STR vs. STR or FIT (Defender’s choice) roll. Defenders can choose to automatically fail this roll if desired.&lt;br /&gt;
&lt;br /&gt;
Shoving players can knock the target Prone or knock them away—Attacker’s choice. &lt;br /&gt;
* If knocked Prone, Prone rules apply.&lt;br /&gt;
* If knocked away, the target gets shoved back [Attacker’s ½ STR] tiles in the direction of the Attacker’s choice.&lt;br /&gt;
=== Grappling ===&lt;br /&gt;
Characters may choose to Grapple a target to prevent escape or otherwise impose various disadvantages.&lt;br /&gt;
&lt;br /&gt;
One character may grapple another by engaging in a STR vs STR contest, so long as one of the Attacker’s hands is free. Attackers can drop a two-handed weapon same turn to attempt to Grapple a target, leaving their weapon on the ground. This weapon would need an action to be retrieved once more.&lt;br /&gt;
&lt;br /&gt;
A Grappled character has the following disadvantages imposed upon them until the Grapple is broken:&lt;br /&gt;
* Movement is reduced to 0. &lt;br /&gt;
** Grappled characters can rotate around the grappler, but cannot disengage or exit the tile adjacent. &lt;br /&gt;
* All attacks from the target are made at disadvantage&lt;br /&gt;
* Grappled characters do not provoke AOOs&lt;br /&gt;
&lt;br /&gt;
On the Grappled character’s turn, they may use their action to attempt to break free with another STR vs STR contest. On fail, the Grappled character remains Grappled. &lt;br /&gt;
&lt;br /&gt;
Characters grappling with a target can force that target to a prone state as an action with no additional contest. Both characters are considered Prone. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grappled targets are not restrained or disarmed&#039;&#039;&#039;. Grappled characters may still attack any target that would normally be capable of with the weapon they may (or may not) have equipped, including the grappler, at disadvantage. A successful attack on the grappler from the target forces a reroll of the contest. &lt;br /&gt;
&lt;br /&gt;
Grapplers may attack any character with a one-handed weapon at disadvantage. Disadvantage does not apply if their target is &#039;&#039;&#039;Cooperative&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Targets under duress, such as hostages, are considered &#039;&#039;&#039;Uncooperative&#039;&#039;&#039;, even if they are not attempting to actively fight their Grappler. Targets who are unconscious or otherwise incapacitated are considered &#039;&#039;&#039;Cooperative&#039;&#039;&#039;.&lt;br /&gt;
=== Human Shield ===&lt;br /&gt;
As an action or reaction, a Grappler may choose to use their target or themselves as a &#039;&#039;&#039;Human Shield&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When using a Human Shield, the Grappler gains a Full Cover bonus. When the Grappler is acting as a Human Shield, the target gains a Full Cover bonus. &lt;br /&gt;
&lt;br /&gt;
Any Attacks made against the &#039;&#039;&#039;Shielded&#039;&#039;&#039; character will require a roll from the Human Shield as well. Regardless of the Shielded character’s success or failure, if the Human Shield fails their Defense roll, they take the attack’s damage as well.&lt;br /&gt;
=== Focusing ===&lt;br /&gt;
Focusing for a turn or starting combat while Focused gives Advantage to a Ranged Attack made on the next turn. If you do not fire after aiming, this bonus is lost unless Aiming is maintained.&lt;br /&gt;
&lt;br /&gt;
Focusing can be done from cover. Becoming engaged or taking melee damage while Focused “breaks” your focus. If a ranged attack is made against a Focused character, that character must make a DC 12 Resolve roll to remain focused.&lt;br /&gt;
&lt;br /&gt;
Focus is maintained so long as the character does not move or otherwise have their Focus broken. Once broken, Focus needs to be done once more.&lt;br /&gt;
=== Reloading ===&lt;br /&gt;
Once your available ammunition (clip, magazine, etc.) is spent, players using a ranged weapon must make a roll to Reload (DC 10). On success, their Reload is instant, allowing them to take a normal action. On failure, a turn must be dedicated to Reloading their weapon. &lt;br /&gt;
=== Suppressive Fire ===&lt;br /&gt;
With a fully automatic weapon aimed at a player behind cover, attacking players can lay down Suppressive Fire. More than half of the weapon’s magazine must remain.&lt;br /&gt;
&lt;br /&gt;
Suppressive Fire requires a typical Ranged ATK vs. Ranged DEF roll. On the attacker’s success, typical damage applies. &lt;br /&gt;
&lt;br /&gt;
Regardless of success or failure, the attacking player burns their magazine of their automatic weapon to attempt to negate movement or action from the defending player. If a defender attempts to move or act in a way that would expose them to Suppressive Fire, they must make a Resolve (DC 12) roll. On success, they may take their action as normal. On failure, they are startled in place. A target under Suppressive Fire may take any other action or reaction that would not expose them to suppressive fire. &lt;br /&gt;
&lt;br /&gt;
Suppressive Fire ends at the beginning of the attacker’s next turn. At the end of Suppressive Fire, the attacking player must reload to continue using their currently equipped weapon.&lt;br /&gt;
= Armor =&lt;br /&gt;
Armor is applied as Light, Medium, or Heavy armor. Each type of armor has its own Armor Stacks and Drawbacks. &lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Armor Rules &lt;br /&gt;
|-&lt;br /&gt;
| Type || Stacks || Drawbacks&lt;br /&gt;
|-&lt;br /&gt;
| Light || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Medium || 2 || -1 Initiative, -1 Movement&lt;br /&gt;
|-&lt;br /&gt;
| Heavy || 3 || -2 Initiative, -2 Movement&lt;br /&gt;
|}&lt;br /&gt;
If an attack is taken while wearing armor, a stack is consumed to reduce the damage of that attack to 1. Critical attacks deal 2 stacks of damage to armor and deal full damage if only one Armor Stack is remaining.&lt;br /&gt;
= Ranged Combat =&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
| Ranged Defense || -&lt;br /&gt;
|-&lt;br /&gt;
| Ranged Attack || Aiming/2&lt;br /&gt;
|}&lt;br /&gt;
Several aspects of the mechanical environment affect ranged rolls, including the lighting of an area, whether or not a weapon is scoped, and more. Please note that Sniping a character from a distance, while an option, is still subject to appropriate combat rules, CK rules, and ticketing. The range of a ranged weapon is dictated by in-game line-of-sight (LoS). Consider the following before making an attack roll:&lt;br /&gt;
* Whether the target is ‘visible’ &lt;br /&gt;
* Whether there is a clear line of fire from attacker to defender&lt;br /&gt;
* Whether any allies are engaged in any actions that may put them at risk of being hit (i.e. grappling)&lt;br /&gt;
The above factors determine whether a target can be hit. In the special case of the flamethrower, its range is 15 in-game tiles.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Ranged Weapons&lt;br /&gt;
! Weapon Type !! Damage !! Crit Requirement !! Crit Dmg&lt;br /&gt;
|-&lt;br /&gt;
| Assault Weapons || 4 || Double 5/Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| M60/LMG&#039;s|| 5 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Rifles &amp;amp; Shotguns || 4 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Pistols || 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Flamethrowers &amp;lt;ref&amp;gt;Flamethrower Crit Rule: If a character holding a Flamethrower is hit by a Critical Hit, there&#039;s a 50% chance (1-2-3 on a d6 roll) that the fuel canister explodes. Dealing 2 Damage to the wielder.&amp;lt;/ref&amp;gt; &amp;lt;ref&amp;gt;Flamethrower Cover Rules: Engaged Condition and a 50% body coverage behind a tile don&#039;t offer Cover from a Flamethrower attack. If a Flamethrower attack targets a character engaged with other characters, all of them will be hit by the attack and roll Ranged Defence. Only a coverage of at least 80% behind a Tile grants the Cover modifier from a Flamethrower Attack. (ex: Only a limb/head visible)&amp;lt;/ref&amp;gt; || 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Crossbows || 4 || Double 6 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Melee Combat =&lt;br /&gt;
Melee combat occurs with contested rolls. The success of a melee attack roll is dependent on the following values:&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Melee Attack !! ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|-&lt;br /&gt;
| Melee Defense|| ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|}&lt;br /&gt;
Each Melee weapon has its own damage and crit requirements.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Weapon Type || Damage || Crit Requirement || Crit Damage&lt;br /&gt;
|-&lt;br /&gt;
| Two-Handed|| 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| One-Handed &amp;lt;ref&amp;gt;One-Handed Double-Tap: Characters utilizing one-handed weapons can forgo their movement to make a second attack at disadvantage. This attack cannot crit.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Spears &amp;lt;ref&amp;gt;Spear Range Rule: A character holding a spear engages hostile characters within 2 Tiles instead of 1.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 3&lt;br /&gt;
|-&lt;br /&gt;
| Bare Hands, Improvised, Weapon Stocks|| 1 || - || -&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=422</id>
		<title>Template:Combat Rolls</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=422"/>
		<updated>2025-11-10T19:19:19Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* Ranged Combat */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Combat =&lt;br /&gt;
Here at NWO, we pride ourselves on the sense of danger and risk ever-present around our characters and plots. Every combat encounter can be a heroic victory or a crushing defeat, and very often brings with it lethal consequences. Every fight should be entered with the understanding that this...could be it.&lt;br /&gt;
&lt;br /&gt;
While all players are expected to adhere to these rules when engaging in combat between characters on their own, we try to be more open and rewarding towards creativity when interacting with our Staff team in events and encounters. While the rules listed below are the usual standard used by our Storytellers and players during these Staff-hosted scenes, our NPC creations and environmental hazards sometimes require us to make adjustments to the mechanics of a scene to properly simulate the threats or conditions our players are facing. &lt;br /&gt;
&lt;br /&gt;
Not to worry, though, as this same flexibility extends to you as a player! If you want to perform actions not easily expressed through these rules, talk to the Staff running the scene you are within in the OoC chat tab, and they will very often work with you to try and actualize your imagined plan. This is no guarantee that it will work, but player creativity is something we always attempt to reward.&lt;br /&gt;
=== Actions &amp;amp; Movement ===&lt;br /&gt;
A combat round is typically comprised of an &#039;&#039;&#039;Action&#039;&#039;&#039; and &#039;&#039;&#039;Movement&#039;&#039;&#039;. &#039;&#039;&#039;Reactions&#039;&#039;&#039; can sometimes be taken in special circumstances, such as &#039;&#039;&#039;Attacks of Opportunity&#039;&#039;&#039; or while &#039;&#039;&#039;Grappling&#039;&#039;&#039; someone. Exceptions will be noted in the relevant sections.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Movement&#039;&#039;&#039; is self-explanatory—It is the way a character “moves” around a space. Characters must toggle their movement range on their Dice Panel and may only move in one path to a point within their movement range. Movement imposes a -2 shooting penalty, regardless of the order of one’s Action or Movement.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Actions&#039;&#039;&#039; include a variety of things such as &#039;&#039;&#039;Attacking&#039;&#039;&#039;, &#039;&#039;&#039;Grappling&#039;&#039;&#039;, &#039;&#039;&#039;Shoving&#039;&#039;&#039;, &#039;&#039;&#039;Throwing&#039;&#039;&#039;, &#039;&#039;&#039;Helping&#039;&#039;&#039;, or virtually anything that is not movement. &lt;br /&gt;
&lt;br /&gt;
Your combat roll in a scenario is determined by the weapon a character has equipped, but will generally be &#039;&#039;&#039;Melee&#039;&#039;&#039;, &#039;&#039;&#039;Ranged&#039;&#039;&#039;, or &#039;&#039;&#039;Unarmed&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Those who &#039;&#039;&#039;apply statuses (AoE, DoT, Suppressive Fire, etc.) or wish to utilize special rulings and mechanics are responsible for enforcing them&#039;&#039;&#039;. If you forget to apply your statuses or recall your rulings when using them on others, you lose that turn of said status. This rule applies to NPCs and PCs.&lt;br /&gt;
=== Engagement ===&lt;br /&gt;
Engagement occurs when one character enters another’s melee range. &#039;&#039;&#039;Engagement does not occur if the weapon being held is a ranged weapon&#039;&#039;&#039;. By default, this is one tile for all melee weapons except for spears, which have a two-tile range. &lt;br /&gt;
&lt;br /&gt;
While Engaged, characters cannot shoot with two-handed ranged weapons and cannot make any movement without triggering an AOO, including movement around an attacker.&lt;br /&gt;
=== Dashing ===&lt;br /&gt;
As an action, a character can double their movement speed from 6 to 12 tiles. Dashing is not the same as Disengaging, and can still provoke Engagement or Attacks of Opportunity.&lt;br /&gt;
=== Disarming ===&lt;br /&gt;
As an action, a character may choose to Disarm their target so long as they have at least one hand free. An attacker with only one hand free attempts their Disarm at disadvantage. An attacker with two hands free attempts their Disarm with no penalty. &lt;br /&gt;
&lt;br /&gt;
Disarms are made with a normal one-handed or unarmed attack roll. On success, the target is disarmed, dropping their weapon on the ground or in the hands of their attacker if the attacker has enough hands free (Attacker’s choice). The disarming character also deals damage equal to the weapon in their hand (1 damage if unarmed). &lt;br /&gt;
=== Swapping Weapons ===&lt;br /&gt;
Swapping weapons in combat is risky, but often necessary. Swapping weapons takes an action in most scenarios. However, if the weapon you are attempting to swap to is a one-handed weapon that is readily available, such as a holstered sidearm or a sheathed knife, you can choose to drop your currently equipped weapon on the ground to Quick Draw your one-handed weapon within the same turn. Your dropped weapon will require an action to pick up and equip once more. Please note that attachments, equipment, or other factors such as backstory or experience do not affect this rule.&lt;br /&gt;
=== Disengaging ===&lt;br /&gt;
As an action, a character may choose to Disengage from combat. This allows them to make any movement, barring any conditions which may prevent said movement, without proccing AOOs. &lt;br /&gt;
&lt;br /&gt;
If you are Engaged by two or more people, Disengage actions require a Resolve (DC 12) roll to be attempted. &lt;br /&gt;
=== Helping ===&lt;br /&gt;
As an action, any character can Help another character by rolling a Resolve (DC 10) roll. On success, the target gains advantage on their next roll, depending on the nature of the help. Helping must be done with a specific intention that is laid out clearly and concisely by the helping player on their turn. In other words, you cannot blanket Help on every possible roll that another character might do. It is recommended to have a brief OOC discussion about what your Helping action will look like with the target. &lt;br /&gt;
&lt;br /&gt;
Some examples of Help rolls might include:&lt;br /&gt;
* Attempting to restrain someone who is already grappled to give the attacking player an easier strike (Melee or Ranged Attack—While Grappling)&lt;br /&gt;
* Assisting a player in moving a heavy object (Strength)&lt;br /&gt;
* Providing an extra pair of hands during a medical emergency (First Aid)&lt;br /&gt;
* Providing an extra pair of hands while making dinner (Cooking)&lt;br /&gt;
&lt;br /&gt;
This list is, of course, non-exhaustive. Be creative!&lt;br /&gt;
=== Stealth &amp;amp; Ambushing ===&lt;br /&gt;
Ambushing a combatant gives several advantages. &lt;br /&gt;
&lt;br /&gt;
To attempt an Ambush, the attacker must succeed on a Hiding roll against the defender’s Perception roll. If the defender’s Perception roll beats the attacker’s Hiding roll, combat initiates as per usual, and no bonuses are obtained.&lt;br /&gt;
&lt;br /&gt;
Combatants who are ambushed are Surprised and cannot act for the first round of combat. Conversely, attackers who have Ambushed a combatant or combatants have advantage on attack rolls made during this Surprise round.&lt;br /&gt;
=== Overwatch ===&lt;br /&gt;
While using a weapon, players can use their turn to go on Overwatch. Overwatch allows a player to “delay” their attack until certain events happen. Players must define these events clearly and concisely on their turn.&lt;br /&gt;
&lt;br /&gt;
Examples of these events might include:&lt;br /&gt;
* When a specific character moves&lt;br /&gt;
* When any non-ally character enters a certain area&lt;br /&gt;
* Before or after another character acts&lt;br /&gt;
* If another character takes or deals damage&lt;br /&gt;
&lt;br /&gt;
When Overwatch is triggered, typical range and rolling rules still apply. If Overwatch is not used, Overwatch ends on the player’s following turn unless used again.&lt;br /&gt;
=== Throwables, Areas of Effect (AoE), and Conditions ===&lt;br /&gt;
Various weapons, such as throwables, may leave an Area of Affect (AOE). AOEs can apply various Conditions to a target. Some examples of these Conditions include:&lt;br /&gt;
* Burning (Fire)&lt;br /&gt;
* Burning (Acid)&lt;br /&gt;
* Electrocution&lt;br /&gt;
* Smoke&lt;br /&gt;
* Poison&lt;br /&gt;
&lt;br /&gt;
All of these Conditions apply the same mechanical effect. Regardless of the type, Conditions apply disadvantage to the afflicted character. &lt;br /&gt;
&lt;br /&gt;
As a rule of thumb, AOEs affect a 3x3 tile area and linger on the ground for 3 turns unless specified by the item or Storyteller. A character moving into an existing AOE requires passing a DC 10 Robustness check or else risk taking on the Condition imposed by it. &lt;br /&gt;
&lt;br /&gt;
An action can be spent by an afflicted character to remove a Condition unless attempted while within an AOE that applies it. Conditions can be stacked up to two times.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Name !! Damage On-Hit !! Area of Effect&lt;br /&gt;
|-&lt;br /&gt;
| Molotov || 2 || 3x3&lt;br /&gt;
|-&lt;br /&gt;
| Smoke Bomb || - || 5x5&lt;br /&gt;
|-&lt;br /&gt;
| Acid Attack || 3 || Single Target&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Throwables can be tossed onto a tile, affecting the adjacent 3x3 area, by making a Ranged Attack (DC 12) roll. On failure, the throwable is placed in a random location within 2 tiles of the intended location.&lt;br /&gt;
=== Attacks of Opportunity ===&lt;br /&gt;
Attacks of Opportunity (AOO) or Opportunity Attacks can occur when a target chooses to leave an attacker’s Engagement range. Please note that moving while staying within range of an attacker does not trigger an AOO. By default, all Human characters can use one AOO per round of combat, resetting on that character’s turn. Zombies, infected, and other creatures may have more attacks of opportunity, dependent on their individual skill sets and Storyteller preferences.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ranged weapons cannot utilize Attacks of Opportunity to deal a Ranged Attack&#039;&#039;&#039;. A person with a ranged weapon may make an &#039;&#039;&#039;Improvised Weapon&#039;&#039;&#039; roll to hit someone with a stock or “pistol whip,” but they cannot fire at a retreating opponent. &lt;br /&gt;
=== Going Prone ===&lt;br /&gt;
Prone refers to the act of lying on one’s back or stomach on the ground. In NWO, one can take a prone stance or be made to take a prone stance willingly or unwillingly. The Prone condition comes with several advantages and disadvantages:&lt;br /&gt;
* Prone characters can crawl to an adjacent tile while prone. If a character is prone and behind cover, they cannot make an attack unless their target is adjacent and not on the opposite side of said cover. &lt;br /&gt;
* Ranged attacks against prone characters are at a disadvantage.&lt;br /&gt;
** Ranged attacks have advantage against prone targets within CQC/point-blank range (1 tile). If Engaged, typical Engagement rules apply. &lt;br /&gt;
* Melee attacks against prone characters are at advantage.&lt;br /&gt;
* Prone characters may use their movement or action to stand from being prone.&lt;br /&gt;
* Prone characters cannot Engage characters within melee range.&lt;br /&gt;
=== Shoving ===&lt;br /&gt;
Players may also choose to shove another player to displace or knock them down.&lt;br /&gt;
&lt;br /&gt;
One player can shove another by engaging in a contested STR vs. STR or FIT (Defender’s choice) roll. Defenders can choose to automatically fail this roll if desired.&lt;br /&gt;
&lt;br /&gt;
Shoving players can knock the target Prone or knock them away—Attacker’s choice. &lt;br /&gt;
* If knocked Prone, Prone rules apply.&lt;br /&gt;
* If knocked away, the target gets shoved back [Attacker’s ½ STR] tiles in the direction of the Attacker’s choice.&lt;br /&gt;
=== Grappling ===&lt;br /&gt;
Characters may choose to Grapple a target to prevent escape or otherwise impose various disadvantages.&lt;br /&gt;
&lt;br /&gt;
One character may grapple another by engaging in a STR vs STR contest, so long as one of the Attacker’s hands is free. Attackers can drop a two-handed weapon same turn to attempt to Grapple a target, leaving their weapon on the ground. This weapon would need an action to be retrieved once more.&lt;br /&gt;
&lt;br /&gt;
A Grappled character has the following disadvantages imposed upon them until the Grapple is broken:&lt;br /&gt;
* Movement is reduced to 0. &lt;br /&gt;
** Grappled characters can rotate around the grappler, but cannot disengage or exit the tile adjacent. &lt;br /&gt;
* All attacks from the target are made at disadvantage&lt;br /&gt;
* Grappled characters do not provoke AOOs&lt;br /&gt;
&lt;br /&gt;
On the Grappled character’s turn, they may use their action to attempt to break free with another STR vs STR contest. On fail, the Grappled character remains Grappled. &lt;br /&gt;
&lt;br /&gt;
Characters grappling with a target can force that target to a prone state as an action with no additional contest. Both characters are considered Prone. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grappled targets are not restrained or disarmed&#039;&#039;&#039;. Grappled characters may still attack any target that would normally be capable of with the weapon they may (or may not) have equipped, including the grappler, at disadvantage. A successful attack on the grappler from the target forces a reroll of the contest. &lt;br /&gt;
&lt;br /&gt;
Grapplers may attack any character with a one-handed weapon at disadvantage. Disadvantage does not apply if their target is &#039;&#039;&#039;Cooperative&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Targets under duress, such as hostages, are considered &#039;&#039;&#039;Uncooperative&#039;&#039;&#039;, even if they are not attempting to actively fight their Grappler. Targets who are unconscious or otherwise incapacitated are considered &#039;&#039;&#039;Cooperative&#039;&#039;&#039;.&lt;br /&gt;
=== Human Shield ===&lt;br /&gt;
As an action or reaction, a Grappler may choose to use their target or themselves as a &#039;&#039;&#039;Human Shield&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When using a Human Shield, the Grappler gains a Full Cover bonus. When the Grappler is acting as a Human Shield, the target gains a Full Cover bonus. &lt;br /&gt;
&lt;br /&gt;
Any Attacks made against the &#039;&#039;&#039;Shielded&#039;&#039;&#039; character will require a roll from the Human Shield as well. Regardless of the Shielded character’s success or failure, if the Human Shield fails their Defense roll, they take the attack’s damage as well.&lt;br /&gt;
=== Focusing ===&lt;br /&gt;
Focusing for a turn or starting combat while Focused gives Advantage to a Ranged Attack made on the next turn. If you do not fire after aiming, this bonus is lost unless Aiming is maintained.&lt;br /&gt;
&lt;br /&gt;
Focusing can be done from cover. Becoming engaged or taking melee damage while Focused “breaks” your focus. If a ranged attack is made against a Focused character, that character must make a DC 12 Resolve roll to remain focused.&lt;br /&gt;
&lt;br /&gt;
Focus is maintained so long as the character does not move or otherwise have their Focus broken. Once broken, Focus needs to be done once more.&lt;br /&gt;
=== Reloading ===&lt;br /&gt;
Once your available ammunition (clip, magazine, etc.) is spent, players using a ranged weapon must make a roll to Reload (DC 10). On success, their Reload is instant, allowing them to take a normal action. On failure, a turn must be dedicated to Reloading their weapon. &lt;br /&gt;
=== Suppressive Fire ===&lt;br /&gt;
With a fully automatic weapon aimed at a player behind cover, attacking players can lay down Suppressive Fire. More than half of the weapon’s magazine must remain.&lt;br /&gt;
&lt;br /&gt;
Suppressive Fire requires a typical Ranged ATK vs. Ranged DEF roll. On the attacker’s success, typical damage applies. &lt;br /&gt;
&lt;br /&gt;
Regardless of success or failure, the attacking player burns their magazine of their automatic weapon to attempt to negate movement or action from the defending player. If a defender attempts to move or act in a way that would expose them to Suppressive Fire, they must make a Resolve (DC 12) roll. On success, they may take their action as normal. On failure, they are startled in place. A target under Suppressive Fire may take any other action or reaction that would not expose them to suppressive fire. &lt;br /&gt;
&lt;br /&gt;
Suppressive Fire ends at the beginning of the attacker’s next turn. At the end of Suppressive Fire, the attacking player must reload to continue using their currently equipped weapon.&lt;br /&gt;
= Armor =&lt;br /&gt;
Armor is applied as Light, Medium, or Heavy armor. Each type of armor has its own Armor Stacks and Drawbacks. &lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Armor Rules &lt;br /&gt;
|-&lt;br /&gt;
| Type || Stacks || Drawbacks&lt;br /&gt;
|-&lt;br /&gt;
| Light || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Medium || 2 || -1 Initiative, -1 Movement&lt;br /&gt;
|-&lt;br /&gt;
| Heavy || 3 || -2 Initiative, -2 Movement&lt;br /&gt;
|}&lt;br /&gt;
If an attack is taken while wearing armor, a stack is consumed to reduce the damage of that attack to 1. Critical attacks deal 2 stacks of damage to armor and deal full damage if only one Armor Stack is remaining.&lt;br /&gt;
= Ranged Combat =&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
| Ranged Defense || -&lt;br /&gt;
|-&lt;br /&gt;
| Ranged Attack || Aiming/2&lt;br /&gt;
|}&lt;br /&gt;
Several aspects of the mechanical environment affect ranged rolls, including the lighting of an area, whether or not a weapon is scoped, and more. Please note that Sniping a character from a distance, while an option, is still subject to appropriate combat rules, CK rules, and ticketing. The range of a ranged weapon is dictated by in-game line-of-sight (LoS). Consider the following before making an attack roll:&lt;br /&gt;
* Whether the target is ‘visible’ &lt;br /&gt;
* Whether there is a clear line of fire from attacker to defender&lt;br /&gt;
* Whether any allies are engaged in any actions that may put them at risk of being hit (i.e. grappling)&lt;br /&gt;
The above factors determine whether a target can be hit. In the special case of the flamethrower, its range is 15 in-game tiles.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Ranged Weapons&lt;br /&gt;
! Weapon Type !! Damage !! Crit Requirement !! Crit Dmg&lt;br /&gt;
|-&lt;br /&gt;
| Assault Weapons || 4 || Double 5/Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| M60/LMG&#039;s|| 5 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Rifles &amp;amp; Shotguns || 4 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Pistols || 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Flamethrowers &amp;lt;ref&amp;gt;Flamethrower Crit Rule: If a character holding a Flamethrower is hit by a Critical Hit, there&#039;s a 50% chance (1-2-3 on a d6 roll) that the fuel canister explodes. Dealing 2 Damage to the wielder.&amp;lt;/ref&amp;gt; &amp;lt;ref&amp;gt;Flamethrower Cover Rules: Engaged Condition and a 50% body coverage behind a tile don&#039;t offer Cover from a Flamethrower attack. If a Flamethrower attack targets a character engaged with other characters, all of them will be hit by the attack and roll Ranged Defence. Only a coverage of at least 80% behind a Tile grants the Cover modifier from a Flamethrower Attack. (ex: Only a limb/head visible)&amp;lt;/ref&amp;gt; || 3 || Double 5/Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Crossbows || 4 || Double 6 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Melee Combat =&lt;br /&gt;
Melee combat occurs with contested rolls. The success of a melee attack roll is dependent on the following values:&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Melee Attack !! ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|-&lt;br /&gt;
| Melee Defense|| ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|}&lt;br /&gt;
Each Melee weapon has its own damage and crit requirements.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Weapon Type || Damage || Crit Requirement || Crit Damage&lt;br /&gt;
|-&lt;br /&gt;
| Two-Handed|| 3 || Double 5 or Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| One-Handed &amp;lt;ref&amp;gt;One-Handed Double-Tap: Characters utilizing one-handed weapons can forgo their movement to make a second attack at disadvantage. This attack cannot crit.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Spears &amp;lt;ref&amp;gt;Spear Range Rule: A character holding a spear engages hostile characters within 2 Tiles instead of 1.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 3&lt;br /&gt;
|-&lt;br /&gt;
| Bare Hands, Improvised, Weapon Stocks|| 1 || - || -&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=421</id>
		<title>Template:Combat Rolls</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Combat_Rolls&amp;diff=421"/>
		<updated>2025-11-10T19:19:04Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* Ranged Combat */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Combat =&lt;br /&gt;
Here at NWO, we pride ourselves on the sense of danger and risk ever-present around our characters and plots. Every combat encounter can be a heroic victory or a crushing defeat, and very often brings with it lethal consequences. Every fight should be entered with the understanding that this...could be it.&lt;br /&gt;
&lt;br /&gt;
While all players are expected to adhere to these rules when engaging in combat between characters on their own, we try to be more open and rewarding towards creativity when interacting with our Staff team in events and encounters. While the rules listed below are the usual standard used by our Storytellers and players during these Staff-hosted scenes, our NPC creations and environmental hazards sometimes require us to make adjustments to the mechanics of a scene to properly simulate the threats or conditions our players are facing. &lt;br /&gt;
&lt;br /&gt;
Not to worry, though, as this same flexibility extends to you as a player! If you want to perform actions not easily expressed through these rules, talk to the Staff running the scene you are within in the OoC chat tab, and they will very often work with you to try and actualize your imagined plan. This is no guarantee that it will work, but player creativity is something we always attempt to reward.&lt;br /&gt;
=== Actions &amp;amp; Movement ===&lt;br /&gt;
A combat round is typically comprised of an &#039;&#039;&#039;Action&#039;&#039;&#039; and &#039;&#039;&#039;Movement&#039;&#039;&#039;. &#039;&#039;&#039;Reactions&#039;&#039;&#039; can sometimes be taken in special circumstances, such as &#039;&#039;&#039;Attacks of Opportunity&#039;&#039;&#039; or while &#039;&#039;&#039;Grappling&#039;&#039;&#039; someone. Exceptions will be noted in the relevant sections.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Movement&#039;&#039;&#039; is self-explanatory—It is the way a character “moves” around a space. Characters must toggle their movement range on their Dice Panel and may only move in one path to a point within their movement range. Movement imposes a -2 shooting penalty, regardless of the order of one’s Action or Movement.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Actions&#039;&#039;&#039; include a variety of things such as &#039;&#039;&#039;Attacking&#039;&#039;&#039;, &#039;&#039;&#039;Grappling&#039;&#039;&#039;, &#039;&#039;&#039;Shoving&#039;&#039;&#039;, &#039;&#039;&#039;Throwing&#039;&#039;&#039;, &#039;&#039;&#039;Helping&#039;&#039;&#039;, or virtually anything that is not movement. &lt;br /&gt;
&lt;br /&gt;
Your combat roll in a scenario is determined by the weapon a character has equipped, but will generally be &#039;&#039;&#039;Melee&#039;&#039;&#039;, &#039;&#039;&#039;Ranged&#039;&#039;&#039;, or &#039;&#039;&#039;Unarmed&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Those who &#039;&#039;&#039;apply statuses (AoE, DoT, Suppressive Fire, etc.) or wish to utilize special rulings and mechanics are responsible for enforcing them&#039;&#039;&#039;. If you forget to apply your statuses or recall your rulings when using them on others, you lose that turn of said status. This rule applies to NPCs and PCs.&lt;br /&gt;
=== Engagement ===&lt;br /&gt;
Engagement occurs when one character enters another’s melee range. &#039;&#039;&#039;Engagement does not occur if the weapon being held is a ranged weapon&#039;&#039;&#039;. By default, this is one tile for all melee weapons except for spears, which have a two-tile range. &lt;br /&gt;
&lt;br /&gt;
While Engaged, characters cannot shoot with two-handed ranged weapons and cannot make any movement without triggering an AOO, including movement around an attacker.&lt;br /&gt;
=== Dashing ===&lt;br /&gt;
As an action, a character can double their movement speed from 6 to 12 tiles. Dashing is not the same as Disengaging, and can still provoke Engagement or Attacks of Opportunity.&lt;br /&gt;
=== Disarming ===&lt;br /&gt;
As an action, a character may choose to Disarm their target so long as they have at least one hand free. An attacker with only one hand free attempts their Disarm at disadvantage. An attacker with two hands free attempts their Disarm with no penalty. &lt;br /&gt;
&lt;br /&gt;
Disarms are made with a normal one-handed or unarmed attack roll. On success, the target is disarmed, dropping their weapon on the ground or in the hands of their attacker if the attacker has enough hands free (Attacker’s choice). The disarming character also deals damage equal to the weapon in their hand (1 damage if unarmed). &lt;br /&gt;
=== Swapping Weapons ===&lt;br /&gt;
Swapping weapons in combat is risky, but often necessary. Swapping weapons takes an action in most scenarios. However, if the weapon you are attempting to swap to is a one-handed weapon that is readily available, such as a holstered sidearm or a sheathed knife, you can choose to drop your currently equipped weapon on the ground to Quick Draw your one-handed weapon within the same turn. Your dropped weapon will require an action to pick up and equip once more. Please note that attachments, equipment, or other factors such as backstory or experience do not affect this rule.&lt;br /&gt;
=== Disengaging ===&lt;br /&gt;
As an action, a character may choose to Disengage from combat. This allows them to make any movement, barring any conditions which may prevent said movement, without proccing AOOs. &lt;br /&gt;
&lt;br /&gt;
If you are Engaged by two or more people, Disengage actions require a Resolve (DC 12) roll to be attempted. &lt;br /&gt;
=== Helping ===&lt;br /&gt;
As an action, any character can Help another character by rolling a Resolve (DC 10) roll. On success, the target gains advantage on their next roll, depending on the nature of the help. Helping must be done with a specific intention that is laid out clearly and concisely by the helping player on their turn. In other words, you cannot blanket Help on every possible roll that another character might do. It is recommended to have a brief OOC discussion about what your Helping action will look like with the target. &lt;br /&gt;
&lt;br /&gt;
Some examples of Help rolls might include:&lt;br /&gt;
* Attempting to restrain someone who is already grappled to give the attacking player an easier strike (Melee or Ranged Attack—While Grappling)&lt;br /&gt;
* Assisting a player in moving a heavy object (Strength)&lt;br /&gt;
* Providing an extra pair of hands during a medical emergency (First Aid)&lt;br /&gt;
* Providing an extra pair of hands while making dinner (Cooking)&lt;br /&gt;
&lt;br /&gt;
This list is, of course, non-exhaustive. Be creative!&lt;br /&gt;
=== Stealth &amp;amp; Ambushing ===&lt;br /&gt;
Ambushing a combatant gives several advantages. &lt;br /&gt;
&lt;br /&gt;
To attempt an Ambush, the attacker must succeed on a Hiding roll against the defender’s Perception roll. If the defender’s Perception roll beats the attacker’s Hiding roll, combat initiates as per usual, and no bonuses are obtained.&lt;br /&gt;
&lt;br /&gt;
Combatants who are ambushed are Surprised and cannot act for the first round of combat. Conversely, attackers who have Ambushed a combatant or combatants have advantage on attack rolls made during this Surprise round.&lt;br /&gt;
=== Overwatch ===&lt;br /&gt;
While using a weapon, players can use their turn to go on Overwatch. Overwatch allows a player to “delay” their attack until certain events happen. Players must define these events clearly and concisely on their turn.&lt;br /&gt;
&lt;br /&gt;
Examples of these events might include:&lt;br /&gt;
* When a specific character moves&lt;br /&gt;
* When any non-ally character enters a certain area&lt;br /&gt;
* Before or after another character acts&lt;br /&gt;
* If another character takes or deals damage&lt;br /&gt;
&lt;br /&gt;
When Overwatch is triggered, typical range and rolling rules still apply. If Overwatch is not used, Overwatch ends on the player’s following turn unless used again.&lt;br /&gt;
=== Throwables, Areas of Effect (AoE), and Conditions ===&lt;br /&gt;
Various weapons, such as throwables, may leave an Area of Affect (AOE). AOEs can apply various Conditions to a target. Some examples of these Conditions include:&lt;br /&gt;
* Burning (Fire)&lt;br /&gt;
* Burning (Acid)&lt;br /&gt;
* Electrocution&lt;br /&gt;
* Smoke&lt;br /&gt;
* Poison&lt;br /&gt;
&lt;br /&gt;
All of these Conditions apply the same mechanical effect. Regardless of the type, Conditions apply disadvantage to the afflicted character. &lt;br /&gt;
&lt;br /&gt;
As a rule of thumb, AOEs affect a 3x3 tile area and linger on the ground for 3 turns unless specified by the item or Storyteller. A character moving into an existing AOE requires passing a DC 10 Robustness check or else risk taking on the Condition imposed by it. &lt;br /&gt;
&lt;br /&gt;
An action can be spent by an afflicted character to remove a Condition unless attempted while within an AOE that applies it. Conditions can be stacked up to two times.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Name !! Damage On-Hit !! Area of Effect&lt;br /&gt;
|-&lt;br /&gt;
| Molotov || 2 || 3x3&lt;br /&gt;
|-&lt;br /&gt;
| Smoke Bomb || - || 5x5&lt;br /&gt;
|-&lt;br /&gt;
| Acid Attack || 3 || Single Target&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Throwables can be tossed onto a tile, affecting the adjacent 3x3 area, by making a Ranged Attack (DC 12) roll. On failure, the throwable is placed in a random location within 2 tiles of the intended location.&lt;br /&gt;
=== Attacks of Opportunity ===&lt;br /&gt;
Attacks of Opportunity (AOO) or Opportunity Attacks can occur when a target chooses to leave an attacker’s Engagement range. Please note that moving while staying within range of an attacker does not trigger an AOO. By default, all Human characters can use one AOO per round of combat, resetting on that character’s turn. Zombies, infected, and other creatures may have more attacks of opportunity, dependent on their individual skill sets and Storyteller preferences.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ranged weapons cannot utilize Attacks of Opportunity to deal a Ranged Attack&#039;&#039;&#039;. A person with a ranged weapon may make an &#039;&#039;&#039;Improvised Weapon&#039;&#039;&#039; roll to hit someone with a stock or “pistol whip,” but they cannot fire at a retreating opponent. &lt;br /&gt;
=== Going Prone ===&lt;br /&gt;
Prone refers to the act of lying on one’s back or stomach on the ground. In NWO, one can take a prone stance or be made to take a prone stance willingly or unwillingly. The Prone condition comes with several advantages and disadvantages:&lt;br /&gt;
* Prone characters can crawl to an adjacent tile while prone. If a character is prone and behind cover, they cannot make an attack unless their target is adjacent and not on the opposite side of said cover. &lt;br /&gt;
* Ranged attacks against prone characters are at a disadvantage.&lt;br /&gt;
** Ranged attacks have advantage against prone targets within CQC/point-blank range (1 tile). If Engaged, typical Engagement rules apply. &lt;br /&gt;
* Melee attacks against prone characters are at advantage.&lt;br /&gt;
* Prone characters may use their movement or action to stand from being prone.&lt;br /&gt;
* Prone characters cannot Engage characters within melee range.&lt;br /&gt;
=== Shoving ===&lt;br /&gt;
Players may also choose to shove another player to displace or knock them down.&lt;br /&gt;
&lt;br /&gt;
One player can shove another by engaging in a contested STR vs. STR or FIT (Defender’s choice) roll. Defenders can choose to automatically fail this roll if desired.&lt;br /&gt;
&lt;br /&gt;
Shoving players can knock the target Prone or knock them away—Attacker’s choice. &lt;br /&gt;
* If knocked Prone, Prone rules apply.&lt;br /&gt;
* If knocked away, the target gets shoved back [Attacker’s ½ STR] tiles in the direction of the Attacker’s choice.&lt;br /&gt;
=== Grappling ===&lt;br /&gt;
Characters may choose to Grapple a target to prevent escape or otherwise impose various disadvantages.&lt;br /&gt;
&lt;br /&gt;
One character may grapple another by engaging in a STR vs STR contest, so long as one of the Attacker’s hands is free. Attackers can drop a two-handed weapon same turn to attempt to Grapple a target, leaving their weapon on the ground. This weapon would need an action to be retrieved once more.&lt;br /&gt;
&lt;br /&gt;
A Grappled character has the following disadvantages imposed upon them until the Grapple is broken:&lt;br /&gt;
* Movement is reduced to 0. &lt;br /&gt;
** Grappled characters can rotate around the grappler, but cannot disengage or exit the tile adjacent. &lt;br /&gt;
* All attacks from the target are made at disadvantage&lt;br /&gt;
* Grappled characters do not provoke AOOs&lt;br /&gt;
&lt;br /&gt;
On the Grappled character’s turn, they may use their action to attempt to break free with another STR vs STR contest. On fail, the Grappled character remains Grappled. &lt;br /&gt;
&lt;br /&gt;
Characters grappling with a target can force that target to a prone state as an action with no additional contest. Both characters are considered Prone. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Grappled targets are not restrained or disarmed&#039;&#039;&#039;. Grappled characters may still attack any target that would normally be capable of with the weapon they may (or may not) have equipped, including the grappler, at disadvantage. A successful attack on the grappler from the target forces a reroll of the contest. &lt;br /&gt;
&lt;br /&gt;
Grapplers may attack any character with a one-handed weapon at disadvantage. Disadvantage does not apply if their target is &#039;&#039;&#039;Cooperative&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Targets under duress, such as hostages, are considered &#039;&#039;&#039;Uncooperative&#039;&#039;&#039;, even if they are not attempting to actively fight their Grappler. Targets who are unconscious or otherwise incapacitated are considered &#039;&#039;&#039;Cooperative&#039;&#039;&#039;.&lt;br /&gt;
=== Human Shield ===&lt;br /&gt;
As an action or reaction, a Grappler may choose to use their target or themselves as a &#039;&#039;&#039;Human Shield&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When using a Human Shield, the Grappler gains a Full Cover bonus. When the Grappler is acting as a Human Shield, the target gains a Full Cover bonus. &lt;br /&gt;
&lt;br /&gt;
Any Attacks made against the &#039;&#039;&#039;Shielded&#039;&#039;&#039; character will require a roll from the Human Shield as well. Regardless of the Shielded character’s success or failure, if the Human Shield fails their Defense roll, they take the attack’s damage as well.&lt;br /&gt;
=== Focusing ===&lt;br /&gt;
Focusing for a turn or starting combat while Focused gives Advantage to a Ranged Attack made on the next turn. If you do not fire after aiming, this bonus is lost unless Aiming is maintained.&lt;br /&gt;
&lt;br /&gt;
Focusing can be done from cover. Becoming engaged or taking melee damage while Focused “breaks” your focus. If a ranged attack is made against a Focused character, that character must make a DC 12 Resolve roll to remain focused.&lt;br /&gt;
&lt;br /&gt;
Focus is maintained so long as the character does not move or otherwise have their Focus broken. Once broken, Focus needs to be done once more.&lt;br /&gt;
=== Reloading ===&lt;br /&gt;
Once your available ammunition (clip, magazine, etc.) is spent, players using a ranged weapon must make a roll to Reload (DC 10). On success, their Reload is instant, allowing them to take a normal action. On failure, a turn must be dedicated to Reloading their weapon. &lt;br /&gt;
=== Suppressive Fire ===&lt;br /&gt;
With a fully automatic weapon aimed at a player behind cover, attacking players can lay down Suppressive Fire. More than half of the weapon’s magazine must remain.&lt;br /&gt;
&lt;br /&gt;
Suppressive Fire requires a typical Ranged ATK vs. Ranged DEF roll. On the attacker’s success, typical damage applies. &lt;br /&gt;
&lt;br /&gt;
Regardless of success or failure, the attacking player burns their magazine of their automatic weapon to attempt to negate movement or action from the defending player. If a defender attempts to move or act in a way that would expose them to Suppressive Fire, they must make a Resolve (DC 12) roll. On success, they may take their action as normal. On failure, they are startled in place. A target under Suppressive Fire may take any other action or reaction that would not expose them to suppressive fire. &lt;br /&gt;
&lt;br /&gt;
Suppressive Fire ends at the beginning of the attacker’s next turn. At the end of Suppressive Fire, the attacking player must reload to continue using their currently equipped weapon.&lt;br /&gt;
= Armor =&lt;br /&gt;
Armor is applied as Light, Medium, or Heavy armor. Each type of armor has its own Armor Stacks and Drawbacks. &lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Armor Rules &lt;br /&gt;
|-&lt;br /&gt;
| Type || Stacks || Drawbacks&lt;br /&gt;
|-&lt;br /&gt;
| Light || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Medium || 2 || -1 Initiative, -1 Movement&lt;br /&gt;
|-&lt;br /&gt;
| Heavy || 3 || -2 Initiative, -2 Movement&lt;br /&gt;
|}&lt;br /&gt;
If an attack is taken while wearing armor, a stack is consumed to reduce the damage of that attack to 1. Critical attacks deal 2 stacks of damage to armor and deal full damage if only one Armor Stack is remaining.&lt;br /&gt;
= Ranged Combat =&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
| Ranged Defense || -&lt;br /&gt;
|-&lt;br /&gt;
| Ranged Attack || Aiming/2&lt;br /&gt;
|}&lt;br /&gt;
Several aspects of the mechanical environment affect ranged rolls, including the lighting of an area, whether or not a weapon is scoped, and more. Please note that Sniping a character from a distance, while an option, is still subject to appropriate combat rules, CK rules, and ticketing. The range of a ranged weapon is dictated by in-game line-of-sight (LoS). Consider the following before making an attack roll:&lt;br /&gt;
* Whether the target is ‘visible’ &lt;br /&gt;
* Whether there is a clear line of fire from attacker to defender&lt;br /&gt;
* Whether any allies are engaged in any actions that may put them at risk of being hit (i.e. grappling)&lt;br /&gt;
The above factors determine whether a target can be hit. In the special case of the flamethrower, its range is 15 in-game tiles.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Ranged Weapons&lt;br /&gt;
! Weapon Type !! Damage !! Crit Requirement !! Crit Dmg&lt;br /&gt;
|-&lt;br /&gt;
| Assault Weapons || 4 || Double 5/Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| M60/LMG&#039;s|| 5 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Rifles &amp;amp; Shotguns || 4 || Double 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
| Pistols || 3 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Flamethrowers &amp;lt;ref&amp;gt;Flamethrower Crit Rule: If a character holding a Flamethrower is hit by a Critical Hit, there&#039;s a 50% chance (1-2-3 on a d6 roll) that the fuel canister explodes. Dealing 2 Damage to the wielder.&amp;lt;/ref&amp;gt; &amp;lt;ref&amp;gt;Flamethrower Cover Rules: Engaged Condition and a 50% body coverage behind a tile don&#039;t offer Cover from a Flamethrower attack. If a Flamethrower attack targets a character engaged with other characters, all of them will be hit by the attack and roll Ranged Defence. Only a coverage of at least 80% behind a Tile grants the Cover modifier from a Flamethrower Attack. (ex: Only a limb/head visible)&amp;lt;/ref&amp;gt; || 3 || Double 5 or Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Crossbows || 4 || Double 6 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Melee Combat =&lt;br /&gt;
Melee combat occurs with contested rolls. The success of a melee attack roll is dependent on the following values:&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Melee Attack !! ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|-&lt;br /&gt;
| Melee Defense|| ½ Weapon Skill (or ½ Nimble if Unarmed) + STR Bonus (+1 at lvl 8, +2 at lvl 10)&lt;br /&gt;
|}&lt;br /&gt;
Each Melee weapon has its own damage and crit requirements.&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ Throwable Examples&lt;br /&gt;
! Weapon Type || Damage || Crit Requirement || Crit Damage&lt;br /&gt;
|-&lt;br /&gt;
| Two-Handed|| 3 || Double 5 or Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| One-Handed &amp;lt;ref&amp;gt;One-Handed Double-Tap: Characters utilizing one-handed weapons can forgo their movement to make a second attack at disadvantage. This attack cannot crit.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 4&lt;br /&gt;
|-&lt;br /&gt;
| Spears &amp;lt;ref&amp;gt;Spear Range Rule: A character holding a spear engages hostile characters within 2 Tiles instead of 1.&amp;lt;/ref&amp;gt;|| 2 || Double 6 || 3&lt;br /&gt;
|-&lt;br /&gt;
| Bare Hands, Improvised, Weapon Stocks|| 1 || - || -&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=420</id>
		<title>Template:Character Creation</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=420"/>
		<updated>2025-11-10T18:08:46Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* 📖 Vanilla Negative Traits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Character Creation==&lt;br /&gt;
Characters on NWO are supposed to feel like real people, and each character build will first need to be approved before the character can Spawn.&lt;br /&gt;
Using this guide, at the end of a Character Application thread, post what kind of Occupation and Traits the character should have. Every Trait will need to be justified properly in the Background. Both Negatives, and Positives.&lt;br /&gt;
&lt;br /&gt;
==Skill Caps &amp;amp; Traits==&lt;br /&gt;
On NWO, every skill has a max cap level. Caps are influenced by the starting level in character creation, and will be 4, 6, 8 and 10. A skill that starts with at least one level will already be at its maximum possible level.&lt;br /&gt;
&lt;br /&gt;
===Gaining Levels &amp;amp; XP===&lt;br /&gt;
If a character starts with 0 levels in a skill, they will be able to level it up to Level 4. Leveling these skills happens through normal PZ Mechanical Leveling up.&lt;br /&gt;
&lt;br /&gt;
===Skill Traits===&lt;br /&gt;
Since Backgrounds will not give any starting level in Crafting, Agility, Survival and Combat skills, a character build will mostly come from the selection of traits. Our custom Skill Traits are organized in three different tiers that have different costs and Starting levels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Examples&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Point Cost !! Points Added !! Starting Level !! Skill Cap !! Limit&lt;br /&gt;
|-&lt;br /&gt;
| None || 0 || +0 Levels || Level 0 || Level 4 || None &lt;br /&gt;
|-&lt;br /&gt;
| 🥉 Amateur || 1 || +1 Levels || Level 6 || Level 6 || None&lt;br /&gt;
|-&lt;br /&gt;
| 🥈 Experienced || 2 || +2 Levels || Level 8 || Level 8 || Two Per Character&lt;br /&gt;
|-&lt;br /&gt;
| 🥇 Expert || 3 || +3 Levels || Level 10 || Level 10 || One Per Character&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A Character can&#039;t also have same Skill Traits of different Tiers. (For example: Both Amateur Carpenter *and* Experienced Carpenter).&lt;br /&gt;
&lt;br /&gt;
==Traits List &amp;amp; Costs==&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Positive Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Speed Demon || 1 || +1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Low Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Low Hunger || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Herbalist || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Iron Gut || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Keen Hearing || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Dexterous || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Organized || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Cats Eyes || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Adrenaline Junkie || 2 || +1 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Graceful || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Brave || 2 || +2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Outdoorsman || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Inconspicuous || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Fast Healer || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fast Learner || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Fast Reader || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Resilient || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Thick Skinned || 2 || +2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fit || 2 || Level 8 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Strong || 2 || Level 8 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Fit || 4 || Level 10 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Very Strong || 4 || Level 10 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Eagle Eyed || 3 || +1 Ranged Attack, +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Desensitized || 3 || +3 Resolve&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Negative Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Prone to Illness || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Smoker || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Sunday Driver || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Learner || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Reader || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Healer || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Weak Stomach || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Fear of Blood || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Agoraphobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| All Thumbs || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Claustrophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Cowardly || 2 || -2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Pacifist || 3 || -2 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Short Sighted || 2 || -1 Ranged Attack, -1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Thin Skinned || 2 || -2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Conspicuous || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Clumsy || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Hard of Hearing || 2 || -2 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Disorganized || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Hearty Appetite || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| High Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak || 2 || 4 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Unfit || 2 || 4 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Illiterate || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Deaf || 4 || -4 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Very Weak || 4 || 2 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Unfit || 4 || 2 Fitness&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥉 Amateur Skill Traits===&lt;br /&gt;
Cost: 1 Point each. &#039;&#039;&#039;No Limit.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Tailor || +1 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Doctor || +1 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Mechanic|| +1 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Electrician || +1 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Metalworker || +1 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Homesteader || +1 Cooking, +1 Farming, +1 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Shooter || +1 Aiming, +1 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Outdoorsman || +1 Fishing, +1 Foraging, +1 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Athlete || +1 Nimble, +1 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Rogue || +1 Sneaking, +1 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Survival) || +1 Axe, +1 Spear, +1 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blunt) || +1 Long Blunt, +1 Short Blunt, +1 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blade) || +1 Long Blade, +1 Short Blade, +1 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥈  Experienced Skill Traits===&lt;br /&gt;
Cost: 2 Points each. &#039;&#039;&#039;Max 2.&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Tailor || +2 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Doctor || +2 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Mechanic || +2 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Electrician || +2 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Metalworker || +2 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Homesteader || +2 Cooking, +2 Farming, +2 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Shooter || +2 Aiming, +2 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Outdoorsman || +2 Fishing, +2 Foraging, +2 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Athlete || +2 Nimble, +2 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Rogue || +2 Sneaking, +2 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Survival) || +2 Axe, +2 Spear, +2 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blunt) || +2 Long Blunt, +2 Short Blunt, +2 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blade) || +2 Long Blade, +2 Short Blade, +2 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥇  Expert Skill Traits===&lt;br /&gt;
Cost: 3 Points each. &#039;&#039;&#039;Max 1.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Expert Tailor || +3 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Expert Doctor || +3 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Expert Mechanic || +3 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Expert Electrician || +3 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Expert Metalworker || +3 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Expert Homesteader || +3 Cooking, +3 Farming, +3 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Expert Shooter || +3 Aiming, +3 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Expert Outdoorsman || +3 Fishing, +3 Foraging, +3 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Expert Athlete || +3 Nimble, +3 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Expert Rogue || +3 Sneaking, +3 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Survival) || +3 Axe, +3 Spear, +3 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blunt) || +3 Long Blunt, +3 Short Blunt, +3 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blade) || +3 Long Blade, +3 Short Blade, +3 Maintenance&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=419</id>
		<title>Template:Character Creation</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=419"/>
		<updated>2025-11-10T18:01:48Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* 📖 Vanilla Positive Traits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Character Creation==&lt;br /&gt;
Characters on NWO are supposed to feel like real people, and each character build will first need to be approved before the character can Spawn.&lt;br /&gt;
Using this guide, at the end of a Character Application thread, post what kind of Occupation and Traits the character should have. Every Trait will need to be justified properly in the Background. Both Negatives, and Positives.&lt;br /&gt;
&lt;br /&gt;
==Skill Caps &amp;amp; Traits==&lt;br /&gt;
On NWO, every skill has a max cap level. Caps are influenced by the starting level in character creation, and will be 4, 6, 8 and 10. A skill that starts with at least one level will already be at its maximum possible level.&lt;br /&gt;
&lt;br /&gt;
===Gaining Levels &amp;amp; XP===&lt;br /&gt;
If a character starts with 0 levels in a skill, they will be able to level it up to Level 4. Leveling these skills happens through normal PZ Mechanical Leveling up.&lt;br /&gt;
&lt;br /&gt;
===Skill Traits===&lt;br /&gt;
Since Backgrounds will not give any starting level in Crafting, Agility, Survival and Combat skills, a character build will mostly come from the selection of traits. Our custom Skill Traits are organized in three different tiers that have different costs and Starting levels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Examples&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Point Cost !! Points Added !! Starting Level !! Skill Cap !! Limit&lt;br /&gt;
|-&lt;br /&gt;
| None || 0 || +0 Levels || Level 0 || Level 4 || None &lt;br /&gt;
|-&lt;br /&gt;
| 🥉 Amateur || 1 || +1 Levels || Level 6 || Level 6 || None&lt;br /&gt;
|-&lt;br /&gt;
| 🥈 Experienced || 2 || +2 Levels || Level 8 || Level 8 || Two Per Character&lt;br /&gt;
|-&lt;br /&gt;
| 🥇 Expert || 3 || +3 Levels || Level 10 || Level 10 || One Per Character&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A Character can&#039;t also have same Skill Traits of different Tiers. (For example: Both Amateur Carpenter *and* Experienced Carpenter).&lt;br /&gt;
&lt;br /&gt;
==Traits List &amp;amp; Costs==&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Positive Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Speed Demon || 1 || +1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Low Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Low Hunger || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Herbalist || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Iron Gut || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Keen Hearing || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Dexterous || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Organized || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Cats Eyes || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Adrenaline Junkie || 2 || +1 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Graceful || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Brave || 2 || +2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Outdoorsman || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Inconspicuous || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Fast Healer || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fast Learner || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Fast Reader || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Resilient || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Thick Skinned || 2 || +2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fit || 2 || Level 8 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Strong || 2 || Level 8 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Fit || 4 || Level 10 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Very Strong || 4 || Level 10 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Eagle Eyed || 3 || +1 Ranged Attack, +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Desensitized || 3 || +3 Resolve&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Negative Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Prone to Illness || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Smoker || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Slow Driver || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Learner || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak Stomach || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Hemophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Agoraphobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| All Thumbs || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Claustrophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Cowardly || 2 || -2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Pacifist || 3 || -2 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Short Sighted || 2 || -1 Ranged Attack, -1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Thin Skinned || 2 || -2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Conspicuous || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Clumsy || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Hard of Hearing || 2 || -2 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Disorganized || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak || 2 || 4 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Unfit || 2 || 4 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Illiterate || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Deaf || 4 || -4 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Very Weak || 3 || 2 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Unfit || 3 || 2 Fitness&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥉 Amateur Skill Traits===&lt;br /&gt;
Cost: 1 Point each. &#039;&#039;&#039;No Limit.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Tailor || +1 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Doctor || +1 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Mechanic|| +1 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Electrician || +1 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Metalworker || +1 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Homesteader || +1 Cooking, +1 Farming, +1 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Shooter || +1 Aiming, +1 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Outdoorsman || +1 Fishing, +1 Foraging, +1 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Athlete || +1 Nimble, +1 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Rogue || +1 Sneaking, +1 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Survival) || +1 Axe, +1 Spear, +1 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blunt) || +1 Long Blunt, +1 Short Blunt, +1 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blade) || +1 Long Blade, +1 Short Blade, +1 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥈  Experienced Skill Traits===&lt;br /&gt;
Cost: 2 Points each. &#039;&#039;&#039;Max 2.&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Tailor || +2 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Doctor || +2 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Mechanic || +2 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Electrician || +2 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Metalworker || +2 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Homesteader || +2 Cooking, +2 Farming, +2 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Shooter || +2 Aiming, +2 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Outdoorsman || +2 Fishing, +2 Foraging, +2 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Athlete || +2 Nimble, +2 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Rogue || +2 Sneaking, +2 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Survival) || +2 Axe, +2 Spear, +2 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blunt) || +2 Long Blunt, +2 Short Blunt, +2 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blade) || +2 Long Blade, +2 Short Blade, +2 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥇  Expert Skill Traits===&lt;br /&gt;
Cost: 3 Points each. &#039;&#039;&#039;Max 1.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Expert Tailor || +3 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Expert Doctor || +3 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Expert Mechanic || +3 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Expert Electrician || +3 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Expert Metalworker || +3 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Expert Homesteader || +3 Cooking, +3 Farming, +3 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Expert Shooter || +3 Aiming, +3 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Expert Outdoorsman || +3 Fishing, +3 Foraging, +3 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Expert Athlete || +3 Nimble, +3 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Expert Rogue || +3 Sneaking, +3 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Survival) || +3 Axe, +3 Spear, +3 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blunt) || +3 Long Blunt, +3 Short Blunt, +3 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blade) || +3 Long Blade, +3 Short Blade, +3 Maintenance&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=418</id>
		<title>Template:Character Creation</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=418"/>
		<updated>2025-11-10T17:33:26Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* 🥇  Expert Skill Traits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Character Creation==&lt;br /&gt;
Characters on NWO are supposed to feel like real people, and each character build will first need to be approved before the character can Spawn.&lt;br /&gt;
Using this guide, at the end of a Character Application thread, post what kind of Occupation and Traits the character should have. Every Trait will need to be justified properly in the Background. Both Negatives, and Positives.&lt;br /&gt;
&lt;br /&gt;
==Skill Caps &amp;amp; Traits==&lt;br /&gt;
On NWO, every skill has a max cap level. Caps are influenced by the starting level in character creation, and will be 4, 6, 8 and 10. A skill that starts with at least one level will already be at its maximum possible level.&lt;br /&gt;
&lt;br /&gt;
===Gaining Levels &amp;amp; XP===&lt;br /&gt;
If a character starts with 0 levels in a skill, they will be able to level it up to Level 4. Leveling these skills happens through normal PZ Mechanical Leveling up.&lt;br /&gt;
&lt;br /&gt;
===Skill Traits===&lt;br /&gt;
Since Backgrounds will not give any starting level in Crafting, Agility, Survival and Combat skills, a character build will mostly come from the selection of traits. Our custom Skill Traits are organized in three different tiers that have different costs and Starting levels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Examples&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Point Cost !! Points Added !! Starting Level !! Skill Cap !! Limit&lt;br /&gt;
|-&lt;br /&gt;
| None || 0 || +0 Levels || Level 0 || Level 4 || None &lt;br /&gt;
|-&lt;br /&gt;
| 🥉 Amateur || 1 || +1 Levels || Level 6 || Level 6 || None&lt;br /&gt;
|-&lt;br /&gt;
| 🥈 Experienced || 2 || +2 Levels || Level 8 || Level 8 || Two Per Character&lt;br /&gt;
|-&lt;br /&gt;
| 🥇 Expert || 3 || +3 Levels || Level 10 || Level 10 || One Per Character&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A Character can&#039;t also have same Skill Traits of different Tiers. (For example: Both Amateur Carpenter *and* Experienced Carpenter).&lt;br /&gt;
&lt;br /&gt;
==Traits List &amp;amp; Costs==&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Positive Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Speed Demon || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Low Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Herbalist || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Iron Gut || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Cats Eyes || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Adrenaline Junkie || 2 || +1 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Graceful || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Brave || 2 || +2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Inconspicuous || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Fast Healer || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Resilient || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fit || 2 || Level 8 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Strong || 2 || Level 8 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Eagle Eyed || 3 || +1 Ranged Attack, +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Desensitized || 3 || +3 Resolve&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Negative Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Prone to Illness || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Smoker || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Slow Driver || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Learner || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak Stomach || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Hemophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Agoraphobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| All Thumbs || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Claustrophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Cowardly || 2 || -2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Pacifist || 3 || -2 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Short Sighted || 2 || -1 Ranged Attack, -1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Thin Skinned || 2 || -2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Conspicuous || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Clumsy || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Hard of Hearing || 2 || -2 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Disorganized || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak || 2 || 4 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Unfit || 2 || 4 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Illiterate || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Deaf || 4 || -4 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Very Weak || 3 || 2 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Unfit || 3 || 2 Fitness&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥉 Amateur Skill Traits===&lt;br /&gt;
Cost: 1 Point each. &#039;&#039;&#039;No Limit.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Tailor || +1 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Doctor || +1 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Mechanic|| +1 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Electrician || +1 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Metalworker || +1 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Homesteader || +1 Cooking, +1 Farming, +1 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Shooter || +1 Aiming, +1 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Outdoorsman || +1 Fishing, +1 Foraging, +1 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Athlete || +1 Nimble, +1 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Rogue || +1 Sneaking, +1 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Survival) || +1 Axe, +1 Spear, +1 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blunt) || +1 Long Blunt, +1 Short Blunt, +1 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blade) || +1 Long Blade, +1 Short Blade, +1 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥈  Experienced Skill Traits===&lt;br /&gt;
Cost: 2 Points each. &#039;&#039;&#039;Max 2.&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Tailor || +2 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Doctor || +2 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Mechanic || +2 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Electrician || +2 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Metalworker || +2 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Homesteader || +2 Cooking, +2 Farming, +2 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Shooter || +2 Aiming, +2 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Outdoorsman || +2 Fishing, +2 Foraging, +2 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Athlete || +2 Nimble, +2 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Rogue || +2 Sneaking, +2 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Survival) || +2 Axe, +2 Spear, +2 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blunt) || +2 Long Blunt, +2 Short Blunt, +2 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blade) || +2 Long Blade, +2 Short Blade, +2 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥇  Expert Skill Traits===&lt;br /&gt;
Cost: 3 Points each. &#039;&#039;&#039;Max 1.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Expert Tailor || +3 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Expert Doctor || +3 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Expert Mechanic || +3 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Expert Electrician || +3 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Expert Metalworker || +3 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Expert Homesteader || +3 Cooking, +3 Farming, +3 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Expert Shooter || +3 Aiming, +3 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Expert Outdoorsman || +3 Fishing, +3 Foraging, +3 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Expert Athlete || +3 Nimble, +3 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Expert Rogue || +3 Sneaking, +3 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Survival) || +3 Axe, +3 Spear, +3 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blunt) || +3 Long Blunt, +3 Short Blunt, +3 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blade) || +3 Long Blade, +3 Short Blade, +3 Maintenance&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=417</id>
		<title>Template:Character Creation</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=417"/>
		<updated>2025-11-10T17:32:54Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* 🥈  Experienced Skill Traits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Character Creation==&lt;br /&gt;
Characters on NWO are supposed to feel like real people, and each character build will first need to be approved before the character can Spawn.&lt;br /&gt;
Using this guide, at the end of a Character Application thread, post what kind of Occupation and Traits the character should have. Every Trait will need to be justified properly in the Background. Both Negatives, and Positives.&lt;br /&gt;
&lt;br /&gt;
==Skill Caps &amp;amp; Traits==&lt;br /&gt;
On NWO, every skill has a max cap level. Caps are influenced by the starting level in character creation, and will be 4, 6, 8 and 10. A skill that starts with at least one level will already be at its maximum possible level.&lt;br /&gt;
&lt;br /&gt;
===Gaining Levels &amp;amp; XP===&lt;br /&gt;
If a character starts with 0 levels in a skill, they will be able to level it up to Level 4. Leveling these skills happens through normal PZ Mechanical Leveling up.&lt;br /&gt;
&lt;br /&gt;
===Skill Traits===&lt;br /&gt;
Since Backgrounds will not give any starting level in Crafting, Agility, Survival and Combat skills, a character build will mostly come from the selection of traits. Our custom Skill Traits are organized in three different tiers that have different costs and Starting levels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Examples&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Point Cost !! Points Added !! Starting Level !! Skill Cap !! Limit&lt;br /&gt;
|-&lt;br /&gt;
| None || 0 || +0 Levels || Level 0 || Level 4 || None &lt;br /&gt;
|-&lt;br /&gt;
| 🥉 Amateur || 1 || +1 Levels || Level 6 || Level 6 || None&lt;br /&gt;
|-&lt;br /&gt;
| 🥈 Experienced || 2 || +2 Levels || Level 8 || Level 8 || Two Per Character&lt;br /&gt;
|-&lt;br /&gt;
| 🥇 Expert || 3 || +3 Levels || Level 10 || Level 10 || One Per Character&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A Character can&#039;t also have same Skill Traits of different Tiers. (For example: Both Amateur Carpenter *and* Experienced Carpenter).&lt;br /&gt;
&lt;br /&gt;
==Traits List &amp;amp; Costs==&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Positive Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Speed Demon || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Low Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Herbalist || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Iron Gut || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Cats Eyes || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Adrenaline Junkie || 2 || +1 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Graceful || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Brave || 2 || +2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Inconspicuous || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Fast Healer || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Resilient || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fit || 2 || Level 8 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Strong || 2 || Level 8 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Eagle Eyed || 3 || +1 Ranged Attack, +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Desensitized || 3 || +3 Resolve&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Negative Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Prone to Illness || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Smoker || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Slow Driver || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Learner || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak Stomach || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Hemophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Agoraphobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| All Thumbs || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Claustrophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Cowardly || 2 || -2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Pacifist || 3 || -2 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Short Sighted || 2 || -1 Ranged Attack, -1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Thin Skinned || 2 || -2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Conspicuous || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Clumsy || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Hard of Hearing || 2 || -2 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Disorganized || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak || 2 || 4 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Unfit || 2 || 4 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Illiterate || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Deaf || 4 || -4 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Very Weak || 3 || 2 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Unfit || 3 || 2 Fitness&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥉 Amateur Skill Traits===&lt;br /&gt;
Cost: 1 Point each. &#039;&#039;&#039;No Limit.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Tailor || +1 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Doctor || +1 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Mechanic|| +1 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Electrician || +1 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Metalworker || +1 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Homesteader || +1 Cooking, +1 Farming, +1 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Shooter || +1 Aiming, +1 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Outdoorsman || +1 Fishing, +1 Foraging, +1 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Athlete || +1 Nimble, +1 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Rogue || +1 Sneaking, +1 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Survival) || +1 Axe, +1 Spear, +1 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blunt) || +1 Long Blunt, +1 Short Blunt, +1 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blade) || +1 Long Blade, +1 Short Blade, +1 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥈  Experienced Skill Traits===&lt;br /&gt;
Cost: 2 Points each. &#039;&#039;&#039;Max 2.&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Tailor || +2 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Doctor || +2 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Mechanic || +2 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Electrician || +2 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Metalworker || +2 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Homesteader || +2 Cooking, +2 Farming, +2 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Shooter || +2 Aiming, +2 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Outdoorsman || +2 Fishing, +2 Foraging, +2 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Athlete || +2 Nimble, +2 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Rogue || +2 Sneaking, +2 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Survival) || +2 Axe, +2 Spear, +2 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blunt) || +2 Long Blunt, +2 Short Blunt, +2 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blade) || +2 Long Blade, +2 Short Blade, +2 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥇  Expert Skill Traits===&lt;br /&gt;
Cost: 3 Points each. &#039;&#039;&#039;Max 1.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Expert Carpenter || +3 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Expert Tailor || +3 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Expert Doctor || +3 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Expert Mechanic || +3 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Expert Electrician || +3 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Expert Metalworker || +3 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Expert Homesteader || +3 Cooking, +3 Farming&lt;br /&gt;
|-&lt;br /&gt;
| Expert Shooter || +3 Aiming, +3 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Expert Outdoorsman || +3 Fishing, +3 Foraging, +3 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Expert Athlete || +3 Nimble, +3 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Expert Rogue || +3 Sneaking, +3 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Survival) || +3 Axe, +3 Spear, +3 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blunt) || +3 Long Blunt, +3 Short Blunt, +3 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blade) || +3 Long Blade, +3 Short Blade, +3 Maintenance&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=416</id>
		<title>Template:Character Creation</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=Template:Character_Creation&amp;diff=416"/>
		<updated>2025-11-10T17:32:32Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: /* 🥉 Amateur Skill Traits */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Character Creation==&lt;br /&gt;
Characters on NWO are supposed to feel like real people, and each character build will first need to be approved before the character can Spawn.&lt;br /&gt;
Using this guide, at the end of a Character Application thread, post what kind of Occupation and Traits the character should have. Every Trait will need to be justified properly in the Background. Both Negatives, and Positives.&lt;br /&gt;
&lt;br /&gt;
==Skill Caps &amp;amp; Traits==&lt;br /&gt;
On NWO, every skill has a max cap level. Caps are influenced by the starting level in character creation, and will be 4, 6, 8 and 10. A skill that starts with at least one level will already be at its maximum possible level.&lt;br /&gt;
&lt;br /&gt;
===Gaining Levels &amp;amp; XP===&lt;br /&gt;
If a character starts with 0 levels in a skill, they will be able to level it up to Level 4. Leveling these skills happens through normal PZ Mechanical Leveling up.&lt;br /&gt;
&lt;br /&gt;
===Skill Traits===&lt;br /&gt;
Since Backgrounds will not give any starting level in Crafting, Agility, Survival and Combat skills, a character build will mostly come from the selection of traits. Our custom Skill Traits are organized in three different tiers that have different costs and Starting levels:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+ Examples&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Point Cost !! Points Added !! Starting Level !! Skill Cap !! Limit&lt;br /&gt;
|-&lt;br /&gt;
| None || 0 || +0 Levels || Level 0 || Level 4 || None &lt;br /&gt;
|-&lt;br /&gt;
| 🥉 Amateur || 1 || +1 Levels || Level 6 || Level 6 || None&lt;br /&gt;
|-&lt;br /&gt;
| 🥈 Experienced || 2 || +2 Levels || Level 8 || Level 8 || Two Per Character&lt;br /&gt;
|-&lt;br /&gt;
| 🥇 Expert || 3 || +3 Levels || Level 10 || Level 10 || One Per Character&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A Character can&#039;t also have same Skill Traits of different Tiers. (For example: Both Amateur Carpenter *and* Experienced Carpenter).&lt;br /&gt;
&lt;br /&gt;
==Traits List &amp;amp; Costs==&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Positive Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Speed Demon || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Low Thirst || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Herbalist || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Iron Gut || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Cats Eyes || 2 || +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Adrenaline Junkie || 2 || +1 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Graceful || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Brave || 2 || +2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Inconspicuous || 1 || +1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Fast Healer || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Resilient || 2 || +1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Fit || 2 || Level 8 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Strong || 2 || Level 8 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Eagle Eyed || 3 || +1 Ranged Attack, +1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Desensitized || 3 || +3 Resolve&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===📖 Vanilla Negative Traits===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
|-&lt;br /&gt;
! Name !! Cost !! Dice Bonus&lt;br /&gt;
|-&lt;br /&gt;
| Prone to Illness || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Smoker || 1 || -1 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Slow Driver || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Slow Learner || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak Stomach || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Hemophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Agoraphobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| All Thumbs || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Claustrophobic || 1 || -1 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Cowardly || 2 || -2 Resolve&lt;br /&gt;
|-&lt;br /&gt;
| Pacifist || 3 || -2 Initiative&lt;br /&gt;
|-&lt;br /&gt;
| Short Sighted || 2 || -1 Ranged Attack, -1 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Thin Skinned || 2 || -2 Robustness&lt;br /&gt;
|-&lt;br /&gt;
| Conspicuous || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Clumsy || 1 || -1 Stealth&lt;br /&gt;
|-&lt;br /&gt;
| Hard of Hearing || 2 || -2 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Disorganized || 1 || -&lt;br /&gt;
|-&lt;br /&gt;
| Weak || 2 || 4 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Unfit || 2 || 4 Fitness&lt;br /&gt;
|-&lt;br /&gt;
| Illiterate || 2 || -&lt;br /&gt;
|-&lt;br /&gt;
| Deaf || 4 || -4 Perception&lt;br /&gt;
|-&lt;br /&gt;
| Very Weak || 3 || 2 Strength&lt;br /&gt;
|-&lt;br /&gt;
| Very Unfit || 3 || 2 Fitness&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥉 Amateur Skill Traits===&lt;br /&gt;
Cost: 1 Point each. &#039;&#039;&#039;No Limit.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Tailor || +1 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Doctor || +1 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Mechanic|| +1 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Electrician || +1 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Metalworker || +1 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Homesteader || +1 Cooking, +1 Farming, +1 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Shooter || +1 Aiming, +1 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Outdoorsman || +1 Fishing, +1 Foraging, +1 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Athlete || +1 Nimble, +1 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Rogue || +1 Sneaking, +1 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Survival) || +1 Axe, +1 Spear, +1 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blunt) || +1 Long Blunt, +1 Short Blunt, +1 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Amateur Fighter (Blade) || +1 Long Blade, +1 Short Blade, +1 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥈  Experienced Skill Traits===&lt;br /&gt;
Cost: 2 Points each. &#039;&#039;&#039;Max 2.&#039;&#039;&#039;&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Carpenter || +2 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Tailor || +2 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Doctor || +2 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Mechanic || +2 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Electrician || +2 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Metalworker || +2 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Homesteader || +2 Cooking, +2 Farming&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Shooter || +2 Aiming, +2 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Outdoorsman || +2 Fishing, +2 Foraging, +2 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Athlete || +2 Nimble, +2 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Rogue || +2 Sneaking, +2 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Survival) || +2 Axe, +2 Spear, +2 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blunt) || +2 Long Blunt, +2 Short Blunt, +2 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Experienced Fighter (Blade) || +2 Long Blade, +2 Short Blade, +2 Maintenance&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===🥇  Expert Skill Traits===&lt;br /&gt;
Cost: 3 Points each. &#039;&#039;&#039;Max 1.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|+ &lt;br /&gt;
|-&lt;br /&gt;
! Name !! Description&lt;br /&gt;
|-&lt;br /&gt;
| Expert Carpenter || +3 Carpentry&lt;br /&gt;
|-&lt;br /&gt;
| Expert Tailor || +3 Tailoring&lt;br /&gt;
|-&lt;br /&gt;
| Expert Doctor || +3 First Aid&lt;br /&gt;
|-&lt;br /&gt;
| Expert Mechanic || +3 Mechanics&lt;br /&gt;
|-&lt;br /&gt;
| Expert Electrician || +3 Electrical&lt;br /&gt;
|-&lt;br /&gt;
| Expert Metalworker || +3 Metalworking&lt;br /&gt;
|-&lt;br /&gt;
| Expert Homesteader || +3 Cooking, +3 Farming&lt;br /&gt;
|-&lt;br /&gt;
| Expert Shooter || +3 Aiming, +3 Reloading&lt;br /&gt;
|-&lt;br /&gt;
| Expert Outdoorsman || +3 Fishing, +3 Foraging, +3 Trapping&lt;br /&gt;
|-&lt;br /&gt;
| Expert Athlete || +3 Nimble, +3 Sprinting&lt;br /&gt;
|-&lt;br /&gt;
| Expert Rogue || +3 Sneaking, +3 Lightfooted&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Survival) || +3 Axe, +3 Spear, +3 maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blunt) || +3 Long Blunt, +3 Short Blunt, +3 Maintenance&lt;br /&gt;
|-&lt;br /&gt;
| Expert Fighter (Blade) || +3 Long Blade, +3 Short Blade, +3 Maintenance&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=415</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=415"/>
		<updated>2025-11-10T17:07:53Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller (WORK IN PROGRESS) &amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
      // ADD: Half of Nimble level to Melee Attack and Melee Defence&lt;br /&gt;
    var halfNimble = Math.floor(level / 2);&lt;br /&gt;
    ensureMetric(breakdown, &#039;Melee Attack&#039;); breakdown[&#039;Melee Attack&#039;].skills += halfNimble;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Melee Defence&#039;); breakdown[&#039;Melee Defence&#039;].skills += halfNimble;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // All skills add half their level to their own check&lt;br /&gt;
  ensureMetric(breakdown, skillName);&lt;br /&gt;
  breakdown[skillName].skills += mod;&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
      // Add Strength modifier to weapon skills&lt;br /&gt;
      var weaponSkills = [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Axe&#039;, &#039;Spear&#039;];&lt;br /&gt;
      for (i = 0; i &amp;lt; weaponSkills.length; i++) {&lt;br /&gt;
        var ws = weaponSkills[i];&lt;br /&gt;
        ensureMetric(breakdown, ws);&lt;br /&gt;
        if (str &amp;lt;= 4)       breakdown[ws].derived += -1;&lt;br /&gt;
        else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
        else if (str &amp;lt;= 8)  breakdown[ws].derived += 1;&lt;br /&gt;
        else if (str &amp;gt;= 10) breakdown[ws].derived += 2;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=414</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=414"/>
		<updated>2025-11-10T16:44:15Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller (WORK IN PROGRESS) &amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
      // ADD: Half of Nimble level to Melee Attack and Melee Defence&lt;br /&gt;
    var halfNimble = Math.floor(level / 2);&lt;br /&gt;
    ensureMetric(breakdown, &#039;Melee Attack&#039;); breakdown[&#039;Melee Attack&#039;].skills += halfNimble;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Melee Defence&#039;); breakdown[&#039;Melee Defence&#039;].skills += halfNimble;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=413</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=413"/>
		<updated>2025-11-10T16:39:56Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller (WORK IN PROGRESS) &amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  // ADD: Half of Nimble level to Melee Attack&lt;br /&gt;
  var halfNimble = Math.floor(level / 2);&lt;br /&gt;
  ensureMetric(breakdown, &#039;Melee Attack&#039;); breakdown[&#039;Melee Attack&#039;].skills += halfNimble;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=412</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=412"/>
		<updated>2025-11-10T16:34:39Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller (WORK IN PROGRESS) &amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=411</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=411"/>
		<updated>2025-11-10T16:33:17Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller (WORK IN PROGRESS) &amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
      var nim = breakdown.nimble.skills;&lt;br /&gt;
      if (nim &amp;lt;= 1)  { breakdown[&#039;Melee Attack&#039;].derived += 0;  breakdown[&#039;Melee Defence&#039;].derived += 0; }&lt;br /&gt;
      else if (nim &amp;lt;= 2)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (nim &amp;lt;= 4)  { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
      else if (nim &amp;lt;= 6)  { breakdown[&#039;Melee Attack&#039;].derived += 3;  breakdown[&#039;Melee Defence&#039;].derived += 3; }&lt;br /&gt;
      else if (nim &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 4;  breakdown[&#039;Melee Defence&#039;].derived += 4; }&lt;br /&gt;
      else if (nim = 10) { breakdown[&#039;Melee Attack&#039;].derived += 5;  breakdown[&#039;Melee Defence&#039;].derived += 5; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=410</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=410"/>
		<updated>2025-11-10T16:19:17Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller (WORK IN PROGRESS) &amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=409</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=409"/>
		<updated>2025-11-10T15:12:33Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: Undo revision 408 by Dubhleohan (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=408</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=408"/>
		<updated>2025-11-10T14:43:37Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { STR: 6, FIT: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { FIT: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { STR: 1 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: { FIT: 2 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: { STR: 2 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hemophobic&#039;,         cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { STR: -1 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { FIT: -1 } },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: { STR: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: { FIT: -2 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fit) {&lt;br /&gt;
      if (fit &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fit &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fit &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fit &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.STR;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.FIT;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base STR: &amp;lt;b&amp;gt;&#039; + bg.base.STR + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base FIT: &amp;lt;b&amp;gt;&#039; + bg.base.FIT + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base STR/FIT, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var str   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fit   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + str + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fit + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=407</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=407"/>
		<updated>2025-11-10T14:39:22Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;High Thirst&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=406</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=406"/>
		<updated>2025-11-10T14:33:45Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Brewer&#039;,             cost: 2, effects: { Brewing: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Light Eater&#039;,        cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 4, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 4, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Sunday Driver&#039;,      cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 4, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 4, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=405</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=405"/>
		<updated>2025-11-10T14:23:05Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: { Strength: 4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fear of Blood&#039;,      cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: { Fitness: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=404</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=404"/>
		<updated>2025-11-10T14:02:23Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: { Fitness: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: { Strength: 4 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hemophobic&#039;,         cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -2 } },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: { Strength: -4 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: { Fitness: -4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=403</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=403"/>
		<updated>2025-11-10T14:00:29Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { Strength: 6, Fitness: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 1 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hemophobic&#039;,         cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -1 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -1 } },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: { Fitness: -2 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fitness) {&lt;br /&gt;
      if (fitness &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fitness &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fitness &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fitness &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.Strength;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.Fitness;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Strength: &amp;lt;b&amp;gt;&#039; + bg.base.Strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base Fitness: &amp;lt;b&amp;gt;&#039; + bg.base.Fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base Strength/Fitness, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var strength   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fitness   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + strength + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fitness + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=402</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=402"/>
		<updated>2025-11-10T04:52:28Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { STR: 6, FIT: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { FIT: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { STR: 1 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: { FIT: 2 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: { STR: 2 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hemophobic&#039;,         cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { STR: -1 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { FIT: -1 } },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: { STR: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: { FIT: -2 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fit) {&lt;br /&gt;
      if (fit &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fit &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fit &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fit &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.STR;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.FIT;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base STR: &amp;lt;b&amp;gt;&#039; + bg.base.STR + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base FIT: &amp;lt;b&amp;gt;&#039; + bg.base.FIT + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base STR/FIT, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var str   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fit   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + str + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fit + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=401</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=401"/>
		<updated>2025-11-10T04:42:32Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { STR: 6, FIT: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { FIT: 2 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { STR: 2 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: { FIT: 4 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: { STR: 4 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hemophobic&#039;,         cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { STR: -2 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { FIT: -2 } },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: { STR: -4 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: { FIT: -4 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fit) {&lt;br /&gt;
      if (fit &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fit &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fit &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fit &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.STR;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.FIT;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base STR: &amp;lt;b&amp;gt;&#039; + bg.base.STR + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base FIT: &amp;lt;b&amp;gt;&#039; + bg.base.FIT + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base STR/FIT, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var str   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fit   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + str + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fit + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=400</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=400"/>
		<updated>2025-11-10T04:41:14Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { STR: 6, FIT: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { FIT: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { STR: 1 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: { FIT: 2 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: { STR: 2 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hemophobic&#039;,         cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { STR: -1 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { FIT: -1 } },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: { STR: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: { FIT: -2 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fit) {&lt;br /&gt;
      if (fit &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fit &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fit &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fit &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.STR;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.FIT;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base STR: &amp;lt;b&amp;gt;&#039; + bg.base.STR + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base FIT: &amp;lt;b&amp;gt;&#039; + bg.base.FIT + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base STR/FIT, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var str   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fit   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + str + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fit + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=399</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=399"/>
		<updated>2025-11-10T04:33:58Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { STR: 6, FIT: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: { Fitness: 1 } },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: { Strength: 1 } },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: { Fitness: 2 } },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: { Strength: 2 } },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hemophobic&#039;,         cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: { Strength: -1 } },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: { Fitness: -1 } },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: { Strength: -2 } },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: { Fitness: -2 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fit) {&lt;br /&gt;
      if (fit &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fit &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fit &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fit &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.STR;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.FIT;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base STR: &amp;lt;b&amp;gt;&#039; + bg.base.STR + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base FIT: &amp;lt;b&amp;gt;&#039; + bg.base.FIT + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base STR/FIT, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var str   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fit   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + str + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fit + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
	<entry>
		<id>https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=398</id>
		<title>MediaWiki:Gadget-NWOBuilder.js</title>
		<link rel="alternate" type="text/html" href="https://www.no-way-out.com/index.php?title=MediaWiki:Gadget-NWOBuilder.js&amp;diff=398"/>
		<updated>2025-11-10T04:31:51Z</updated>

		<summary type="html">&lt;p&gt;Dubhleohan: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;// NWO Character Builder gadget – mounts into &amp;lt;div id=&amp;quot;nwo-builder&amp;quot;&amp;gt;&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
  function shouldRun($root) {&lt;br /&gt;
    if ($root &amp;amp;&amp;amp; $root.length) return true;&lt;br /&gt;
    return false;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  function mount() {&lt;br /&gt;
    var $root = $(&#039;#nwo-builder&#039;).first();&lt;br /&gt;
    if (!$root.length || $root.data(&#039;mounted&#039;)) return;&lt;br /&gt;
    if (!shouldRun($root)) return;&lt;br /&gt;
&lt;br /&gt;
    $root.addClass(&#039;nwo-builder&#039;).data(&#039;mounted&#039;, true);&lt;br /&gt;
&lt;br /&gt;
    $root.html(&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;h1&amp;gt;NWO Character Builder (2d6)&amp;lt;/h1&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;muted&amp;quot;&amp;gt;Pick a Background, add Traits &amp;amp; Skill Traits, then test 2d6 rolls with the live modifier. Built for the No Way Out dice rules.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;1) Background, Trait Points &amp;amp; Selections&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;bg-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;2) 2d6 Test Roller&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;roller-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;grid&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;3) Traits &amp;amp; Skills&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;traits-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;card&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;hd&amp;quot;&amp;gt;4) Build Summary &amp;amp; Modifiers&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;bd&amp;quot; id=&amp;quot;summary-area&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;p class=&amp;quot;kudos&amp;quot;&amp;gt;Rules referenced from the NWO &amp;lt;em&amp;gt;Dice Guide&amp;lt;/em&amp;gt;. &#039; +&lt;br /&gt;
        &#039;&amp;lt;a href=&amp;quot;/wiki/Dice_Guide&amp;quot; style=&amp;quot;color:var(--accent)&amp;quot;&amp;gt;See the Dice Guide&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;&#039; +&lt;br /&gt;
      &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
    );&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       DATA (from your spec)&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var BACKGROUNDS = {&lt;br /&gt;
      Survivor: {&lt;br /&gt;
        traitPoints: 12, base: { STR: 6, FIT: 6 },&lt;br /&gt;
        freeTraits: [&lt;br /&gt;
        ]&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var POSITIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Organized&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Speed Demon&#039;,        cost: 1, effects: { Resolve: 1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Herbalist&#039;,          cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Outdoorsman&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Iron Gut&#039;,           cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Cats Eyes&#039;,          cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Adrenaline Junkie&#039;,  cost: 2, effects: { Initiative: 1 } },&lt;br /&gt;
      { name: &#039;Graceful&#039;,           cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Brave&#039;,              cost: 2, effects: { Resolve: 2 } },&lt;br /&gt;
      { name: &#039;Inconspicuous&#039;,      cost: 1, effects: { Hiding: 1 } },&lt;br /&gt;
      { name: &#039;Fast Healer&#039;,        cost: 2, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fast Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Fast Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Low Hunger&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Keen Hearing&#039;,       cost: 2, effects: { Perception: 1 } },&lt;br /&gt;
      { name: &#039;Resilient&#039;,          cost: 1, effects: { Robustness: 1 } },&lt;br /&gt;
      { name: &#039;Fit&#039;,                cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Strong&#039;,             cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Very Fit&#039;,           cost: 3, effects: {} },&lt;br /&gt;
      { name: &#039;Very Strong&#039;,        cost: 3, effects: {} },&lt;br /&gt;
      { name: &#039;Dexterous&#039;,          cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Thick Skinned&#039;,      cost: 2, effects: { Robustness: 2 } },&lt;br /&gt;
      { name: &#039;Eagle Eyed&#039;,         cost: 3, effects: { &#039;Ranged Attack&#039;: 1, Perception: 1 } },&lt;br /&gt;
      { name: &#039;Desensitized&#039;,       cost: 3, effects: { Resolve: 3 } }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var NEGATIVE_TRAITS = [&lt;br /&gt;
      { name: &#039;Prone to Illness&#039;,   cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Smoker&#039;,             cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Low Thirst&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Driver&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Learner&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Reader&#039;,        cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Slow Healer&#039;,        cost: 1, effects: { Robustness: -1 } },&lt;br /&gt;
      { name: &#039;Weak Stomach&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hearty Appetite&#039;,    cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Hemophobic&#039;,         cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Agoraphobic&#039;,        cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;All Thumbs&#039;,         cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Claustrophobic&#039;,     cost: 1, effects: { Resolve: -1 } },&lt;br /&gt;
      { name: &#039;Cowardly&#039;,           cost: 2, effects: { Resolve: -2 } },&lt;br /&gt;
      { name: &#039;Pacifist&#039;,           cost: 3, effects: { Initiative: -2 } },&lt;br /&gt;
      { name: &#039;Short Sighted&#039;,      cost: 2, effects: { &#039;Ranged Attack&#039;: -1, Perception: -1 } },&lt;br /&gt;
      { name: &#039;Thin Skinned&#039;,       cost: 2, effects: { Robustness: -2 } },&lt;br /&gt;
      { name: &#039;Conspicuous&#039;,        cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Clumsy&#039;,             cost: 1, effects: { Hiding: -1 } },&lt;br /&gt;
      { name: &#039;Hard of Hearing&#039;,    cost: 2, effects: { Perception: -2 } },&lt;br /&gt;
      { name: &#039;Disorganized&#039;,       cost: 1, effects: {} },&lt;br /&gt;
      { name: &#039;Weak&#039;,               cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Unfit&#039;,              cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Illiterate&#039;,         cost: 2, effects: {} },&lt;br /&gt;
      { name: &#039;Deaf&#039;,               cost: 4, effects: { Perception: -4 } },&lt;br /&gt;
      { name: &#039;Very Weak&#039;,          cost: 3, effects: {} },&lt;br /&gt;
      { name: &#039;Very Unfit&#039;,         cost: 3, effects: {} }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var SKILL_TIERS = {&lt;br /&gt;
      Amateur:     { cost: 1, start: 6,  cap: 6,  limitPerChar: Infinity },&lt;br /&gt;
      Experienced: { cost: 2, start: 8,  cap: 8,  limitPerChar: 2 },&lt;br /&gt;
      Expert:      { cost: 3, start: 10, cap: 10, limitPerChar: 1 }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var SKILL_GROUPS = [&lt;br /&gt;
      { key: &#039;Mechanic&#039;,     label: &#039;Mechanic&#039;,     list: [&#039;Mechanics&#039;] },&lt;br /&gt;
      { key: &#039;Tailor&#039;,       label: &#039;Tailor&#039;,       list: [&#039;Tailoring&#039;] },&lt;br /&gt;
      { key: &#039;Doctor&#039;,       label: &#039;Doctor&#039;,       list: [&#039;First Aid&#039;] },&lt;br /&gt;
      { key: &#039;Electrical&#039;,   label: &#039;Electrical&#039;,   list: [&#039;Electronics&#039;] },&lt;br /&gt;
      { key: &#039;Metalworker&#039;,  label: &#039;Metalworker&#039;,  list: [&#039;Metalworking&#039;] },&lt;br /&gt;
      { key: &#039;Homesteader&#039;,  label: &#039;Homesteader&#039;,  list: [&#039;Cooking&#039;, &#039;Farming&#039;, &#039;Carpentry&#039;] },&lt;br /&gt;
      { key: &#039;Shooter&#039;,      label: &#039;Shooter&#039;,      list: [&#039;Aiming&#039;, &#039;Reloading&#039;] },&lt;br /&gt;
      { key: &#039;OutdoorsmanS&#039;, label: &#039;Outdoorsman&#039;,  list: [&#039;Fishing&#039;, &#039;Foraging&#039;, &#039;Trapping&#039;] },&lt;br /&gt;
      { key: &#039;Athlete&#039;,      label: &#039;Athlete&#039;,      list: [&#039;Nimble&#039;, &#039;Sprinting&#039;] },&lt;br /&gt;
      { key: &#039;Rogue&#039;,        label: &#039;Rogue&#039;,        list: [&#039;Sneaking&#039;, &#039;Lightfooted&#039;] },&lt;br /&gt;
      { key: &#039;FighterSurv&#039;,  label: &#039;Fighter (Survival)&#039;, list: [&#039;Axe&#039;, &#039;Spear&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlunt&#039;, label: &#039;Fighter (Blunt)&#039;,    list: [&#039;Long Blunt&#039;, &#039;Short Blunt&#039;, &#039;Maintenance&#039;] },&lt;br /&gt;
      { key: &#039;FighterBlade&#039;, label: &#039;Fighter (Blade)&#039;,    list: [&#039;Long Blade&#039;, &#039;Short Blade&#039;, &#039;Maintenance&#039;] }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Engine / Calculations&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    var CHECKS = [&lt;br /&gt;
      &#039;Strength&#039;,&#039;Fitness&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&lt;br /&gt;
      &#039;Carpentry&#039;,&#039;Farming&#039;,&#039;First Aid&#039;,&#039;Mechanics&#039;,&#039;Electronics&#039;,&#039;Metalworking&#039;,&lt;br /&gt;
      &#039;Cooking&#039;,&#039;Tailoring&#039;,&#039;Aiming&#039;,&#039;Reloading&#039;,&#039;Fishing&#039;,&#039;Foraging&#039;,&#039;Trapping&#039;,&lt;br /&gt;
      &#039;Nimble&#039;,&#039;Sprinting&#039;,&#039;Sneaking&#039;,&#039;Lightfooted&#039;,&#039;Hiding&#039;,&#039;Axe&#039;,&#039;Spear&#039;,&lt;br /&gt;
      &#039;Maintenance&#039;,&#039;Long Blunt&#039;,&#039;Short Blunt&#039;,&#039;Long Blade&#039;,&#039;Short Blade&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var state = { background: &#039;Survivor&#039;, pos: new Set(), neg: new Set(), skills: {} };&lt;br /&gt;
&lt;br /&gt;
    function ensureMetric(breakdown, key) {&lt;br /&gt;
      if (!breakdown[key]) breakdown[key] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSkillModByLevel(level) {&lt;br /&gt;
      return Math.floor((level || 0) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getTierStart(tierName) {&lt;br /&gt;
      var def = SKILL_TIERS[tierName];&lt;br /&gt;
      return def ? (def.start || 0) : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calcMaxHP(fit) {&lt;br /&gt;
      if (fit &amp;gt;= 10) return 6;&lt;br /&gt;
      if (fit &amp;gt;= 8)  return 5;&lt;br /&gt;
      if (fit &amp;gt;= 6)  return 4;&lt;br /&gt;
      if (fit &amp;gt;= 4)  return 3;&lt;br /&gt;
      return 2;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function currentSkillLevels() {&lt;br /&gt;
      var out = Object.create(null);&lt;br /&gt;
      var i, group, tier, lv, s, name;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        group = SKILL_GROUPS[i];&lt;br /&gt;
        tier  = state.skills[group.key];&lt;br /&gt;
        if (!tier) continue;&lt;br /&gt;
        lv = getTierStart(tier);&lt;br /&gt;
        for (s = 0; s &amp;lt; group.list.length; s++) {&lt;br /&gt;
          name = group.list[s];&lt;br /&gt;
          if (!out[name]) out[name] = 0;&lt;br /&gt;
          out[name] += lv; // ADD levels if multiple groups touch same skill&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sumEffects() {&lt;br /&gt;
      var breakdown = {};&lt;br /&gt;
      var i;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        breakdown[CHECKS[i]] = { base: 0, traits: 0, skills: 0, derived: 0, total: 0 };&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var bg = BACKGROUNDS[state.background];&lt;br /&gt;
      breakdown.Strength.base   = bg.base.STR;&lt;br /&gt;
      breakdown.Fitness.base    = bg.base.FIT;&lt;br /&gt;
      breakdown.Perception.base = 0;&lt;br /&gt;
      breakdown.Resolve.base    = 0;&lt;br /&gt;
&lt;br /&gt;
      // Positive traits&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var pt = POSITIVE_TRAITS[i];&lt;br /&gt;
        if (state.pos.has(pt.name)) {&lt;br /&gt;
          for (var k1 in pt.effects) if (pt.effects.hasOwnProperty(k1)) {&lt;br /&gt;
            ensureMetric(breakdown, k1);&lt;br /&gt;
            breakdown[k1].traits += pt.effects[k1];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Negative traits&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        if (state.neg.has(nt.name)) {&lt;br /&gt;
          for (var k2 in nt.effects) if (nt.effects.hasOwnProperty(k2)) {&lt;br /&gt;
            ensureMetric(breakdown, k2);&lt;br /&gt;
            breakdown[k2].traits += nt.effects[k2];&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // Background freebies&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        var ft = bg.freeTraits[i];&lt;br /&gt;
        for (var k3 in ft.effects) if (ft.effects.hasOwnProperty(k3)) {&lt;br /&gt;
          ensureMetric(breakdown, k3);&lt;br /&gt;
          breakdown[k3].traits += ft.effects[k3];&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
// Skill levels → roll mods&lt;br /&gt;
var lvMap = currentSkillLevels();&lt;br /&gt;
Object.keys(lvMap).forEach(function (skillName) {&lt;br /&gt;
  var level = lvMap[skillName];&lt;br /&gt;
  var mod   = getSkillModByLevel(level);&lt;br /&gt;
&lt;br /&gt;
  // Aiming → Ranged Attack&lt;br /&gt;
  if (skillName === &#039;Aiming&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Ranged Attack&#039;); breakdown[&#039;Ranged Attack&#039;].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Nimble → Initiative (and show Nimble too)&lt;br /&gt;
  else if (skillName === &#039;Nimble&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Initiative&#039;); breakdown.Initiative.skills += mod;&lt;br /&gt;
    ensureMetric(breakdown, &#039;Nimble&#039;);     breakdown.Nimble.skills     += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Sneaking → Hiding&lt;br /&gt;
  else if (skillName === &#039;Sneaking&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, &#039;Hiding&#039;); breakdown.Hiding.skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Reloading / First Aid map 1:1&lt;br /&gt;
  else if (skillName === &#039;Reloading&#039; || skillName === &#039;First Aid&#039;) {&lt;br /&gt;
    ensureMetric(breakdown, skillName); breakdown[skillName].skills += mod;&lt;br /&gt;
  }&lt;br /&gt;
  // Other skills (Carpentry, etc.) don’t affect a roll directly&lt;br /&gt;
  else {&lt;br /&gt;
    ensureMetric(breakdown, skillName);&lt;br /&gt;
  }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Strength.total = breakdown.Strength.base + breakdown.Strength.traits;&lt;br /&gt;
      var str = breakdown.Strength.total;&lt;br /&gt;
      if (str &amp;lt;= 4)       { breakdown[&#039;Melee Attack&#039;].derived += -1; breakdown[&#039;Melee Defence&#039;].derived += -1; }&lt;br /&gt;
      else if (str &amp;lt;= 6)  { /* no mod */ }&lt;br /&gt;
      else if (str &amp;lt;= 8)  { breakdown[&#039;Melee Attack&#039;].derived += 1;  breakdown[&#039;Melee Defence&#039;].derived += 1; }&lt;br /&gt;
      else if (str &amp;gt;= 10) { breakdown[&#039;Melee Attack&#039;].derived += 2;  breakdown[&#039;Melee Defence&#039;].derived += 2; }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      breakdown.Fitness.total = breakdown.Fitness.base + breakdown.Fitness.traits;&lt;br /&gt;
      var fitMod = Math.max(Math.floor((breakdown.Fitness.total - 6) / 2), 0);&lt;br /&gt;
      breakdown.Robustness.derived += fitMod;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) {&lt;br /&gt;
        var key = CHECKS[i];&lt;br /&gt;
        var m = breakdown[key];&lt;br /&gt;
        m.total = (m.base || 0) + (m.traits || 0) + (m.skills || 0) + (m.derived || 0);&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      breakdown.MaxHP = { value: calcMaxHP(breakdown.Fitness.total) };&lt;br /&gt;
      return breakdown;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function spend() {&lt;br /&gt;
      var pts = BACKGROUNDS[state.background].traitPoints, i, tier;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) if (state.pos.has(POSITIVE_TRAITS[i].name)) pts -= POSITIVE_TRAITS[i].cost;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) if (state.neg.has(NEGATIVE_TRAITS[i].name)) pts += NEGATIVE_TRAITS[i].cost;&lt;br /&gt;
      for (var key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        pts -= SKILL_TIERS[tier].cost;&lt;br /&gt;
      }&lt;br /&gt;
      return pts;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function skillTierCounts() {&lt;br /&gt;
      var exp = 0, ex = 0, tier, key;&lt;br /&gt;
      for (key in state.skills) if (state.skills.hasOwnProperty(key)) {&lt;br /&gt;
        tier = state.skills[key];&lt;br /&gt;
        if (tier === &#039;Experienced&#039;) exp++;&lt;br /&gt;
        if (tier === &#039;Expert&#039;)      ex++;&lt;br /&gt;
      }&lt;br /&gt;
      return { exp: exp, ex: ex };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* =========================&lt;br /&gt;
       Rendering&lt;br /&gt;
       ========================= */&lt;br /&gt;
&lt;br /&gt;
    function renderBackground() {&lt;br /&gt;
      var bg = BACKGROUNDS[state.background], i;&lt;br /&gt;
&lt;br /&gt;
      var opts = &#039;&#039;, keys = Object.keys(BACKGROUNDS);&lt;br /&gt;
      for (i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var k = keys[i];&lt;br /&gt;
        opts += &#039;&amp;lt;option value=&amp;quot;&#039; + k + &#039;&amp;quot;&#039; + (state.background === k ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;&#039; + k + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var freebies = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; bg.freeTraits.length; i++) {&lt;br /&gt;
        freebies += &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;&#039; + bg.freeTraits[i].name + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      var used = (bg.traitPoints - spend());&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Background&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;backgroundSel&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points: &amp;lt;b&amp;gt;&#039; + bg.traitPoints + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base STR: &amp;lt;b&amp;gt;&#039; + bg.base.STR + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Base FIT: &amp;lt;b&amp;gt;&#039; + bg.base.FIT + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          freebies +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;muted section&amp;quot;&amp;gt;Background gives Trait Points, base STR/FIT, and two thematic traits.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Trait Points Used: &amp;lt;b&amp;gt;&#039; + used + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill &#039; + (spend() &amp;lt; 0 ? &#039;warning&#039; : &#039;success&#039;) + &#039;&amp;quot;&amp;gt;Remaining: &amp;lt;b&amp;gt;&#039; + spend() + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#bg-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#backgroundSel&#039;).off(&#039;change input&#039;).on(&#039;change input&#039;, function (e) {&lt;br /&gt;
        state.background = e.target.value;&lt;br /&gt;
        renderAll();&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderTraitsAndSkills() {&lt;br /&gt;
      var prevPosOpen = $(&#039;#posDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      var prevNegOpen = $(&#039;#negDetails&#039;).prop(&#039;open&#039;);&lt;br /&gt;
      if (typeof prevPosOpen === &#039;undefined&#039;) prevPosOpen = true;&lt;br /&gt;
      if (typeof prevNegOpen === &#039;undefined&#039;) prevNegOpen = true;&lt;br /&gt;
&lt;br /&gt;
      var counts = skillTierCounts();&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;posDetails&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Positive Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(costs subtract from your points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;positives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;details id=&amp;quot;negDetails&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;summary&amp;gt;&amp;lt;strong&amp;gt;Negative Traits&amp;lt;/strong&amp;gt; &amp;lt;span class=&amp;quot;small&amp;quot;&amp;gt;(refund points)&amp;lt;/span&amp;gt;&amp;lt;/summary&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;list&amp;quot; id=&amp;quot;negatives&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/details&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;section&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;strong&amp;gt;Skill Traits&amp;lt;/strong&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;small&amp;quot;&amp;gt;Amateur (1 pt, L6 cap), Experienced (2 pts, L8 cap, &amp;lt;b&amp;gt;max 2&amp;lt;/b&amp;gt;), Expert (3 pts, L10 cap, &amp;lt;b&amp;gt;max 1&amp;lt;/b&amp;gt;). One tier per skill group.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div id=&amp;quot;skills&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;totals section&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Experienced: &amp;lt;b&amp;gt;&#039; + counts.exp + &#039;/2&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Expert: &amp;lt;b&amp;gt;&#039; + counts.ex + &#039;/1&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#traits-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#posDetails&#039;).prop(&#039;open&#039;, !!prevPosOpen);&lt;br /&gt;
      $(&#039;#negDetails&#039;).prop(&#039;open&#039;, !!prevNegOpen);&lt;br /&gt;
&lt;br /&gt;
      // Positives&lt;br /&gt;
      var posHtml = &#039;&#039;, i, k, v;&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) {&lt;br /&gt;
        var t = POSITIVE_TRAITS[i];&lt;br /&gt;
        var id = &#039;pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var checked = state.pos.has(t.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var mods = &#039;&#039;;&lt;br /&gt;
        for (k in t.effects) if (t.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = t.effects[k];&lt;br /&gt;
          mods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        posHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + id + &#039;&amp;quot; &#039; + checked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + t.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(cost &#039; + t.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + mods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#positives&#039;).html(posHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; POSITIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#pos_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.pos.add(t.name); else state.pos.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(POSITIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Negatives&lt;br /&gt;
      var negHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) {&lt;br /&gt;
        var nt = NEGATIVE_TRAITS[i];&lt;br /&gt;
        var nid = &#039;neg_&#039; + nt.name.replace(/\s+/g, &#039;_&#039;);&lt;br /&gt;
        var nchecked = state.neg.has(nt.name) ? &#039;checked&#039; : &#039;&#039;;&lt;br /&gt;
        var nmods = &#039;&#039;;&lt;br /&gt;
        for (k in nt.effects) if (nt.effects.hasOwnProperty(k)) {&lt;br /&gt;
          v = nt.effects[k];&lt;br /&gt;
          nmods += &#039;&amp;lt;span class=&amp;quot;tag&amp;quot;&amp;gt;&#039; + k + &#039;: &#039; + (v &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + v + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        negHtml += &#039;&amp;lt;label class=&amp;quot;checkbox&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot; id=&amp;quot;&#039; + nid + &#039;&amp;quot; &#039; + nchecked + &#039;/&amp;gt; &#039; +&lt;br /&gt;
                   &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + nt.name + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;muted&amp;quot;&amp;gt;(refund &#039; + nt.cost + &#039;)&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                   &#039;&amp;lt;div class=&amp;quot;flex&amp;quot;&amp;gt;&#039; + nmods + &#039;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/label&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#negatives&#039;).html(negHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; NEGATIVE_TRAITS.length; i++) (function (t) {&lt;br /&gt;
        $(&#039;#neg_&#039; + t.name.replace(/\s+/g, &#039;_&#039;)).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          if (e.target.checked) state.neg.add(t.name); else state.neg.delete(t.name);&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(NEGATIVE_TRAITS[i]);&lt;br /&gt;
&lt;br /&gt;
      // Skills&lt;br /&gt;
      var skillsHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) {&lt;br /&gt;
        var s = SKILL_GROUPS[i];&lt;br /&gt;
        var val = state.skills[s.key] || &#039;&#039;;&lt;br /&gt;
        var scounts = skillTierCounts();&lt;br /&gt;
        var opts = &#039;&#039;;&lt;br /&gt;
        var tiers = [&#039;&#039;, &#039;Amateur&#039;, &#039;Experienced&#039;, &#039;Expert&#039;];&lt;br /&gt;
        for (var ti = 0; ti &amp;lt; tiers.length; ti++) {&lt;br /&gt;
          var tier = tiers[ti];&lt;br /&gt;
          if (tier === &#039;&#039;) {&lt;br /&gt;
            opts += &#039;&amp;lt;option value=&amp;quot;&amp;quot;&#039; + (val === &#039;&#039; ? &#039; selected&#039; : &#039;&#039;) + &#039;&amp;gt;None&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Experienced&#039; &amp;amp;&amp;amp; scounts.exp &amp;gt;= 2 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Experienced (max 2)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else if (tier === &#039;Expert&#039; &amp;amp;&amp;amp; scounts.ex &amp;gt;= 1 &amp;amp;&amp;amp; val !== tier) {&lt;br /&gt;
            opts += &#039;&amp;lt;option disabled&amp;gt;Expert (max 1)&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          } else {&lt;br /&gt;
            var tdata = SKILL_TIERS[tier];&lt;br /&gt;
            var lab = tier + &#039; — cost &#039; + tdata.cost + &#039;, start L&#039; + tdata.start + &#039;, cap &#039; + tdata.cap;&lt;br /&gt;
            opts += &#039;&amp;lt;option &#039; + (val === tier ? &#039;selected&#039; : &#039;&#039;) + &#039; value=&amp;quot;&#039; + tier + &#039;&amp;quot;&amp;gt;&#039; + lab + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var shownStart = val ? getTierStart(val) : 0;&lt;br /&gt;
        var shownMod   = val ? getSkillModByLevel(shownStart) : 0;&lt;br /&gt;
&lt;br /&gt;
        skillsHtml +=&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;row&amp;quot; style=&amp;quot;align-items:center;margin:6px 0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&#039; + s.label + &#039;&amp;lt;/b&amp;gt; &amp;lt;span class=&amp;quot;small muted&amp;quot;&amp;gt;(&#039; + s.list.join(&#039;, &#039;) + &#039;)&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div&amp;gt;&amp;lt;select id=&amp;quot;skill_&#039; + s.key + &#039;&amp;quot;&amp;gt;&#039; + opts + &#039;&amp;lt;/select&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; style=&amp;quot;grid-column:1 / -1; color:var(--muted)&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
              &#039;Level: &amp;lt;b&amp;gt;&#039; + shownStart + &#039;&amp;lt;/b&amp;gt; • Modifier: &amp;lt;b&amp;gt;&#039; + shownMod + &#039;&amp;lt;/b&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      $(&#039;#skills&#039;).html(skillsHtml);&lt;br /&gt;
      for (i = 0; i &amp;lt; SKILL_GROUPS.length; i++) (function (key) {&lt;br /&gt;
        $(&#039;#skill_&#039; + key).off(&#039;change&#039;).on(&#039;change&#039;, function (e) {&lt;br /&gt;
          var v = e.target.value || null;&lt;br /&gt;
          if (v) state.skills[key] = v; else delete state.skills[key];&lt;br /&gt;
          renderAll();&lt;br /&gt;
        });&lt;br /&gt;
      })(SKILL_GROUPS[i].key);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* ---------- Summary &amp;amp; Roller ---------- */&lt;br /&gt;
&lt;br /&gt;
    var ROLL_CHECKS = [&lt;br /&gt;
      &#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Reloading&#039;,&lt;br /&gt;
      &#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;,&lt;br /&gt;
      &#039;First Aid&#039;,&#039;Nimble&#039;,&#039;Hiding&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function renderSummary() {&lt;br /&gt;
      var b = sumEffects();&lt;br /&gt;
&lt;br /&gt;
      var str   = (b.Strength &amp;amp;&amp;amp; b.Strength.total) || 0;&lt;br /&gt;
      var fit   = (b.Fitness  &amp;amp;&amp;amp; b.Fitness.total)  || 0;&lt;br /&gt;
      var maxHP = (b.MaxHP    &amp;amp;&amp;amp; b.MaxHP.value)    || 0;&lt;br /&gt;
      var levels = currentSkillLevels();&lt;br /&gt;
      var names  = Object.keys(levels).sort();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
      var skillRows = &#039;&#039;;&lt;br /&gt;
      for (var i = 0; i &amp;lt; names.length; i++) {&lt;br /&gt;
        var nm = names[i], lv = levels[nm];&lt;br /&gt;
        if (lv &amp;gt; 0) skillRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + nm + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + lv + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!skillRows) skillRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No skills selected yet.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var rollRows = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; ROLL_CHECKS.length; i++) {&lt;br /&gt;
        var chk = ROLL_CHECKS[i];&lt;br /&gt;
        var tot = (b[chk] &amp;amp;&amp;amp; (b[chk].total || 0)) || 0;&lt;br /&gt;
        if (tot !== 0) rollRows += &#039;&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&#039; + chk + &#039;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;b&amp;gt;&#039; + (tot &amp;gt; 0 ? &#039;+&#039; : &#039;&#039;) + tot + &#039;&amp;lt;/b&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
      }&lt;br /&gt;
      if (!rollRows) rollRows = &#039;&amp;lt;tr&amp;gt;&amp;lt;td colspan=&amp;quot;2&amp;quot; class=&amp;quot;muted&amp;quot;&amp;gt;No roll modifiers.&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;totals&amp;quot; style=&amp;quot;margin-bottom:8px&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Strength: &amp;lt;b&amp;gt;&#039; + str + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Fitness: &amp;lt;b&amp;gt;&#039; + fit + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;span class=&amp;quot;pill&amp;quot;&amp;gt;Max HP: &amp;lt;b&amp;gt;&#039; + maxHP + &#039;&amp;lt;/b&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Skills with Levels&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Level&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          skillRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;table class=&amp;quot;stat-table&amp;quot; style=&amp;quot;margin-top:12px&amp;quot;&amp;gt;&amp;lt;thead&amp;gt;&amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;Roll Modifiers&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;Mod&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;&amp;lt;/thead&amp;gt;&amp;lt;tbody&amp;gt;&#039; +&lt;br /&gt;
          rollRows +&lt;br /&gt;
        &#039;&amp;lt;/tbody&amp;gt;&amp;lt;/table&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#summary-area&#039;).html(html);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderRoller() {&lt;br /&gt;
      var preferred = [&#039;Melee Attack&#039;,&#039;Melee Defence&#039;,&#039;Ranged Attack&#039;,&#039;Perception&#039;,&#039;Resolve&#039;,&#039;Initiative&#039;,&#039;Robustness&#039;];&lt;br /&gt;
      var inPref = {};&lt;br /&gt;
      var i, optHtml = &#039;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) inPref[preferred[i]] = true;&lt;br /&gt;
&lt;br /&gt;
      for (i = 0; i &amp;lt; preferred.length; i++) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + preferred[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
      for (i = 0; i &amp;lt; CHECKS.length; i++) if (!inPref[CHECKS[i]]) optHtml += &#039;&amp;lt;option&amp;gt;&#039; + CHECKS[i] + &#039;&amp;lt;/option&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      var html =&lt;br /&gt;
        &#039;&amp;lt;label&amp;gt;Roll Test Type&amp;lt;/label&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;select id=&amp;quot;rollType&amp;quot;&amp;gt;&#039; + optHtml + &#039;&amp;lt;/select&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;rollbox&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;button id=&amp;quot;rollBtn&amp;quot;&amp;gt;Roll 2d6&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div class=&amp;quot;dice&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d1&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;die&amp;quot; id=&amp;quot;d2&amp;quot;&amp;gt;–&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;total&amp;quot; id=&amp;quot;total&amp;quot;&amp;gt;Total —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;small&amp;quot; id=&amp;quot;breakdown&amp;quot;&amp;gt;Modifier breakdown will appear here.&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
          &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
      $(&#039;#roller-area&#039;).html(html);&lt;br /&gt;
      $(&#039;#rollBtn&#039;).off(&#039;click&#039;).on(&#039;click&#039;, function () {&lt;br /&gt;
        var name = $(&#039;#rollType&#039;).val();&lt;br /&gt;
        var a = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var b2 = 1 + Math.floor(Math.random() * 6);&lt;br /&gt;
        var bd = sumEffects();&lt;br /&gt;
        var row = bd[name] || { base:0, traits:0, skills:0, derived:0, total:0 };&lt;br /&gt;
        var mod = row.total || 0;&lt;br /&gt;
        $(&#039;#d1&#039;).text(a); $(&#039;#d2&#039;).text(b2);&lt;br /&gt;
        $(&#039;#total&#039;).text(&#039;Total &#039; + (a + b2 + mod) + &#039;  (2d6=&#039; + (a + b2) + &#039; &#039; + (mod &amp;gt;= 0 ? &#039;+&#039; : &#039;&#039;) + mod + &#039;)&#039;);&lt;br /&gt;
        $(&#039;#breakdown&#039;).html(&#039;Base: &#039; + (row.base||0) + &#039; + Traits: &#039; + (row.traits||0) + &#039; + Skills: &#039; + (row.skills||0) + &#039; + Derived: &#039; + (row.derived||0) + &#039; = &amp;lt;b&amp;gt;&#039; + mod + &#039;&amp;lt;/b&amp;gt;&#039;);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll() {&lt;br /&gt;
      renderBackground();&lt;br /&gt;
      renderTraitsAndSkills();&lt;br /&gt;
      renderSummary();&lt;br /&gt;
      renderRoller();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    renderAll();&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  $(mount);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(function () { mount(); });&lt;br /&gt;
&lt;br /&gt;
})(mediaWiki, jQuery);&lt;/div&gt;</summary>
		<author><name>Dubhleohan</name></author>
	</entry>
</feed>