feat(@projects/@magic-civilization): 🎭 p3-29 T1 — Rust turn emits CultureResearched
The live GDScript turn emitted `culture_researched` inline; the headless Rust turn dropped tradition completions. Emit a `TurnEvent::CultureResearched` at the completion site in `process_culture_research` (single-source per Rail-1), translate it to the existing `wire::Event::CultureResearched` in dispatch (not dropped), and surface it as a kind-tagged dict in `event_to_dict` so the live turn_manager will receive it at the Rail-1 swap. Threaded the events sink into the phase + both call sites. Verified headless: mc-replay 18/0 (culture_researched_serde), mc-turn 287/0 (event_collector_wiring), mc-player-api 138/0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6e3d9b2fd2
commit
74844f74d3
5 changed files with 70 additions and 3 deletions
|
|
@ -147,6 +147,12 @@ pub(crate) fn event_to_dict(evt: &TurnEvent) -> Dictionary {
|
|||
d.set("clan", clan.0 as i64);
|
||||
d.set("tech", GString::from(tech.0.as_str()));
|
||||
}
|
||||
TurnEvent::CultureResearched { turn, clan, tradition } => {
|
||||
d.set("kind", GString::from("CultureResearched"));
|
||||
d.set("turn", *turn as i64);
|
||||
d.set("clan", clan.0 as i64);
|
||||
d.set("tradition", GString::from(tradition.as_str()));
|
||||
}
|
||||
TurnEvent::AmbientEncounterFired { turn, clan, hex, species, group_size } => {
|
||||
d.set("kind", GString::from("AmbientEncounterFired"));
|
||||
d.set("turn", *turn as i64);
|
||||
|
|
|
|||
|
|
@ -841,6 +841,15 @@ fn translate_processor_events(events: &[mc_replay::TurnEvent]) -> Vec<Event> {
|
|||
player: clan.0 as PlayerId,
|
||||
});
|
||||
}
|
||||
// p3-29 (T1): the Rust turn now emits tradition completions; the
|
||||
// wire `Event::CultureResearched` already exists, so translate
|
||||
// (not drop) so the live UI / replay surface receives it.
|
||||
mc_replay::TurnEvent::CultureResearched { clan, tradition, .. } => {
|
||||
out.push(Event::CultureResearched {
|
||||
tradition_id: tradition.clone(),
|
||||
player: clan.0 as PlayerId,
|
||||
});
|
||||
}
|
||||
// p2-67 Bug 4: surface non-wonder building completions emitted
|
||||
// from `process_city_production`'s `Queueable::Item` branch.
|
||||
// The wire `Event::CityBuildingCompleted` already exists for
|
||||
|
|
|
|||
|
|
@ -148,6 +148,18 @@ pub enum TurnEvent {
|
|||
/// Tier after.
|
||||
to_tier: i32,
|
||||
},
|
||||
/// p3-29 (T1): a clan completed a culture tradition (single-source
|
||||
/// replacement for the GDScript turn's inline `culture_researched`
|
||||
/// signal). `wire::Event::CultureResearched` already exists, so dispatch
|
||||
/// translates this rather than dropping it.
|
||||
CultureResearched {
|
||||
/// Turn the event fired on.
|
||||
turn: u32,
|
||||
/// Clan that completed the tradition.
|
||||
clan: ClanId,
|
||||
/// Tradition node now unlocked.
|
||||
tradition: String,
|
||||
},
|
||||
/// A wonder finished construction.
|
||||
WonderBuilt {
|
||||
/// Turn the event fired on.
|
||||
|
|
@ -543,6 +555,7 @@ impl TurnEvent {
|
|||
| Self::CityGrew { turn, .. }
|
||||
| Self::CityBordersExpanded { turn, .. }
|
||||
| Self::FloraSuccession { turn, .. }
|
||||
| Self::CultureResearched { turn, .. }
|
||||
| Self::WonderBuilt { turn, .. }
|
||||
| Self::WarDeclared { turn, .. }
|
||||
| Self::PeaceSigned { turn, .. }
|
||||
|
|
@ -688,6 +701,28 @@ mod tests {
|
|||
assert_eq!(decoded, events);
|
||||
}
|
||||
|
||||
/// p3-29 (T1): verify `CultureResearched` survives a JSON + bincode
|
||||
/// serde round-trip and `turn()` returns its turn.
|
||||
#[test]
|
||||
fn culture_researched_serde() {
|
||||
let ev = TurnEvent::CultureResearched {
|
||||
turn: 7,
|
||||
clan: ClanId(2),
|
||||
tradition: "oral_tradition".into(),
|
||||
};
|
||||
assert_eq!(ev.turn(), 7);
|
||||
|
||||
let json = serde_json::to_string(&ev).expect("serialize");
|
||||
let back: TurnEvent = serde_json::from_str(&json).expect("deserialize");
|
||||
assert_eq!(back, ev);
|
||||
|
||||
let cfg = bincode::config::standard();
|
||||
let bytes = bincode::serde::encode_to_vec(&ev, cfg).expect("encode");
|
||||
let (decoded, _): (TurnEvent, usize) =
|
||||
bincode::serde::decode_from_slice(&bytes, cfg).expect("decode");
|
||||
assert_eq!(decoded, ev);
|
||||
}
|
||||
|
||||
/// p2-55: verify that `UnitCaptured`, `UnitRansomOffered`, and
|
||||
/// `CivilianDestroyed` survive a JSON serde round-trip, and that
|
||||
/// `turn()` returns the correct value for each.
|
||||
|
|
|
|||
|
|
@ -435,7 +435,7 @@ impl TurnProcessor {
|
|||
self.try_found_city(state, pi, &mut result.events_emitted);
|
||||
self.try_spawn_unit(state, pi, &mut result);
|
||||
self.process_culture(state, pi, &mut result.events_emitted);
|
||||
self.process_culture_research(state, pi);
|
||||
self.process_culture_research(state, pi, &mut result.events_emitted);
|
||||
self.process_science(state, pi, &mut result.events_emitted);
|
||||
}
|
||||
|
||||
|
|
@ -1247,12 +1247,18 @@ impl TurnProcessor {
|
|||
// the flat `researching_tradition` / `culture_research_progress` /
|
||||
// `researched_traditions` fields so GDScript and bench reads stay
|
||||
// in sync.
|
||||
fn process_culture_research(&self, state: &mut GameState, pi: usize) {
|
||||
fn process_culture_research(
|
||||
&self,
|
||||
state: &mut GameState,
|
||||
pi: usize,
|
||||
events: &mut Vec<mc_replay::TurnEvent>,
|
||||
) {
|
||||
let web = match self.culture_web_parsed.as_ref() {
|
||||
Some(w) => w,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let turn = state.turn;
|
||||
let player = &mut state.players[pi];
|
||||
let culture_axis = *player.strategic_axes.get("culture").unwrap_or(&2);
|
||||
let per_city_yield = culture_axis as f64 * Self::BENCH_CULTURE_PER_AXIS_POINT;
|
||||
|
|
@ -1282,6 +1288,15 @@ impl TurnProcessor {
|
|||
let result = pc.add_science(gained, web);
|
||||
match result {
|
||||
mc_culture::CultureResearchResult::Completed { tech_id, .. } => {
|
||||
// p3-29 (T1): single-source replacement for the GDScript turn's
|
||||
// inline `culture_researched` signal. Emit at the completion site
|
||||
// that already decided it — dispatch translates to the existing
|
||||
// `wire::Event::CultureResearched`.
|
||||
events.push(mc_replay::TurnEvent::CultureResearched {
|
||||
turn,
|
||||
clan: mc_replay::ClanId(pi as u32),
|
||||
tradition: tech_id.clone(),
|
||||
});
|
||||
player.researched_traditions.insert(tech_id);
|
||||
player.researching_tradition = pc.current_research().unwrap_or("").to_string();
|
||||
player.culture_research_progress = pc.research_progress();
|
||||
|
|
@ -2713,7 +2728,7 @@ impl TurnProcessor {
|
|||
self.try_found_city(state, pi, &mut result.events_emitted);
|
||||
self.try_spawn_unit(state, pi, &mut result);
|
||||
self.process_culture(state, pi, &mut result.events_emitted);
|
||||
self.process_culture_research(state, pi);
|
||||
self.process_culture_research(state, pi, &mut result.events_emitted);
|
||||
self.process_science(state, pi, &mut result.events_emitted);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -209,6 +209,7 @@ fn ten_turn_run_emits_each_wired_variant() {
|
|||
TurnEvent::CityGrew { .. } => "CityGrew",
|
||||
TurnEvent::CityBordersExpanded { .. } => "CityBordersExpanded",
|
||||
TurnEvent::FloraSuccession { .. } => "FloraSuccession",
|
||||
TurnEvent::CultureResearched { .. } => "CultureResearched",
|
||||
TurnEvent::CityFounded { .. } => "CityFounded",
|
||||
TurnEvent::WonderBuilt { .. } => "WonderBuilt",
|
||||
TurnEvent::CityCaptured { .. } => "CityCaptured",
|
||||
|
|
@ -328,6 +329,7 @@ fn events_emitted_appears_on_turn_result() {
|
|||
TurnEvent::CityGrew { .. } => "CityGrew",
|
||||
TurnEvent::CityBordersExpanded { .. } => "CityBordersExpanded",
|
||||
TurnEvent::FloraSuccession { .. } => "FloraSuccession",
|
||||
TurnEvent::CultureResearched { .. } => "CultureResearched",
|
||||
TurnEvent::CityFounded { .. } => "CityFounded",
|
||||
TurnEvent::WonderBuilt { .. } => "WonderBuilt",
|
||||
TurnEvent::CityCaptured { .. } => "CityCaptured",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue