feat(@projects/@magic-civilization): 🛤️ Rail-1 Phase-0 — project Golden Age state to the HUD

ResourceView gains golden_age_active + golden_age_turns, projected from the
PlayerState GA fields. These are the top_bar.gd:162/169 entity reads now
available via view_json (HUD badge driven by Rust, not the Player entity).
Omitted from the wire when inactive. Per-city culture_stored (city_screen.gd:287)
is DEFERRED: it has no bench CityState backing (culture is a player-level pool in
the reduced model) — a Phase-1 SOT-flip widening, not fabricated here.

Additive (serde defaults). mc-player-api 140/0 incl. a GA round-trip/omission test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-27 08:03:07 -04:00
parent 568e43084b
commit 0d501a3d72
2 changed files with 29 additions and 0 deletions

View file

@ -162,6 +162,10 @@ fn project_resources(player: &mc_state::game_state::PlayerState) -> ResourceView
// p3-26 B1: surface the happiness pool now computed by the turn's
// happiness_phase (was hardcoded 0 before the phase existed).
happiness_pool: player.happiness,
// Rail-1: surface the Golden Age state the HUD badge reads
// (top_bar.gd:162/169) so it comes from view_json, not the entity.
golden_age_active: player.golden_age_active,
golden_age_turns: player.golden_age_turns.max(0) as u32,
stockpile,
}
}

View file

@ -36,10 +36,21 @@ pub struct ResourceView {
pub culture_per_turn: i32,
/// Global happiness pool (positive = surplus).
pub happiness_pool: i32,
/// Whether the empire is in a Golden Age (drives the HUD badge).
#[serde(default, skip_serializing_if = "core::ops::Not::not")]
pub golden_age_active: bool,
/// Golden-age turns remaining (`0` when not active).
#[serde(default, skip_serializing_if = "crate::view::is_zero_u32")]
pub golden_age_turns: u32,
/// Strategic-resource stockpile (resource_id → amount).
pub stockpile: std::collections::BTreeMap<String, i32>,
}
/// serde helper: skip `u32` fields that are zero (wire economy).
pub(crate) fn is_zero_u32(v: &u32) -> bool {
*v == 0
}
/// Research / tech tab state.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ResearchView {
@ -477,6 +488,20 @@ mod tests {
assert!(!json.contains("posture"), "resting posture must be omitted: {json}");
}
#[test]
fn resource_view_golden_age_round_trips_and_omits_when_inactive() {
let mut r = ResourceView::default();
assert!(!serde_json::to_string(&r).unwrap().contains("golden_age"),
"inactive golden age omitted from wire");
r.golden_age_active = true;
r.golden_age_turns = 8;
let json = serde_json::to_string(&r).unwrap();
assert!(json.contains("\"golden_age_active\":true"), "json={json}");
assert!(json.contains("\"golden_age_turns\":8"), "json={json}");
let back: ResourceView = serde_json::from_str(&json).unwrap();
assert_eq!(r, back);
}
#[test]
fn unit_posture_serialized_only_when_active() {
let mut u = UnitView {