FA-86251 / Game economy crafting balance / Open access
Profession skill-up colors: Roll zero skills up on gray · case 01
A zero roll yields a skill-up on gray recipes.
ROOT CAUSE
The roll comparison is inclusive.
VERIFIED REPAIR
Restore `if r < chance:` at the roll comparison step.
Unsuccessful approach: Mirroring the roll makes a zero roll fail an orange craft.
Case contract
recipe_levels = [learn, yellow, green, gray]. Skill below learn cannot craft (error "too low"). Per roll (0..99), while skill < 300: chance 100 below yellow (orange), 75 below green, 25 below gray, else 0; the skill goes up when roll < chance and the color is re-evaluated each craft.
Why this case matters
Game economies leak or destroy currency when one crafting or pricing rule is off by one boundary, rounding stage or state update; the defect is observable in exact integer outcomes.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(skill, recipe_levels, rolls):
learn, yellow, green, gray = recipe_levels
if skill < learn:
return {'skill': skill, 'ups': 0, 'error': 'too low'}
ups = 0
for r in rolls:
if skill >= 300:
break
if skill < yellow:
chance = 100
elif skill < green:
chance = 75
elif skill < gray:
chance = 25
else:
chance = 0
if r <= chance:
skill += 1
ups += 1
return {'skill': skill, 'ups': ups}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1',
[84, [64, 70, 80, 85], [0, 99, 75, 25, 25, 99, 75, 0]],
{'skill': 85, 'ups': 1}),
('regression roll comparison #1',
[142, [114, 127, 141, 154], [0, 25, 0, 52, 99, 0, 24, 75]],
{'skill': 146, 'ups': 4}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('partial repair boundary #1', [289, [288, 314, 330, 340], [50, 0, 50, 0, 0]], {'skill': 294, 'ups': 5}),
('control #1', [23, [8, 14, 23, 38], [75]], {'skill': 23, 'ups': 0}),
('control #2', [299, [277, 291, 302, 311], [74, 74, 74, 75, 14, 25, 25, 99]], {'skill': 300, 'ups': 1})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('regression roll comparison #1',
[142, [114, 127, 141, 154], [0, 25, 0, 52, 99, 0, 24, 75]],
{'skill': 146, 'ups': 4}),
('fault site roll comparison #1',
[118, [118, 118, 123, 131], [75, 0, 66, 99, 25]],
{'skill': 121, 'ups': 3}),
('partial repair boundary #1', [289, [288, 314, 330, 340], [50, 0, 50, 0, 0]], {'skill': 294, 'ups': 5}),
('partial repair boundary #2', [36, [33, 37, 40, 42], [24, 25, 24]], {'skill': 39, 'ups': 3}),
('control #1',
[135, [136, 138, 140, 143], [25, 25, 25, 7, 25, 0, 25, 25]],
{'skill': 135, 'ups': 0, 'error': 'too low'}),
('control #2', [79, [80, 91, 104, 106], [16, 74]], {'skill': 79, 'ups': 0, 'error': 'too low'})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1', [212, [198, 209, 212, 213], [0, 74, 99, 0]], {'skill': 213, 'ups': 1}),
('fault site roll comparison #2', [229, [196, 209, 224, 239], [25]], {'skill': 229, 'ups': 0}),
('partial repair boundary #1', [36, [33, 37, 40, 42], [24, 25, 24]], {'skill': 39, 'ups': 3}),
('partial repair boundary #2', [100, [90, 100, 102, 105], [25, 25, 75]], {'skill': 102, 'ups': 2}),
('control #1', [300, [67, 71, 73, 83], [9]], {'skill': 300, 'ups': 0}),
('control #2', [299, [55, 58, 59, 62], [9]], {'skill': 299, 'ups': 0})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('regression roll comparison #1', [239, [232, 239, 246, 246], [75]], {'skill': 239, 'ups': 0}),
('regression roll comparison #2',
[90, [90, 94, 100, 110], [24, 74, 24, 24, 75, 75, 74]],
{'skill': 95, 'ups': 5}),
('partial repair boundary #1',
[291, [288, 328, 330, 340], [50, 50, 0, 0, 0, 50, 0, 1, 50, 0, 50]],
{'skill': 300, 'ups': 9}),
('partial repair boundary #2',
[291, [289, 309, 330, 340], [0, 0, 1, 1, 1, 50, 0, 1, 1, 1]],
{'skill': 300, 'ups': 9}),
('control #1', [279, [272, 312, 330, 340], [1, 50, 1, 50, 50]], {'skill': 284, 'ups': 5}),
('control #2', [277, [275, 298, 330, 340], [1, 1, 50]], {'skill': 280, 'ups': 3})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1', [24, [17, 23, 25, 25], [75, 0, 99]], {'skill': 25, 'ups': 1}),
('fault site roll comparison #2',
[178, [157, 164, 168, 179], [24, 69, 99, 74, 75, 74, 33, 0]],
{'skill': 179, 'ups': 1}),
('partial repair boundary #1', [126, [114, 126, 131, 135], [74, 24, 8, 74, 25]], {'skill': 131, 'ups': 5}),
('partial repair boundary #2',
[281, [276, 294, 330, 340], [50, 1, 1, 0, 0, 1, 50, 1, 50]],
{'skill': 290, 'ups': 9}),
('control #1', [32, [33, 46, 61, 75], [0, 0, 25, 0, 6]], {'skill': 32, 'ups': 0, 'error': 'too low'}),
('control #2', [90, [81, 82, 90, 90], [25]], {'skill': 90, 'ups': 0})]]
for label, args, expected in cases[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| gray recipe roll zero #1 | {'skill': 92, 'ups': 2} | {'skill': 90, 'ups': 0} | Failed |
| exactly at learn level #1 | {'skill': 51, 'ups': 1} | {'skill': 51, 'ups': 1} | Passed |
| fault site roll comparison #1 | {'skill': 86, 'ups': 2} | {'skill': 85, 'ups': 1} | Failed |
| regression roll comparison #1 | {'skill': 147, 'ups': 5} | {'skill': 146, 'ups': 4} | Failed |
| at cap #1 | {'skill': 300, 'ups': 1} | {'skill': 300, 'ups': 1} | Passed |
| partial repair boundary #1 | {'skill': 294, 'ups': 5} | {'skill': 294, 'ups': 5} | Passed |
| control #1 | {'skill': 23, 'ups': 0} | {'skill': 23, 'ups': 0} | Passed |
| control #2 | {'skill': 300, 'ups': 1} | {'skill': 300, 'ups': 1} | Passed |
SHA-256 / 46d56a20c360625f23592bc9910b7f2f53e3768780fee6708a29a92802cde800
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(skill, recipe_levels, rolls):
learn, yellow, green, gray = recipe_levels
if skill < learn:
return {'skill': skill, 'ups': 0, 'error': 'too low'}
ups = 0
for r in rolls:
if skill >= 300:
break
if skill < yellow:
chance = 100
elif skill < green:
chance = 75
elif skill < gray:
chance = 25
else:
chance = 0
if r > 100 - chance:
skill += 1
ups += 1
return {'skill': skill, 'ups': ups}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1',
[84, [64, 70, 80, 85], [0, 99, 75, 25, 25, 99, 75, 0]],
{'skill': 85, 'ups': 1}),
('regression roll comparison #1',
[142, [114, 127, 141, 154], [0, 25, 0, 52, 99, 0, 24, 75]],
{'skill': 146, 'ups': 4}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('partial repair boundary #1', [289, [288, 314, 330, 340], [50, 0, 50, 0, 0]], {'skill': 294, 'ups': 5}),
('control #1', [23, [8, 14, 23, 38], [75]], {'skill': 23, 'ups': 0}),
('control #2', [299, [277, 291, 302, 311], [74, 74, 74, 75, 14, 25, 25, 99]], {'skill': 300, 'ups': 1})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('regression roll comparison #1',
[142, [114, 127, 141, 154], [0, 25, 0, 52, 99, 0, 24, 75]],
{'skill': 146, 'ups': 4}),
('fault site roll comparison #1',
[118, [118, 118, 123, 131], [75, 0, 66, 99, 25]],
{'skill': 121, 'ups': 3}),
('partial repair boundary #1', [289, [288, 314, 330, 340], [50, 0, 50, 0, 0]], {'skill': 294, 'ups': 5}),
('partial repair boundary #2', [36, [33, 37, 40, 42], [24, 25, 24]], {'skill': 39, 'ups': 3}),
('control #1',
[135, [136, 138, 140, 143], [25, 25, 25, 7, 25, 0, 25, 25]],
{'skill': 135, 'ups': 0, 'error': 'too low'}),
('control #2', [79, [80, 91, 104, 106], [16, 74]], {'skill': 79, 'ups': 0, 'error': 'too low'})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1', [212, [198, 209, 212, 213], [0, 74, 99, 0]], {'skill': 213, 'ups': 1}),
('fault site roll comparison #2', [229, [196, 209, 224, 239], [25]], {'skill': 229, 'ups': 0}),
('partial repair boundary #1', [36, [33, 37, 40, 42], [24, 25, 24]], {'skill': 39, 'ups': 3}),
('partial repair boundary #2', [100, [90, 100, 102, 105], [25, 25, 75]], {'skill': 102, 'ups': 2}),
('control #1', [300, [67, 71, 73, 83], [9]], {'skill': 300, 'ups': 0}),
('control #2', [299, [55, 58, 59, 62], [9]], {'skill': 299, 'ups': 0})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('regression roll comparison #1', [239, [232, 239, 246, 246], [75]], {'skill': 239, 'ups': 0}),
('regression roll comparison #2',
[90, [90, 94, 100, 110], [24, 74, 24, 24, 75, 75, 74]],
{'skill': 95, 'ups': 5}),
('partial repair boundary #1',
[291, [288, 328, 330, 340], [50, 50, 0, 0, 0, 50, 0, 1, 50, 0, 50]],
{'skill': 300, 'ups': 9}),
('partial repair boundary #2',
[291, [289, 309, 330, 340], [0, 0, 1, 1, 1, 50, 0, 1, 1, 1]],
{'skill': 300, 'ups': 9}),
('control #1', [279, [272, 312, 330, 340], [1, 50, 1, 50, 50]], {'skill': 284, 'ups': 5}),
('control #2', [277, [275, 298, 330, 340], [1, 1, 50]], {'skill': 280, 'ups': 3})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1', [24, [17, 23, 25, 25], [75, 0, 99]], {'skill': 25, 'ups': 1}),
('fault site roll comparison #2',
[178, [157, 164, 168, 179], [24, 69, 99, 74, 75, 74, 33, 0]],
{'skill': 179, 'ups': 1}),
('partial repair boundary #1', [126, [114, 126, 131, 135], [74, 24, 8, 74, 25]], {'skill': 131, 'ups': 5}),
('partial repair boundary #2',
[281, [276, 294, 330, 340], [50, 1, 1, 0, 0, 1, 50, 1, 50]],
{'skill': 290, 'ups': 9}),
('control #1', [32, [33, 46, 61, 75], [0, 0, 25, 0, 6]], {'skill': 32, 'ups': 0, 'error': 'too low'}),
('control #2', [90, [81, 82, 90, 90], [25]], {'skill': 90, 'ups': 0})]]
for label, args, expected in cases[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| gray recipe roll zero #1 | {'skill': 90, 'ups': 0} | {'skill': 90, 'ups': 0} | Passed |
| exactly at learn level #1 | {'skill': 50, 'ups': 0} | {'skill': 51, 'ups': 1} | Failed |
| fault site roll comparison #1 | {'skill': 85, 'ups': 1} | {'skill': 85, 'ups': 1} | Passed |
| regression roll comparison #1 | {'skill': 143, 'ups': 1} | {'skill': 146, 'ups': 4} | Failed |
| at cap #1 | {'skill': 299, 'ups': 0} | {'skill': 300, 'ups': 1} | Failed |
| partial repair boundary #1 | {'skill': 291, 'ups': 2} | {'skill': 294, 'ups': 5} | Failed |
| control #1 | {'skill': 23, 'ups': 0} | {'skill': 23, 'ups': 0} | Passed |
| control #2 | {'skill': 300, 'ups': 1} | {'skill': 300, 'ups': 1} | Passed |
SHA-256 / e225f0bfbadb34910297020e619a9bc6c087e51f28de381ca72a80d7c75d5db3
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(skill, recipe_levels, rolls):
learn, yellow, green, gray = recipe_levels
if skill < learn:
return {'skill': skill, 'ups': 0, 'error': 'too low'}
ups = 0
for r in rolls:
if skill >= 300:
break
if skill < yellow:
chance = 100
elif skill < green:
chance = 75
elif skill < gray:
chance = 25
else:
chance = 0
if r < chance:
skill += 1
ups += 1
return {'skill': skill, 'ups': ups}
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1',
[84, [64, 70, 80, 85], [0, 99, 75, 25, 25, 99, 75, 0]],
{'skill': 85, 'ups': 1}),
('regression roll comparison #1',
[142, [114, 127, 141, 154], [0, 25, 0, 52, 99, 0, 24, 75]],
{'skill': 146, 'ups': 4}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('partial repair boundary #1', [289, [288, 314, 330, 340], [50, 0, 50, 0, 0]], {'skill': 294, 'ups': 5}),
('control #1', [23, [8, 14, 23, 38], [75]], {'skill': 23, 'ups': 0}),
('control #2', [299, [277, 291, 302, 311], [74, 74, 74, 75, 14, 25, 25, 99]], {'skill': 300, 'ups': 1})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('regression roll comparison #1',
[142, [114, 127, 141, 154], [0, 25, 0, 52, 99, 0, 24, 75]],
{'skill': 146, 'ups': 4}),
('fault site roll comparison #1',
[118, [118, 118, 123, 131], [75, 0, 66, 99, 25]],
{'skill': 121, 'ups': 3}),
('partial repair boundary #1', [289, [288, 314, 330, 340], [50, 0, 50, 0, 0]], {'skill': 294, 'ups': 5}),
('partial repair boundary #2', [36, [33, 37, 40, 42], [24, 25, 24]], {'skill': 39, 'ups': 3}),
('control #1',
[135, [136, 138, 140, 143], [25, 25, 25, 7, 25, 0, 25, 25]],
{'skill': 135, 'ups': 0, 'error': 'too low'}),
('control #2', [79, [80, 91, 104, 106], [16, 74]], {'skill': 79, 'ups': 0, 'error': 'too low'})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1', [212, [198, 209, 212, 213], [0, 74, 99, 0]], {'skill': 213, 'ups': 1}),
('fault site roll comparison #2', [229, [196, 209, 224, 239], [25]], {'skill': 229, 'ups': 0}),
('partial repair boundary #1', [36, [33, 37, 40, 42], [24, 25, 24]], {'skill': 39, 'ups': 3}),
('partial repair boundary #2', [100, [90, 100, 102, 105], [25, 25, 75]], {'skill': 102, 'ups': 2}),
('control #1', [300, [67, 71, 73, 83], [9]], {'skill': 300, 'ups': 0}),
('control #2', [299, [55, 58, 59, 62], [9]], {'skill': 299, 'ups': 0})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('at cap #1', [299, [200, 250, 275, 320], [0, 0]], {'skill': 300, 'ups': 1}),
('regression roll comparison #1', [239, [232, 239, 246, 246], [75]], {'skill': 239, 'ups': 0}),
('regression roll comparison #2',
[90, [90, 94, 100, 110], [24, 74, 24, 24, 75, 75, 74]],
{'skill': 95, 'ups': 5}),
('partial repair boundary #1',
[291, [288, 328, 330, 340], [50, 50, 0, 0, 0, 50, 0, 1, 50, 0, 50]],
{'skill': 300, 'ups': 9}),
('partial repair boundary #2',
[291, [289, 309, 330, 340], [0, 0, 1, 1, 1, 50, 0, 1, 1, 1]],
{'skill': 300, 'ups': 9}),
('control #1', [279, [272, 312, 330, 340], [1, 50, 1, 50, 50]], {'skill': 284, 'ups': 5}),
('control #2', [277, [275, 298, 330, 340], [1, 1, 50]], {'skill': 280, 'ups': 3})],
[('gray recipe roll zero #1', [90, [50, 60, 70, 80], [0, 0]], {'skill': 90, 'ups': 0}),
('exactly at learn level #1', [50, [50, 60, 70, 80], [0]], {'skill': 51, 'ups': 1}),
('fault site roll comparison #1', [24, [17, 23, 25, 25], [75, 0, 99]], {'skill': 25, 'ups': 1}),
('fault site roll comparison #2',
[178, [157, 164, 168, 179], [24, 69, 99, 74, 75, 74, 33, 0]],
{'skill': 179, 'ups': 1}),
('partial repair boundary #1', [126, [114, 126, 131, 135], [74, 24, 8, 74, 25]], {'skill': 131, 'ups': 5}),
('partial repair boundary #2',
[281, [276, 294, 330, 340], [50, 1, 1, 0, 0, 1, 50, 1, 50]],
{'skill': 290, 'ups': 9}),
('control #1', [32, [33, 46, 61, 75], [0, 0, 25, 0, 6]], {'skill': 32, 'ups': 0, 'error': 'too low'}),
('control #2', [90, [81, 82, 90, 90], [25]], {'skill': 90, 'ups': 0})]]
for label, args, expected in cases[N-1]:
check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
| Boundary fixture | Actual | Expected | Outcome |
|---|---|---|---|
| gray recipe roll zero #1 | {'skill': 90, 'ups': 0} | {'skill': 90, 'ups': 0} | Passed |
| exactly at learn level #1 | {'skill': 51, 'ups': 1} | {'skill': 51, 'ups': 1} | Passed |
| fault site roll comparison #1 | {'skill': 85, 'ups': 1} | {'skill': 85, 'ups': 1} | Passed |
| regression roll comparison #1 | {'skill': 146, 'ups': 4} | {'skill': 146, 'ups': 4} | Passed |
| at cap #1 | {'skill': 300, 'ups': 1} | {'skill': 300, 'ups': 1} | Passed |
| partial repair boundary #1 | {'skill': 294, 'ups': 5} | {'skill': 294, 'ups': 5} | Passed |
| control #1 | {'skill': 23, 'ups': 0} | {'skill': 23, 'ups': 0} | Passed |
| control #2 | {'skill': 300, 'ups': 1} | {'skill': 300, 'ups': 1} | Passed |
SHA-256 / 2847dbd86e6761208a1bd6b6ca7f40b5691dde5e6b321215f54c1aa6d7425514
Verification & scope
Deterministic toy contract stipulated for this model; integer or exact arithmetic only, not a reproduction of any specific game engine. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.
Observations recorded using Python 3.12.14 at 2026-09-29T14:50:47.931246+00:00.
Case digest / 2bc5497b6c77ef28c74de02ad1c22429a718d7033c02546a1eb8e70826e95db9