@@ -59,6 +59,10 @@ pub struct EpuDebugPanel {
5959 preset_manager : PresetManager ,
6060 /// Preset UI state
6161 preset_ui_state : PresetUiState ,
62+ /// Snapshot of game configs (for display when locked)
63+ pub snapshot_configs : hashbrown:: HashMap < u32 , EpuConfig > ,
64+ /// Whether lock mode is active (debugger config replaces ALL game configs)
65+ pub locked : bool ,
6266}
6367
6468impl Default for EpuDebugPanel {
@@ -86,9 +90,26 @@ impl EpuDebugPanel {
8690 editing_env_id : None ,
8791 preset_manager : PresetManager :: default ( ) ,
8892 preset_ui_state : PresetUiState :: default ( ) ,
93+ snapshot_configs : hashbrown:: HashMap :: new ( ) ,
94+ locked : false ,
8995 }
9096 }
9197
98+ /// Update snapshot with current game configs
99+ pub fn update_snapshot ( & mut self , game_configs : & hashbrown:: HashMap < u32 , EpuConfig > ) {
100+ self . snapshot_configs . clone_from ( game_configs) ;
101+ }
102+
103+ /// Check if lock mode is active
104+ pub fn is_locked ( & self ) -> bool {
105+ self . locked
106+ }
107+
108+ /// Get the debugger's override config (from editor state)
109+ pub fn get_override_config ( & self ) -> EpuConfig {
110+ self . editor . export_config ( )
111+ }
112+
92113 /// Toggle panel visibility
93114 pub fn toggle ( & mut self ) {
94115 self . visible = !self . visible ;
@@ -107,28 +128,51 @@ impl EpuDebugPanel {
107128 /// Render the EPU debug panel
108129 ///
109130 /// Returns true if any value was changed (for future integration with live editing)
110- pub fn render (
111- & mut self ,
112- ctx : & egui:: Context ,
113- epu_configs : & hashbrown:: HashMap < u32 , EpuConfig > ,
114- ) -> bool {
131+ pub fn render ( & mut self , ctx : & egui:: Context ) -> bool {
115132 if !self . visible {
116133 return false ;
117134 }
118135
119136 let mut changed = false ;
120137
138+ // Capture config count before closure to avoid borrow conflicts
139+ let config_count = self . snapshot_configs . len ( ) ;
140+
121141 egui:: Window :: new ( "EPU Debug Panel" )
122142 . id ( egui:: Id :: new ( "epu_debug_panel" ) )
123143 . default_pos ( [ 10.0 , 300.0 ] )
124144 . default_size ( [ 500.0 , 600.0 ] )
125145 . resizable ( true )
126146 . collapsible ( true )
127147 . show ( ctx, |ui| {
128- // Header with view mode tabs
148+ // Lock mode banner (prominent when active)
149+ if self . locked {
150+ ui. horizontal ( |ui| {
151+ ui. add_space ( 4.0 ) ;
152+ let banner = egui:: RichText :: new ( "LOCKED - Game EPU disabled, using debugger config" )
153+ . color ( egui:: Color32 :: WHITE )
154+ . strong ( ) ;
155+ ui. colored_label ( egui:: Color32 :: from_rgb ( 200 , 80 , 40 ) , banner) ;
156+ } ) ;
157+ ui. add_space ( 2.0 ) ;
158+ }
159+
160+ // Header with view mode tabs and lock toggle
129161 ui. horizontal ( |ui| {
130162 ui. heading ( "Environment Processing Unit" ) ;
131163 ui. with_layout ( egui:: Layout :: right_to_left ( egui:: Align :: Center ) , |ui| {
164+ // Lock toggle (prominent)
165+ let lock_text = if self . locked {
166+ egui:: RichText :: new ( "LOCK" ) . color ( egui:: Color32 :: from_rgb ( 255 , 120 , 60 ) ) . strong ( )
167+ } else {
168+ egui:: RichText :: new ( "LOCK" ) . color ( egui:: Color32 :: GRAY )
169+ } ;
170+ if ui. checkbox ( & mut self . locked , lock_text) . changed ( ) {
171+ changed = true ;
172+ }
173+
174+ ui. separator ( ) ;
175+
132176 if ui
133177 . selectable_label ( self . view_mode == PanelView :: Editor , "Editor" )
134178 . clicked ( )
@@ -150,7 +194,12 @@ impl EpuDebugPanel {
150194 ui. horizontal ( |ui| {
151195 ui. label ( format ! ( "Defined opcodes: {}" , OPCODE_COUNT ) ) ;
152196 ui. separator ( ) ;
153- ui. label ( format ! ( "Active configs: {}" , epu_configs. len( ) ) ) ;
197+ let config_label = if self . locked {
198+ "Game configs (snapshot): " . to_string ( )
199+ } else {
200+ "Active configs: " . to_string ( )
201+ } ;
202+ ui. label ( format ! ( "{}{}" , config_label, config_count) ) ;
154203 if self . editor . dirty {
155204 ui. separator ( ) ;
156205 ui. colored_label ( egui:: Color32 :: YELLOW , "* Modified" ) ;
@@ -177,16 +226,13 @@ impl EpuDebugPanel {
177226 self . render_opcode_details ( & mut columns[ 1 ] , opcode) ;
178227 } else {
179228 columns[ 1 ] . heading ( "Active Configs" ) ;
180- self . render_active_configs_with_edit (
181- & mut columns[ 1 ] ,
182- epu_configs,
183- ) ;
229+ self . render_active_configs_with_edit ( & mut columns[ 1 ] ) ;
184230 }
185231 } ) ;
186232 }
187233 PanelView :: Editor => {
188234 // Semantic editor view
189- changed |= self . render_editor_view ( ui, epu_configs ) ;
235+ changed |= self . render_editor_view ( ui) ;
190236 }
191237 }
192238 } ) ;
@@ -195,14 +241,10 @@ impl EpuDebugPanel {
195241 }
196242
197243 /// Render the semantic editor view
198- fn render_editor_view (
199- & mut self ,
200- ui : & mut egui:: Ui ,
201- epu_configs : & hashbrown:: HashMap < u32 , EpuConfig > ,
202- ) -> bool {
244+ fn render_editor_view ( & mut self , ui : & mut egui:: Ui ) -> bool {
203245 let mut changed = false ;
204246
205- // Environment selector
247+ // Environment selector (uses snapshot configs)
206248 ui. horizontal ( |ui| {
207249 ui. label ( "Environment:" ) ;
208250
@@ -211,6 +253,7 @@ impl EpuDebugPanel {
211253 None => "New Config" . to_string ( ) ,
212254 } ;
213255
256+ let env_ids: Vec < u32 > = self . snapshot_configs . keys ( ) . copied ( ) . collect ( ) ;
214257 egui:: ComboBox :: from_id_salt ( "env_selector" )
215258 . selected_text ( current_text)
216259 . show_ui ( ui, |ui| {
@@ -224,8 +267,8 @@ impl EpuDebugPanel {
224267
225268 ui. separator ( ) ;
226269
227- // Existing configs
228- for & env_id in epu_configs . keys ( ) {
270+ // Existing configs from snapshot
271+ for env_id in env_ids {
229272 if ui
230273 . selectable_value (
231274 & mut self . editing_env_id ,
@@ -234,7 +277,7 @@ impl EpuDebugPanel {
234277 )
235278 . clicked ( )
236279 {
237- if let Some ( config) = epu_configs . get ( & env_id) {
280+ if let Some ( config) = self . snapshot_configs . get ( & env_id) {
238281 self . editor . load_config ( config) ;
239282 }
240283 }
@@ -244,7 +287,7 @@ impl EpuDebugPanel {
244287 // Load button
245288 if let Some ( env_id) = self . editing_env_id {
246289 if ui. button ( "Reload" ) . clicked ( ) {
247- if let Some ( config) = epu_configs . get ( & env_id) {
290+ if let Some ( config) = self . snapshot_configs . get ( & env_id) {
248291 self . editor . load_config ( config) ;
249292 }
250293 }
@@ -284,13 +327,9 @@ impl EpuDebugPanel {
284327 changed
285328 }
286329
287- /// Render active configs with edit buttons
288- fn render_active_configs_with_edit (
289- & mut self ,
290- ui : & mut egui:: Ui ,
291- configs : & hashbrown:: HashMap < u32 , EpuConfig > ,
292- ) {
293- if configs. is_empty ( ) {
330+ /// Render active configs with edit buttons (uses snapshot_configs)
331+ fn render_active_configs_with_edit ( & mut self , ui : & mut egui:: Ui ) {
332+ if self . snapshot_configs . is_empty ( ) {
294333 ui. label ( "No active EPU configs this frame" ) ;
295334 ui. label ( "" ) ;
296335 ui. label ( "EPU configs are created via epu_set() FFI calls." ) ;
@@ -305,10 +344,17 @@ impl EpuDebugPanel {
305344 return ;
306345 }
307346
347+ // Collect config data to avoid borrow conflicts
348+ let config_entries: Vec < ( u32 , EpuConfig ) > = self
349+ . snapshot_configs
350+ . iter ( )
351+ . map ( |( & id, cfg) | ( id, cfg. clone ( ) ) )
352+ . collect ( ) ;
353+
308354 egui:: ScrollArea :: vertical ( )
309355 . id_salt ( "epu_active_configs" )
310356 . show ( ui, |ui| {
311- for ( env_id, config) in configs . iter ( ) {
357+ for ( env_id, config) in config_entries . iter ( ) {
312358 ui. horizontal ( |ui| {
313359 ui. collapsing ( format ! ( "Environment {}" , env_id) , |ui| {
314360 self . render_config_summary ( ui, config) ;
0 commit comments