local function getUDBT(keyPrefix) return setmetatable({}, { __index = function(luatable, key) return userdata.get(keyPrefix .. "." .. key) end, __newindex = function(luatable, key, value) if (value == nil) then userdata.remove(keyPrefix .. "." .. key) else userdata.set(keyPrefix .. "." .. key, value) end end, }) end local function getUDBTStack(keyPrefix) local udbtStack = getUDBT(keyPrefix) local len = udbtStack.len if (type(len) ~= "number" or len < 0) then udbtStack.len = 0 end return udbtStack end local function UDBTStack_length(udbtStack) return udbtStack.len end local function UDBTStack_top(udbtStack) local len = udbtStack.len if (len > 0) then return udbtStack[len] else return nil end end local function UDBTStack_push(udbtStack, element) local newLen = udbtStack.len + 1 udbtStack.len = newLen udbtStack[newLen] = element end local function UDBTStack_pop(udbtStack) local len = udbtStack.len if (len > 0) then local value = udbtStack[len] udbtStack[len] = nil udbtStack.len = len - 1 return value else return nil end end local function UDBTStack_clear(udbtStack) local len = udbtStack.len udbtStack.len = 0 for i = 1, len do udbtStack[i] = nil end end local onFrameEndListenerName = "Genesis Lemmings TAS lag frames tracker" local function OnAfterFrameUpdate() -- Base64 210DE077-C471-44D5-A5FF-ECAC5FE52E2F local trackingData = getUDBT("d+ANIXHE1USl/+ysX+UuLw") -- Base64 c4f782da-27aa-4f27-bcd2-d0e1212d74a7 local lagFrameTimes = getUDBTStack("2oL3xKonJ0+80tDhIS10pw") -- Base64 8bf16b4e-6062-4279-a78f-ffe6d8296f96 local lagFrameTimesBeyondCurrentFrame = getUDBTStack("Tmvxi2JgeUKnj//m2Clvlg") local currentCounterValue = memory.readbyte(0xF54D) local currentF9E2Value = memory.readbyte(0xF9E2) local currentFrameCounter = emu.framecount() if (currentCounterValue ~= 0) then if (trackingData.stoptracking == nil) then trackingData.stoptracking = true elseif ((not trackingData.stoptracking) and (currentFrameCounter == trackingData.previousFrameCounter + 1)) then local maybeNextLagFrame = UDBTStack_top(lagFrameTimesBeyondCurrentFrame) if ((trackingData.previousF54DCounter == currentCounterValue) and (currentF9E2Value ~= 0)) then trackingData.lagFramesCount = trackingData.lagFramesCount + 1 -- see comment below on why we ignore the first one if (trackingData.lagFramesCount > 1) then if (currentFrameCounter == maybeNextLagFrame) then UDBTStack_pop(lagFrameTimesBeyondCurrentFrame) end console.writeline("lag at frame #" .. currentFrameCounter) UDBTStack_push(lagFrameTimes, currentFrameCounter) end else if (currentFrameCounter == maybeNextLagFrame) then UDBTStack_clear(lagFrameTimesBeyondCurrentFrame) end end if ((trackingData.previousF9E2Value ~= 0) and (currentF9E2Value == 0)) then console.writeline() console.writeline("***** end of level observed on frame " .. currentFrameCounter .. " *****"); console.writeline(UDBTStack_length(lagFrameTimes) .. " lag frames (frame counter values listed below):") local lagFramesListingString = "" for i = 1, UDBTStack_length(lagFrameTimes) do lagFramesListingString = lagFramesListingString .. " " .. lagFrameTimes[i] .. "\r\n" end console.write(lagFramesListingString) console.writeline("*****") console.writeline() end elseif ((not trackingData.stoptracking) and (currentFrameCounter == trackingData.previousFrameCounter - 1)) then if ((trackingData.previousF54DCounter == currentCounterValue) and (trackingData.previousF9E2Value ~= 0)) then trackingData.lagFramesCount = trackingData.lagFramesCount - 1 if (trackingData.lagFramesCount > 0) then UDBTStack_push(lagFrameTimesBeyondCurrentFrame, UDBTStack_pop(lagFrameTimes)) end end else trackingData.stoptracking = true end -- effectiveLagFramesCount: when the level starts we observe the following -- per-frame changes in 0xF54D: 0->1, 1->1, then the regular cadence of 1,2,3,1,2,3 -- Therefore, the straightforward calculation of lagFramesCount always reports -- one extra frame from that phenomenon. This appears unavoidable, so we compensate -- for that initial 1->1 in the output below by not counting it. local effectiveLagFramesCount = trackingData.lagFramesCount if (trackingData.lagFramesCount > 0) then effectiveLagFramesCount = trackingData.lagFramesCount - 1 end local offsetFromBottomOfWindow = 5 if (trackingData.stoptracking) then gui.text(0, offsetFromBottomOfWindow, "lag frames: [aborted tracking, most recent value] " .. effectiveLagFramesCount, nil, nil, "bottomleft") else gui.text(0, offsetFromBottomOfWindow, "lag frames: " .. effectiveLagFramesCount, nil, nil, "bottomleft") end trackingData.previousF54DCounter = currentCounterValue trackingData.previousF9E2Value = currentF9E2Value else trackingData.stoptracking = false trackingData.lagFramesCount = 0 trackingData.previousF54DCounter = 0 trackingData.previousF9E2Value = 0 UDBTStack_clear(lagFrameTimes) UDBTStack_clear(lagFrameTimesBeyondCurrentFrame) end trackingData.previousFrameCounter = currentFrameCounter end console.clear() event.unregisterbyname(onFrameEndListenerName) event.onframestart(OnAfterFrameUpdate, onFrameEndListenerName)