@@ -813,6 +813,14 @@ static func format_score(turns: int, integral: int) -> String:
813813# fidelity for mid-game save/load (and later DQ-11 fork/divergence).
814814# ============================================================================
815815
816+ static func _snap_array (arr ) -> Array :
817+ ## Serialization-boundary float snap for arrays (see DoomSystem.SAVE_QUANTUM).
818+ var out : Array = []
819+ for v in arr :
820+ out .append (DoomSystem ._snap (float (v )))
821+ return out
822+
823+
816824func to_dict () -> Dictionary :
817825 """Serialize state for UI + save/load (see convention block above)"""
818826 var rival_summaries = []
@@ -831,7 +839,7 @@ func to_dict() -> Dictionary:
831839 var doom_data = {}
832840 if doom_system :
833841 doom_data = doom_system .to_dict ()
834- doom_data ["doom" ] = doom
842+ doom_data ["doom" ] = DoomSystem . _snap ( doom )
835843 doom_data ["doom_trend" ] = doom_system ._get_doom_trend ()
836844 doom_data ["doom_status" ] = doom_system .get_doom_status ()
837845 doom_data ["momentum_description" ] = doom_system .get_momentum_description ()
@@ -874,18 +882,23 @@ func to_dict() -> Dictionary:
874882 "ledger" : ledger .to_dict () if ledger else {},
875883 "month_plan" : month_plan .to_dict () if month_plan else {}, # L1/ADR-0009 Attention + reserve + WIP
876884 "cause_log" : cause_log .duplicate (true ), # EE-8 attribution trail
877- "doom" : doom ,
878- "doom_history" : doom_history .duplicate (), # #512 trend graph
885+ # Doom-adjacent floats are SNAPPED at the serialization boundary (both directions,
886+ # same quantum as DoomSystem.SAVE_QUANTUM): the stream model produces full-precision
887+ # doubles, and Godot's JSON parse is not correctly-rounded (calibration §7.2) — a
888+ # 1-ulp corrupted parse re-snaps to the identical double, keeping save/load
889+ # deep-equality byte-stable. Live dynamics never see the quantum.
890+ "doom" : DoomSystem ._snap (doom ),
891+ "doom_history" : _snap_array (doom_history ), # #512 trend graph
879892 # ADR-0015 / DQ-21 world-state intermediaries (doom is computed from these)
880- "ambient_risk" : ambient_risk ,
881- "frontier_capability" : frontier_capability . duplicate ( ),
882- "general_capability" : general_capability ,
883- "global_compute" : global_compute ,
884- "dedicated_ai_compute" : dedicated_ai_compute ,
885- "safety_absorption" : safety_absorption ,
886- "global_alarm" : global_alarm ,
887- "global_panic" : global_panic ,
888- "political_pressure" : political_pressure ,
893+ "ambient_risk" : DoomSystem . _snap ( ambient_risk ) ,
894+ "frontier_capability" : DoomSystem . _snap_dict ( frontier_capability ),
895+ "general_capability" : DoomSystem . _snap ( general_capability ) ,
896+ "global_compute" : DoomSystem . _snap ( global_compute ) ,
897+ "dedicated_ai_compute" : DoomSystem . _snap ( dedicated_ai_compute ) ,
898+ "safety_absorption" : DoomSystem . _snap ( safety_absorption ) ,
899+ "global_alarm" : DoomSystem . _snap ( global_alarm ) ,
900+ "global_panic" : DoomSystem . _snap ( global_panic ) ,
901+ "political_pressure" : DoomSystem . _snap ( political_pressure ) ,
889902 "doom_dampers" : doom_dampers .duplicate (true ),
890903 "doom_pulses" : doom_pulses .duplicate (true ),
891904 "sacred_chain_log" : sacred_chain_log .duplicate (true ),
@@ -910,7 +923,7 @@ func to_dict() -> Dictionary:
910923 "management_capacity" : get_management_capacity (),
911924 "unmanaged_count" : get_unmanaged_count (),
912925 "turn" : turn ,
913- "doom_integral" : doom_integral ,
926+ "doom_integral" : DoomSystem . _snap ( doom_integral ) ,
914927 "turn_display" : get_turn_display (),
915928 "calendar" : get_current_date (),
916929 "game_over" : game_over ,
@@ -955,22 +968,24 @@ func from_dict(data: Dictionary) -> void:
955968 papers = float (data .get ("papers" , 0.0 ))
956969 reputation = float (data .get ("reputation" , 10.0 ))
957970 governance = float (data .get ("governance" , 50.0 )) # was forgotten pre-L7
958- doom = float (data .get ("doom" , 50.0 ))
971+ # Doom-adjacent floats re-snap on load (idempotent under the 1-ulp JSON parse
972+ # corruption — see the to_dict comment + DoomSystem.SAVE_QUANTUM).
973+ doom = DoomSystem ._snap (float (data .get ("doom" , 50.0 )))
959974 doom_history .clear ()
960975 for d in data .get ("doom_history" , []):
961- doom_history .append (float (d ))
976+ doom_history .append (DoomSystem . _snap ( float (d ) ))
962977 # ADR-0015 / DQ-21 world-state intermediaries
963- ambient_risk = float (data .get ("ambient_risk" , Balance .num ("doom.base_per_turn" , 0.06 )))
964- frontier_capability = (data .get ("frontier_capability" , {"player" : 0.0 }) as Dictionary ). duplicate ( )
978+ ambient_risk = DoomSystem . _snap ( float (data .get ("ambient_risk" , Balance .num ("doom.base_per_turn" , 0.06 ) )))
979+ frontier_capability = DoomSystem . _snap_dict (data .get ("frontier_capability" , {"player" : 0.0 }) as Dictionary )
965980 if not frontier_capability .has ("player" ):
966981 frontier_capability ["player" ] = 0.0
967- general_capability = float (data .get ("general_capability" , 0.0 ))
968- global_compute = float (data .get ("global_compute" , 0.0 ))
969- dedicated_ai_compute = float (data .get ("dedicated_ai_compute" , 0.0 ))
970- safety_absorption = float (data .get ("safety_absorption" , 0.0 ))
971- global_alarm = float (data .get ("global_alarm" , 0.0 ))
972- global_panic = float (data .get ("global_panic" , 0.0 ))
973- political_pressure = float (data .get ("political_pressure" , 0.0 ))
982+ general_capability = DoomSystem . _snap ( float (data .get ("general_capability" , 0.0 ) ))
983+ global_compute = DoomSystem . _snap ( float (data .get ("global_compute" , 0.0 ) ))
984+ dedicated_ai_compute = DoomSystem . _snap ( float (data .get ("dedicated_ai_compute" , 0.0 ) ))
985+ safety_absorption = DoomSystem . _snap ( float (data .get ("safety_absorption" , 0.0 ) ))
986+ global_alarm = DoomSystem . _snap ( float (data .get ("global_alarm" , 0.0 ) ))
987+ global_panic = DoomSystem . _snap ( float (data .get ("global_panic" , 0.0 ) ))
988+ political_pressure = DoomSystem . _snap ( float (data .get ("political_pressure" , 0.0 ) ))
974989 doom_dampers = (data .get ("doom_dampers" , []) as Array ).duplicate (true )
975990 doom_pulses = (data .get ("doom_pulses" , []) as Array ).duplicate (true )
976991 sacred_chain_log = (data .get ("sacred_chain_log" , []) as Array ).duplicate (true )
@@ -990,7 +1005,7 @@ func from_dict(data: Dictionary) -> void:
9901005
9911006 # Game state
9921007 turn = int (data .get ("turn" , 0 ))
993- doom_integral = float (data .get ("doom_integral" , 0.0 ))
1008+ doom_integral = DoomSystem . _snap ( float (data .get ("doom_integral" , 0.0 ) ))
9941009 game_over = bool (data .get ("game_over" , false ))
9951010 victory = bool (data .get ("victory" , false ))
9961011 has_cat = bool (data .get ("has_cat" , false ))
0 commit comments