magicciv/.project/objectives/p1-29c-followup-empty-params-json-regression.md
Natalie a7e07f9b0a fix(@projects/@magic-civilization): 🐛 fix empty params json regression
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-05-15 23:41:53 -07:00

4.8 KiB

id title priority status scope category owner created updated_at blocked_by follow_ups
p1-29c-followup-empty-params-json-regression GdEconomy::process_turn fails — `_build_params_json` produces empty string for autoplay seeds p1 done game1 bug shipwright 2026-05-14 2026-05-15
p1-29c

Context

p1-29c autoplay batch ran on apricot 2026-05-14 (autoplay_batch_p1_29c-1778813509, DONE 20:08). All 12 seeds FAILED the E2E gate with hundreds of repeated errors:

ERROR: GdEconomy::process_turn params_json parse: EOF while parsing a value at line 1 column 0

The cities_json + units_json args parsed OK (no preceding parse errors logged) — only params_json is empty.

Caller: src/game/engine/src/modules/empire/economy.gd:43

var params_json: String = _build_params_json(player)
var result: Dictionary = gd_economy.process_turn(cities_json, units_json, params_json)

_build_params_json ends with JSON.stringify({"yield_mult": GameState.get_effective_yield_mult(player, "gold"), ...}). Empty string output from JSON.stringify happens on degenerate inputs (NaN, +/-Inf, cyclic refs). Most likely: get_effective_yield_mult returns NaN for some clan-personality combination.

Acceptance

  • ✓ Reproduce: pick one failing seed, run with verbose logging, capture the _build_params_json inputs that trigger the empty stringify. — Inspection: GameState.get_effective_yield_mult was called from 5 sites (economy.gd:112, turn_processor.gd:51/153/353/398) but never defined anywhere in the codebase (grep -rn "func get_effective_yield_mult" src/ → 0 hits). Calling a missing method on the GameState autoload pushes a runtime error and returns null; the surrounding GDScript evaluation poisons the Dictionary literal, and JSON.stringify emits "". Not NaN/Inf as the prior hypothesis suggested — missing function entirely.
  • ✓ Identify the input that breaks stringify — GameState.get_effective_yield_mult(player, "gold") at economy.gd:112 (and the four turn_processor.gd sites). Function symbol absent from src/game/engine/src/autoloads/game_state.gd.
  • ✓ Fix at source — added GameState.get_effective_yield_mult(player, yield_kind) returning the documented per-yield multiplier: "production"ai_difficulty_modifier (+ per-player override via ai_per_player_production_mult[player.index]), "research"ai_research_modifier (+ override), all other kinds → 1.0 (Rust-side default at api-gdext/src/lib.rs:6178). NaN/Inf/negative results clamp to 1.0 with push_warning. Belt-and-suspenders defensive sanitizer added in economy.gd::_build_params_json (same NaN/Inf/negative clamp) so any future regression cannot reproduce the empty-stringify failure mode.
  • ✓ Re-run autoplay-batch.sh 10 300 on apricot; E2E gate ≥10/12 PASS — verified on batch 20260515_215705 (10/10 PASS, after subsequent update_tile + set_map fixes in commits e200634df + 8820ce04a).
  • ✓ Then unblock p1-29c final acceptance bullet — bullet is now MEASURABLE (apricot infrastructure clean), result is 0/10 PASS (P1 stuck at tier_peak=1 in all 10 seeds, eliminated before tier 2 in 80%). Documented in p1-29c itself; not p1-29c-followup's bullet to flip.

Verification (2026-05-15)

Apricot smoke batch 20260515_072145 launched via scripts/apricot-run.sh launch smoke 10 300.

Behavioral side-effect note (recorded for follow-up triage): the four other call sites of get_effective_yield_mult in turn_processor.gd (production, research, two culture sites) were also hitting the missing function and silently degrading. Restoring the function returns ai_difficulty_modifier / ai_research_modifier / 1.0 to those paths, so per-yield AI scaling now applies as originally intended (warcouncil p1-29 H4 / p1-31). If this batch reveals a different-flavored regression (e.g. AI runaway / production stalls), it is downstream of restoring the intended multiplier path and belongs in a new follow-up rather than re-fixing here.

PASS verdict (filled in on batch completion):

<pending — batch in flight; see scripts/apricot-run.sh status 20260515_072145>

References

  • Batch log: apricot:~/.local/var/p1_29c/run.log
  • Batch dir: apricot:/var/home/lilith/Code/project-buildspace/magic-civilization/.local/batches/autoplay_batch_p1_29c-1778813509/
  • Caller: src/game/engine/src/modules/empire/economy.gd:_build_params_json
  • Sink: src/simulator/api-gdext/src/lib.rs:6221 (the godot_error site).

Why this blocks p1-29c closure

The batch infrastructure must produce clean games before the tier-peak gate (p1-29c bullet 1) can be meaningfully measured. With 0/12 PASS this run, no tier_peak data was captured — p1-29c bullet 1 stays ✗ until the regression is fixed and a follow-up batch succeeds.