diff --git a/code/__defines/zmimic.dm b/code/__defines/zmimic.dm index 5f679f43597..4f2998f1542 100644 --- a/code/__defines/zmimic.dm +++ b/code/__defines/zmimic.dm @@ -1,7 +1,28 @@ +#define ZM_DESTRUCTION_TIMER(TARGET) addtimer(CALLBACK(TARGET, TYPE_PROC_REF(/datum, qdel_self)), 10 SECONDS, TIMER_STOPPABLE) #define TURF_IS_MIMICKING(T) (isturf(T) && (T:z_flags & ZM_MIMIC_BELOW)) -#define CHECK_OO_EXISTENCE(OO) if (OO && !TURF_IS_MIMICKING(OO.loc)) { qdel(OO); } +#define CHECK_OO_EXISTENCE(OO) if (OO && !MOVABLE_IS_ON_ZTURF(OO) && !OO.destruction_timer) { OO.destruction_timer = ZM_DESTRUCTION_TIMER(OO); } #define UPDATE_OO_IF_PRESENT CHECK_OO_EXISTENCE(bound_overlay); if (bound_overlay) { update_above(); } +// I do not apologize. + +// These aren't intended to be used anywhere else, they just can't be undef'd because DM is dum. +#define ZM_INTERNAL_SCAN_LOOKAHEAD(M,VTR,F) ((get_step(M, M:dir)?:VTR & F) || (get_step(M, turn(M:dir, 180))?:VTR & F)) +#define ZM_INTERNAL_SCAN_LOOKBESIDE(M,VTR,F) ((get_step(M, turn(M:dir, 90))?:VTR & F) || (get_step(M, turn(M:dir, -90))?:VTR & F)) + +/// Is this movable visible from a turf that is mimicking below? Note: this does not necessarily mean *directly* below. +#define MOVABLE_IS_BELOW_ZTURF(M) (\ + isturf(M:loc) && (TURF_IS_MIMICKING(M:loc:above) \ + || ((M:z_flags & ZMM_LOOKAHEAD) && ZM_INTERNAL_SCAN_LOOKAHEAD(M, above?:z_flags, ZM_MIMIC_BELOW)) \ + || ((M:z_flags & ZMM_LOOKBESIDE) && ZM_INTERNAL_SCAN_LOOKBESIDE(M, above?:z_flags, ZM_MIMIC_BELOW))) \ +) +/// Is this movable located on a turf that is mimicking below? Note: this does not necessarily mean *directly* on. +#define MOVABLE_IS_ON_ZTURF(M) (\ + (TURF_IS_MIMICKING(M:loc) \ + || ((M:z_flags & ZMM_LOOKAHEAD) && ZM_INTERNAL_SCAN_LOOKAHEAD(M, z_flags, ZM_MIMIC_BELOW)) \ + || ((M:z_flags & ZMM_LOOKBESIDE) && ZM_INTERNAL_SCAN_LOOKBESIDE(M, z_flags, ZM_MIMIC_BELOW))) \ +) +#define MOVABLE_SHALL_MIMIC(AM) (!(AM.z_flags & ZMM_IGNORE) && MOVABLE_IS_BELOW_ZTURF(AM)) + // Turf MZ flags. #define ZM_MIMIC_BELOW 1 //! If this turf should mimic the turf on the Z below. #define ZM_MIMIC_OVERWRITE 2 //! If this turf is Z-mimicking, overwrite the turf's appearance instead of using a movable. This is faster, but means the turf cannot have its own appearance (say, edges or a translucent sprite). @@ -9,10 +30,13 @@ #define ZM_ALLOW_ATMOS 8 //! If this turf permits passage of air. #define ZM_MIMIC_NO_AO 16 //! If the turf shouldn't apply regular turf AO and only do Z-mimic AO. #define ZM_NO_OCCLUDE 32 //! Don't occlude below atoms if we're a non-mimic z-turf. -#define ZM_MIMIC_BASETURF 64 //! Mimic baseturf instead of the below atom. Sometimes useful for elevators. +#define ZM_OVERRIDE 64 //! Copy only z_appearance or baseturf and bail, do not attempt to copy movables. This is significantly cheaper and allows you to override the mimic, but results in movables not being visible. +#define ZM_NO_SHADOW 128 //! If this turf is being copied, hide the shadower. +#define ZM_TERMINATOR 256 //! Consider this turf the terminus of a Z-group, like the bottom of a Z-group or a ZM_OVERRIDE turf. -// Convenience flag. -#define ZM_MIMIC_DEFAULTS (ZM_MIMIC_BELOW|ZM_ALLOW_LIGHTING) +// Convenience flags. +#define ZM_MIMIC_DEFAULTS (ZM_MIMIC_BELOW|ZM_ALLOW_LIGHTING) //! Common defaults for zturfs. +#define ZMM_WIDE_LOAD (ZMM_LOOKAHEAD | ZMM_LOOKBESIDE) //! Atom is big and needs to scan one extra turf in both X and Y. This only extends the range by one turf. Cheap, but not free. // For debug purposes, should contain the above defines in ascending order. var/global/list/mimic_defines = list( @@ -26,5 +50,7 @@ var/global/list/mimic_defines = list( ) // Movable flags. -#define ZMM_IGNORE 1 //! Do not copy this movable. -#define ZMM_MANGLE_PLANES 2 //! Check this movable's overlays/underlays for explicit plane use and mangle for compatibility with Z-Mimic. If you're using emissive overlays, you probably should be using this flag. Expensive, only use if necessary. +#define ZMM_IGNORE 1 //! Do not copy this movable. +#define ZMM_MANGLE_PLANES 2 //! Check this movable's overlays/underlays for explicit plane use and mangle for compatibility with Z-Mimic. If you're using emissive overlays, you probably should be using this flag. Expensive, only use if necessary. +#define ZMM_LOOKAHEAD 3 //! Look one turf ahead and one turf back when considering z-turfs that might be seeing this atom. Cheap, but not free. +#define ZMM_LOOKBESIDE 4 //! Look one turf beside (left/right) when considering z-turfs that might be seeing this atom. Cheap, but not free. diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm index 058afaa1f3d..96dabb65f56 100644 --- a/code/controllers/subsystems/zcopy.dm +++ b/code/controllers/subsystems/zcopy.dm @@ -9,6 +9,18 @@ #define SHADOWER_DARKENING_FACTOR 0.6 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this. #define SHADOWER_DARKENING_COLOR "#999999" // The above, but as an RGB string for lighting-less turfs. +//#define ZM_RECORD_STATS // This doesn't work on O7/Neb right now. + +#ifdef ZM_RECORD_STATS +#define ZM_RECORD_START STAT_START_STOPWATCH +#define ZM_RECORD_STOP STAT_STOP_STOPWATCH +#define ZM_RECORD_WRITE(X...) STAT_LOG_ENTRY(##X) +#else +#define ZM_RECORD_START +#define ZM_RECORD_STOP +#define ZM_RECORD_WRITE(X...) +#endif + SUBSYSTEM_DEF(zcopy) name = "Z-Copy" wait = 1 @@ -25,8 +37,19 @@ SUBSYSTEM_DEF(zcopy) var/openspace_turfs = 0 var/multiqueue_skips_turf = 0 + var/multiqueue_skips_discovery = 0 var/multiqueue_skips_object = 0 + var/total_updates_turf = 0 + var/total_updates_discovery = 0 + var/total_updates_object = 0 + +#ifdef ZM_RECORD_STATS + var/list/turf_stats = list() + var/list/discovery_stats = list() + var/list/mimic_stats = list() +#endif + // Highest Z level in a given Z-group for absolute layering. // zstm[zlev] = group_max var/list/zlev_maximums = list() @@ -108,14 +131,38 @@ SUBSYSTEM_DEF(zcopy) /datum/controller/subsystem/zcopy/stat_entry() var/list/entries = list( - "Mx:[json_encode(zlev_maximums)]", + "", // newline + "ZSt: [build_zstack_display()]", // This is a human-readable list of the z-stacks known to ZM. + "ZMx: [zlev_maximums.Join(", ")]", // And this is the raw internal state. + // This one gets broken out from the below because it's more important. "Q: { T: [queued_turfs.len - (qt_idex - 1)] O: [queued_overlays.len - (qo_idex - 1)] }", - "T: { T: [openspace_turfs] O: [openspace_overlays] }", - "Sk: { T: [multiqueue_skips_turf] O: [multiqueue_skips_object] }", + // In order: Total, Queued, Skipped + "T(O): { T: [openspace_turfs] O: [openspace_overlays] }", + "T(U): { T: [total_updates_turf] D: [total_updates_discovery] O: [total_updates_object] }", + "Sk: { T: [multiqueue_skips_turf] D: [multiqueue_skips_discovery] O: [multiqueue_skips_object] }", "F: { H: [fixup_hit] M: [fixup_miss] N: [fixup_noop] FC: [fixup_cache.len] FKG: [fixup_known_good.len] }" ) ..(entries.Join("\n\t")) +// 1, 2, 3..=7, 8 +/datum/controller/subsystem/zcopy/proc/build_zstack_display() + if (!zlev_maximums.len) + return "" + var/list/zmx = list() + var/idx = 1 + var/span_ctr = 0 + do + if (zlev_maximums[idx] != idx) + span_ctr += 1 + else if (span_ctr) + zmx += "[idx - span_ctr]..=[idx]" + span_ctr = 0 + else + zmx += "[idx]" + idx += 1 + while (idx <= zlev_maximums.len) + return jointext(zmx, ", ") + /datum/controller/subsystem/zcopy/Initialize(timeofday) calculate_zstack_limits() // Flush the queue. @@ -169,8 +216,16 @@ SUBSYSTEM_DEF(zcopy) if (!no_mc_tick) MC_SPLIT_TICK + tick_turfs(no_mc_tick) + + if (!no_mc_tick) + MC_SPLIT_TICK + + tick_mimic(no_mc_tick) + +// - Turf mimic - +/datum/controller/subsystem/zcopy/proc/tick_turfs(no_mc_tick) var/list/curr_turfs = queued_turfs - var/list/curr_ov = queued_overlays while (qt_idex <= curr_turfs.len) var/turf/T = curr_turfs[qt_idex] @@ -188,6 +243,7 @@ SUBSYSTEM_DEF(zcopy) if (T.z_queued > 1) T.z_queued -= 1 multiqueue_skips_turf += 1 + if (no_mc_tick) CHECK_TICK else if (MC_TICK_CHECK) @@ -197,12 +253,23 @@ SUBSYSTEM_DEF(zcopy) // Z-Turf on the bottom-most level, just fake-copy space (or baseturf). // It's impossible for anything to be on the synthetic turf, so ignore the rest of the ZM machinery. if (!T.below) + ZM_RECORD_START flush_z_state(T) - if (T.z_flags & ZM_MIMIC_BASETURF) + if (T.z_flags & ZM_OVERRIDE) simple_appearance_copy(T, get_base_turf_by_area(T), OPENTURF_MAX_PLANE) else simple_appearance_copy(T, SSskybox.dust_cache["[((T.x + T.y) ^ ~(T.x * T.y) + T.z) % 25]"]) + T.z_generation += 1 + T.z_queued -= 1 + total_updates_turf += 1 + + if (T.above) + T.above.update_mimic() + + ZM_RECORD_STOP + ZM_RECORD_WRITE(turf_stats, "Fake: [T.type] on [T.z]") + if (no_mc_tick) CHECK_TICK else if (MC_TICK_CHECK) @@ -215,9 +282,12 @@ SUBSYSTEM_DEF(zcopy) T.z_generation += 1 + ZM_RECORD_START + // Get the bottom-most turf, the one we want to mimic. + // Baseturf mimics act as false bottoms of the stack. var/turf/Td = T - while (Td.below) + while (Td.below && !(Td.z_flags & (ZM_OVERRIDE|ZM_TERMINATOR))) Td = Td.below // Depth must be the depth of the *visible* turf, not self. @@ -227,9 +297,18 @@ SUBSYSTEM_DEF(zcopy) var/t_target = OPENTURF_MAX_PLANE - turf_depth // This is where the turf (but not the copied atoms) gets put. // Turf is set to mimic baseturf, handle that and bail. - if (T.z_flags & ZM_MIMIC_BASETURF) + if (T.z_flags & ZM_OVERRIDE) flush_z_state(T) - simple_appearance_copy(T, get_base_turf_by_area(T), t_target) + simple_appearance_copy(T, Td.z_appearance || get_base_turf_by_area(T), t_target) + + if (T.above) + T.above.update_mimic() + + total_updates_turf += 1 + T.z_queued -= 1 + + ZM_RECORD_STOP + ZM_RECORD_WRITE(turf_stats, "Simple: [T.type] on [T.z]") if (no_mc_tick) CHECK_TICK @@ -237,7 +316,7 @@ SUBSYSTEM_DEF(zcopy) break continue - // If we previously were ZM_MIMIC_BASETURF, there might be an orphaned proxy. + // If we previously were ZM_OVERRIDE, there might be an orphaned proxy. else if (T.mimic_underlay) QDEL_NULL(T.mimic_underlay) @@ -250,7 +329,7 @@ SUBSYSTEM_DEF(zcopy) // This openturf doesn't care about its icon, so we can just overwrite it. if (T.below.mimic_proxy) QDEL_NULL(T.below.mimic_proxy) - T.appearance = T.below + T.appearance = Td.z_appearance || Td T.name = initial(T.name) T.desc = initial(T.desc) T.gender = initial(T.gender) @@ -261,7 +340,7 @@ SUBSYSTEM_DEF(zcopy) if (!T.below.mimic_proxy) T.below.mimic_proxy = new(T) var/atom/movable/openspace/turf_proxy/TO = T.below.mimic_proxy - TO.appearance = Td + TO.appearance = Td.z_appearance || Td TO.name = T.name TO.gender = T.gender // Need to grab this too so PLURAL works properly in examine. TO.opacity = FALSE @@ -286,75 +365,45 @@ SUBSYSTEM_DEF(zcopy) // Handle below atoms. - // Add everything below us to the update queue. + var/shadower_set = FALSE + + // Add everything below us to the discovery queue. for (var/thing in T.below) var/atom/movable/object = thing if (QDELETED(object) || (object.z_flags & ZMM_IGNORE) || object.loc != T.below || object.invisibility == INVISIBILITY_ABSTRACT) - // Don't queue deleted stuff, stuff that's not visible, blacklisted stuff, or stuff that's centered on another tile but intersects ours. + /* Don't queue: + - (q)deleted objects + - Explicitly ignored objects + - Objects not rooted on this turf (multitiles) + - Always-invisible atoms + */ continue // Special case: these are merged into the shadower to reduce memory usage. if (object.type == /atom/movable/lighting_overlay) - T.shadower.copy_lighting(object) + T.shadower.copy_lighting(object, !(T.below.z_flags & ZM_NO_SHADOW)) continue - if (!object.bound_overlay) // Generate a new overlay if the atom doesn't already have one. - object.bound_overlay = new(T) - object.bound_overlay.associated_atom = object - - var/override_depth - var/original_type = object.type - var/original_z = object.z - var/have_performed_fixup = FALSE - - switch (object.type) - // Layering for recursive mimic needs to be inherited. - if (/atom/movable/openspace/mimic) - var/atom/movable/openspace/mimic/OOO = object - original_type = OOO.mimiced_type - override_depth = OOO.override_depth - original_z = OOO.original_z - have_performed_fixup = OOO.have_performed_fixup - - // If this is a turf proxy (the mimic for a non-OVERWRITE turf), it needs to respect space parallax if relevant. - if (/atom/movable/openspace/turf_proxy) - if (T.z_eventually_space) - // Yes, this is an awful hack; I don't want to add yet another override_* var. - override_depth = OPENTURF_MAX_PLANE - SPACE_PLANE - - if (/atom/movable/openspace/turf_mimic) - original_z += 1 - - var/atom/movable/openspace/mimic/OO = object.bound_overlay - - // If the OO was queued for destruction but was claimed by another OT, stop the destruction timer. - if (OO.destruction_timer) - deltimer(OO.destruction_timer) - OO.destruction_timer = null - - OO.depth = override_depth || min(zlev_maximums[T.z] - original_z, OPENTURF_MAX_DEPTH) - - // These types need to be pushed a layer down for bigturfs to function correctly. - switch (original_type) - if (/atom/movable/openspace/multiplier, /atom/movable/openspace/turf_proxy) - if (OO.depth < OPENTURF_MAX_DEPTH) - OO.depth += 1 - - OO.mimiced_type = original_type - OO.override_depth = override_depth - OO.original_z = original_z - OO.have_performed_fixup ||= have_performed_fixup - - // Multi-queue to maintain ordering of updates to these - // queueing it multiple times will result in only the most recent - // actually processing. - OO.queued += 1 - queued_overlays += OO + // If an atom already has an overlay, we probably don't need to discover it again. + // ...but we need to force it if the object was salvaged from another zturf. + if (!object.bound_overlay || object.bound_overlay.destruction_timer) + discover_movable(object, T) + + if (!shadower_set) + if (T.below.z_flags & ZM_NO_SHADOW) + T.shadower.color = null + else + T.shadower.color = SHADOWER_DARKENING_COLOR T.z_queued -= 1 if (T.above) T.above.update_mimic() + total_updates_turf += 1 + + ZM_RECORD_STOP + ZM_RECORD_WRITE(turf_stats, "Complex: [T.type] on [T.z]") + if (no_mc_tick) CHECK_TICK else if (MC_TICK_CHECK) @@ -364,9 +413,9 @@ SUBSYSTEM_DEF(zcopy) curr_turfs.Cut(1, qt_idex) qt_idex = 1 - if (!no_mc_tick) - MC_SPLIT_TICK - +// - Phase: Mimic update -- actually update the mimics' appearance, order sensitive - +/datum/controller/subsystem/zcopy/proc/tick_mimic(no_mc_tick) + var/list/curr_ov = queued_overlays while (qo_idex <= curr_ov.len) var/atom/movable/openspace/mimic/OO = curr_ov[qo_idex] curr_ov[qo_idex] = null @@ -379,8 +428,9 @@ SUBSYSTEM_DEF(zcopy) break continue - if (QDELETED(OO.associated_atom)) // This shouldn't happen, but just in-case. + if (QDELETED(OO.associated_atom)) // This shouldn't happen. qdel(OO) + log_debug("Z-Mimic: Received mimic with QDELETED parent ([OO.associated_atom || ""])") if (no_mc_tick) CHECK_TICK @@ -392,21 +442,24 @@ SUBSYSTEM_DEF(zcopy) if (OO.queued > 1) OO.queued -= 1 multiqueue_skips_object += 1 + if (no_mc_tick) CHECK_TICK else if (MC_TICK_CHECK) break continue + ZM_RECORD_START + // Actually update the overlay. if (OO.dir != OO.associated_atom.dir) - OO.set_dir(OO.associated_atom.dir) + OO.dir = OO.associated_atom.dir // updates are propagated up another way, don't use set_dir + OO.appearance = OO.associated_atom + OO.z_flags = OO.associated_atom.z_flags if (OO.particles != OO.associated_atom.particles) OO.particles = OO.associated_atom.particles - OO.appearance = OO.associated_atom - OO.z_flags = OO.associated_atom.z_flags OO.plane = OPENTURF_MAX_PLANE - OO.depth OO.opacity = FALSE @@ -422,6 +475,11 @@ SUBSYSTEM_DEF(zcopy) if (OO.bound_overlay) // If we have a bound overlay, queue it too. OO.update_above() + total_updates_object += 1 + + ZM_RECORD_STOP + ZM_RECORD_WRITE(mimic_stats, OO.mimiced_type) + if (no_mc_tick) CHECK_TICK else if (MC_TICK_CHECK) @@ -431,15 +489,86 @@ SUBSYSTEM_DEF(zcopy) curr_ov.Cut(1, qo_idex) qo_idex = 1 +// return: is-invalid +/datum/controller/subsystem/zcopy/proc/discover_movable(atom/movable/object) + ASSERT(!QDELETED(object)) + + var/turf/Tloc = object.loc + if (!isturf(Tloc) || !Tloc.above) + return TRUE + + var/turf/T = Tloc.above + + ZM_RECORD_START + + if (!object.bound_overlay) + var/atom/movable/openspace/mimic/M = new(T) + object.bound_overlay = M + M.associated_atom = object + if (TURF_IS_MIMICKING(M.loc)) + .(M) + + var/override_depth + var/original_type = object.type + var/original_z = object.z + + switch (object.type) + // Layering for recursive mimic needs to be inherited. + if (/atom/movable/openspace/mimic) + var/atom/movable/openspace/mimic/OOO = object + original_type = OOO.mimiced_type + override_depth = OOO.override_depth + original_z = OOO.original_z + + // If this is a turf proxy (the mimic for a non-OVERWRITE turf), it needs to respect space parallax if relevant. + if (/atom/movable/openspace/turf_proxy) + if (T.z_eventually_space) + // Yes, this is an awful hack; I don't want to add yet another override_* var. + override_depth = OPENTURF_MAX_PLANE - SPACE_PLANE + + var/atom/movable/openspace/mimic/OO = object.bound_overlay + + // If the OO was queued for destruction but was claimed by another OT, stop the destruction timer. + if (OO.destruction_timer) + deltimer(OO.destruction_timer) + OO.destruction_timer = null + + OO.depth = override_depth || min(zlev_maximums[T.z] - original_z, OPENTURF_MAX_DEPTH) + + switch (original_type) + // These types need to be pushed a layer down for bigturfs to function correctly. + if (/atom/movable/openspace/turf_proxy, /atom/movable/openspace/turf_mimic) + OO.depth += 1 + if (/atom/movable/openspace/multiplier) + OO.depth += 1 + + OO.mimiced_type = original_type + OO.override_depth = override_depth + OO.original_z = original_z + + // Multi-queue to maintain ordering of updates to these + // queueing it multiple times will result in only the most recent + // actually processing. + OO.queued += 1 + queued_overlays += OO + + total_updates_discovery += 1 + + ZM_RECORD_STOP + ZM_RECORD_WRITE(discovery_stats, "Depth [OO.depth] on [OO.z]") + + return FALSE + /datum/controller/subsystem/zcopy/proc/flush_z_state(turf/T) - if(T.below) + if (T.below) // Z-Mimic turfs aren't necessarily above another turf. if (T.below.mimic_above_copy) QDEL_NULL(T.below.mimic_above_copy) if (T.below.mimic_proxy) QDEL_NULL(T.below.mimic_proxy) - for (var/atom/movable/openspace/OO in T) - if (istype(OO, /atom/movable/openspace/mimic)) - qdel(OO) + + QDEL_NULL(T.mimic_underlay) + for (var/atom/movable/openspace/mimic/OO in T) + qdel(OO) /datum/controller/subsystem/zcopy/proc/simple_appearance_copy(turf/T, new_appearance, target_plane) if (T.z_flags & ZM_MIMIC_OVERWRITE) @@ -524,7 +653,7 @@ SUBSYSTEM_DEF(zcopy) fixed_underlays[i] = fixed_appearance if (mutated) - for (var/i in 1 to fixed_overlays.len) + for (var/i in 1 to fixed_underlays.len) if (fixed_underlays[i] == null) fixed_underlays[i] = appearance:underlays[i] @@ -552,6 +681,7 @@ SUBSYSTEM_DEF(zcopy) return MA #define FMT_DEPTH(X) (X == null ? "(null)" : X) +#define FMT_OK(X) (X) ? "OK" : "MISMATCH" // This is a dummy object used so overlays can be shown in the analyzer. /atom/movable/openspace/debug @@ -584,7 +714,7 @@ SUBSYSTEM_DEF(zcopy) "", "

Analysis of [T] at [T.x],[T.y],[T.z]

", "Queue occurrences: [T.z_queued]", - "Above space: Apparent [T.z_eventually_space ? "Yes" : "No"], Actual [is_above_space ? "Yes" : "No"] - [T.z_eventually_space == is_above_space ? "OK" : "MISMATCH"]", + "Above space: Apparent [T.z_eventually_space ? "Yes" : "No"], Actual [is_above_space ? "Yes" : "No"] - [FMT_OK(T.z_eventually_space == is_above_space)]", "Z Flags: [english_list(bitfield2list(T.z_flags, global.mimic_defines), "(none)")]", "Has Shadower: [T.shadower ? "Yes" : "No"]", "Has turf proxy: [T.mimic_proxy ? "Yes" : "No"]", @@ -593,12 +723,14 @@ SUBSYSTEM_DEF(zcopy) "Below: [!T.below ? "(nothing)" : "[T.below] at [T.below.x],[T.below.y],[T.below.z]"]", "Depth: [FMT_DEPTH(T.z_depth)] [T.z_depth == OPENTURF_MAX_DEPTH ? "(max)" : ""]", "Generation: [T.z_generation]", - "Update count: Claimed [claimed_update_count], Actual [real_update_count] - [claimed_update_count == real_update_count ? "OK" : "MISMATCH"]", + "Update count: Claimed [claimed_update_count], Actual [real_update_count] - [FMT_OK(claimed_update_count == real_update_count)]", "