feat: Cards writings and balancing (M8)
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The M8 balance sections are read by the engine without defensive checks: a typo in a sector id
|
||||
// would crash the game at runtime, so every reference is checked here.
|
||||
func TestBalanceSections(t *testing.T) {
|
||||
bal := load(t, "data/balance.json")
|
||||
scn := load(t, "data/scenarios/default.json")
|
||||
sectors := set(keys(scn["sectors"].(obj)))
|
||||
|
||||
for id, v := range bal["needGrowth"].(obj) {
|
||||
if !sectors[id] {
|
||||
t.Errorf("needGrowth: unknown sector %q", id)
|
||||
}
|
||||
if g, ok := v.(float64); !ok || g < 0 || g > 0.2 {
|
||||
t.Errorf("needGrowth.%s must be a yearly rate between 0 and 0.2", id)
|
||||
}
|
||||
}
|
||||
for id := range sectors {
|
||||
if _, ok := bal["needGrowth"].(obj)[id]; !ok {
|
||||
t.Errorf("needGrowth: missing sector %q", id)
|
||||
}
|
||||
}
|
||||
|
||||
w := bal["world"].(obj)
|
||||
sameKeys(t, "balance.world", w, []string{"tau", "noise", "max", "shockChancePerDay", "shock", "boomShare"})
|
||||
for k, v := range w {
|
||||
if f, ok := v.(float64); !ok || f <= 0 {
|
||||
t.Errorf("world.%s must be a positive number", k)
|
||||
}
|
||||
}
|
||||
if g, ok := bal["gain"].(float64); !ok || g <= 0 {
|
||||
t.Error("gain must be positive")
|
||||
}
|
||||
if g, ok := bal["driftScale"].(float64); !ok || g < 0 {
|
||||
t.Error("driftScale must be a number >= 0")
|
||||
}
|
||||
if f, ok := bal["fiscal"].(obj)["growthSens"].(float64); !ok || f < 0 {
|
||||
t.Error("fiscal.growthSens must be a number >= 0")
|
||||
}
|
||||
if c, ok := bal["gameOver"].(obj)["emergencyCooldown"].(float64); !ok || c < 1 {
|
||||
t.Error("gameOver.emergencyCooldown must be at least 1 day")
|
||||
}
|
||||
|
||||
// every indicator and economy variable definition
|
||||
known := set([]string{"tau", "noise", "min", "max", "sectors", "alloc", "econ", "ind", "world", "excess", "short", "drift"})
|
||||
check := func(group string, defs obj) {
|
||||
for id, raw := range defs {
|
||||
def := raw.(obj)
|
||||
for k := range def {
|
||||
if !known[k] {
|
||||
t.Errorf("%s.%s: unknown field %q", group, id, k)
|
||||
}
|
||||
}
|
||||
for _, field := range []string{"sectors", "alloc"} {
|
||||
if m, ok := def[field].(obj); ok {
|
||||
for sid := range m {
|
||||
if !sectors[sid] {
|
||||
t.Errorf("%s.%s.%s: unknown sector %q", group, id, field, sid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, field := range []string{"excess", "short"} {
|
||||
m, ok := def[field].(obj)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
for sid, pair := range m {
|
||||
if !sectors[sid] {
|
||||
t.Errorf("%s.%s.%s: unknown sector %q", group, id, field, sid)
|
||||
}
|
||||
p, ok := pair.([]any)
|
||||
if !ok || len(p) != 2 {
|
||||
t.Errorf("%s.%s.%s.%s must be [from, weight]", group, id, field, sid)
|
||||
continue
|
||||
}
|
||||
from, ok1 := p[0].(float64)
|
||||
weight, ok2 := p[1].(float64)
|
||||
if !ok1 || !ok2 || from < 0 || from > 100 || weight < 0 {
|
||||
t.Errorf("%s.%s.%s.%s: from must be 0..100 and weight >= 0", group, id, field, sid)
|
||||
}
|
||||
}
|
||||
}
|
||||
if m, ok := def["world"].(obj); ok {
|
||||
for k := range m {
|
||||
if k != "cycle" {
|
||||
t.Errorf("%s.%s.world: unknown key %q", group, id, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := def["drift"].(float64); def["drift"] != nil && !ok {
|
||||
t.Errorf("%s.%s.drift must be a number", group, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
check("indicators", bal["indicators"].(obj))
|
||||
check("vars", bal["vars"].(obj))
|
||||
}
|
||||
Reference in New Issue
Block a user