weiss_core/env/visibility/
replay.rs1use super::super::GameEnv;
2use crate::encode::{action_id_for, ACTION_ENCODING_VERSION, OBS_ENCODING_VERSION};
3use crate::events::Event;
4use crate::legal::ActionDesc;
5use crate::replay::{
6 EpisodeBody, EpisodeHeader, ReplayData, ReplayEvent, ReplayFinal, ReplayVisibilityMode,
7 REPLAY_ACTION_ID_UNKNOWN, REPLAY_SCHEMA_VERSION,
8};
9use crate::state::TerminalResult;
10
11impl GameEnv {
12 pub(in crate::env) fn log_event(&mut self, event: Event) {
13 if self.recording {
14 let ctx = self.replay_visibility_context();
15 self.canonical_events.push(event.clone());
16 let replay_event = self.sanitize_event_for_viewer(&event, ctx);
17 self.replay_events.push(replay_event);
18 }
19 if self.debug_event_ring.is_some() {
20 let mut sanitized = [None, None];
21 for viewer in 0..2u8 {
22 let ctx = self.debug_visibility_context(viewer);
23 sanitized[viewer as usize] = Some(self.sanitize_event_for_viewer(&event, ctx));
24 }
25 if let Some(rings) = self.debug_event_ring.as_mut() {
26 for viewer in 0..2u8 {
27 if let Some(entry) = sanitized[viewer as usize].take() {
28 rings[viewer as usize].push(entry);
29 }
30 }
31 }
32 }
33 }
34
35 pub(in crate::env) fn log_action(&mut self, actor: u8, action: ActionDesc) {
36 if !self.recording || !self.replay_config.store_actions {
37 return;
38 }
39 let ctx = self.replay_visibility_context();
40 self.replay_actions_raw.push(action.clone());
41 let logged = self.sanitize_action_for_viewer(&action, actor, ctx);
42 self.replay_actions.push(logged);
43 let raw_id = action_id_for(&action)
44 .and_then(|id| u16::try_from(id).ok())
45 .unwrap_or(REPLAY_ACTION_ID_UNKNOWN);
46 let public_id = self
47 .replay_actions
48 .last()
49 .and_then(action_id_for)
50 .and_then(|id| u16::try_from(id).ok())
51 .unwrap_or(REPLAY_ACTION_ID_UNKNOWN);
52 self.replay_action_ids_raw.push(raw_id);
53 self.replay_action_ids.push(public_id);
54 }
55
56 pub fn finish_episode_replay(&mut self) {
58 if !self.recording {
59 return;
60 }
61 if self.state.terminal.is_some() {
62 let need_terminal = !self
63 .replay_events
64 .iter()
65 .any(|e| matches!(e, ReplayEvent::Terminal { .. }));
66 if need_terminal {
67 let winner = match self.state.terminal {
68 Some(TerminalResult::Win { winner }) => Some(winner),
69 Some(TerminalResult::Draw | TerminalResult::Timeout) => None,
70 None => None,
71 };
72 self.log_event(Event::Terminal { winner });
73 }
74 }
75 let writer = self.replay_writer.clone();
76 if let Some(writer) = writer {
77 let header = EpisodeHeader {
78 obs_version: OBS_ENCODING_VERSION,
79 action_version: ACTION_ENCODING_VERSION,
80 replay_version: REPLAY_SCHEMA_VERSION,
81 seed: self.episode_seed,
82 base_seed: self.base_seed,
83 episode_seed: self.episode_seed,
84 spec_hash: crate::encode::SPEC_HASH,
85 starting_player: self.state.turn.starting_player,
86 deck_ids: self.config.deck_ids,
87 curriculum_id: "default".to_string(),
88 config_hash: self.config.config_hash(&self.curriculum),
89 fingerprint_algo: crate::fingerprint::FINGERPRINT_ALGO.to_string(),
90 env_id: self.env_id,
91 episode_index: self.episode_index,
92 };
93 let (actions, action_ids, events) = match self.replay_config.visibility_mode {
94 ReplayVisibilityMode::Full => (
95 if self.replay_config.store_actions {
96 self.replay_actions_raw.clone()
97 } else {
98 Vec::new()
99 },
100 if self.replay_config.store_actions {
101 self.replay_action_ids_raw.clone()
102 } else {
103 Vec::new()
104 },
105 Some(self.canonical_events.clone()),
106 ),
107 ReplayVisibilityMode::Public => (
108 if self.replay_config.store_actions {
109 self.replay_actions.clone()
110 } else {
111 Vec::new()
112 },
113 if self.replay_config.store_actions {
114 self.replay_action_ids.clone()
115 } else {
116 Vec::new()
117 },
118 Some(self.replay_events.clone()),
119 ),
120 };
121 let body = EpisodeBody {
122 actions,
123 action_ids,
124 events,
125 steps: self.replay_steps.clone(),
126 final_state: Some(ReplayFinal {
127 terminal: self.state.terminal,
128 state_hash: crate::fingerprint::state_fingerprint(&self.state),
129 decision_count: self.state.turn.decision_count,
130 tick_count: self.state.turn.tick_count,
131 }),
132 };
133 if let Err(err) = writer.send(ReplayData { header, body }) {
134 eprintln!("Replay enqueue failed: {err}");
135 }
136 }
137 self.recording = false;
138 }
139}