package.path = "./?.lua;./?/init.lua;" .. package.path local T = require("tests.harness") local check, eq = T.check, T.eq local Rtc = require("src.core.game3.rtc") local TimeEvents = require("src.core.game3.time_events") local Flags = require("src.core.game3.scripting.flags") local Constants = require("src.core.game3.constants") local C = Constants.of("emerald") local CLOCK_SET = C:require("flags", "FLAG_SYS_CLOCK_SET") local VAR_DAYS = C:require("vars", "VAR_DAYS") local DAILY_LO = C:require("flags", "DAILY_FLAGS_START") local function fresh(spec) Rtc.reset() if spec then Rtc.setFixed(spec) end end local function session() return { version = "emerald", store = { flags = {}, vars = {} } } end eq(Rtc.dayCount(0, 1, 1), 1, "2000-01-01 is day 1 (pret ConvertDateToDayCount adds the day)") eq(Rtc.dayCount(0, 3, 1), 31 + 29 + 1, "2000 is a leap year") eq(Rtc.dayCount(1, 1, 1), 367, "2001-01-01 after leap 2000") eq(Rtc.dayCount(4, 3, 1), 4 * 365 + 1 + 31 + 29 + 1, "2004 leap day counted") check(Rtc.isLeapYear(0), "year 0 (2000) leap") check(not Rtc.isLeapYear(100), "2100 not leap") check(Rtc.isLeapYear(400), "2400 leap") local d = Rtc.calcTimeDifference(Rtc.newTime(0, 10, 30, 30), Rtc.newTime(1, 9, 20, 10)) eq(d.days, 0, "borrow days") eq(d.hours, 22, "borrow hours") eq(d.minutes, 49, "borrow minutes") eq(d.seconds, 40, "borrow seconds") d = Rtc.calcTimeDifference(Rtc.newTime(5, 0, 0, 0), Rtc.newTime(2, 0, 0, 0)) eq(d.days, -3, "negative days stay negative") eq(Rtc.newTime(40000, 0, 0, 0).days, 40000 - 65536, "days wrap as s16") eq(Rtc.newTime(0, 200, 0, 0).hours, 200 - 256, "hours wrap as s8") fresh("2004-03-01T10:15:20") local info = Rtc.getInfo(nil) eq(info.year, 4, "fixed clock year") eq(info.month, 3, "fixed clock month") eq(info.hour, 10, "fixed clock hour") eq(Rtc.errorStatus(), 0, "no RTC error in range") local s = session() local off = Rtc.initLocalTimeOffset(s, 7, 45) local lt = Rtc.calcLocalTime(s) eq(lt.days, 0, "local day 0 right after setting") eq(lt.hours, 7, "local hour after setting") eq(lt.minutes, 45, "local minute after setting") eq(lt.seconds, 0, "offset absorbs RTC seconds") check(s.localTimeOffset == off or s.localTimeOffset.days == off.days, "offset stored on session") Rtc.advance(60 * 24 + 30) lt = Rtc.calcLocalTime(s) eq(lt.days, 1, "a day later") eq(lt.hours, 8, "hour after +1d30m") eq(lt.minutes, 15, "minute after +1d30m") fresh("2004-03-01T00:00:00") eq(Rtc.minuteCount(nil), 24 * 60 * Rtc.dayCount(4, 3, 1), "minute count at midnight") Rtc.setFixed("2004-03-01T12:34:00") eq(Rtc.minuteCount(nil), 24 * 60 * Rtc.dayCount(4, 3, 1) + 60 * 0x12 + 0x34, "RtcGetMinuteCount reads BCD hour/minute (pret quirk)") fresh("2100-01-01T00:00:00") check(bit.band(Rtc.errorStatus(), Rtc.ERR.FLAG_MASK) ~= 0, "year out of BCD range is an RTC error") eq(Rtc.getInfo(nil).year, 0, "error returns the 2000-01-01 dummy") fresh() Rtc.setHostClock(function() return { year = 2026, month = 9, day = 27, hour = 23, minute = 59, second = 59 } end) info = Rtc.getInfo(nil) eq(info.year, 26, "host clock year") Rtc.advance(0, 1) info = Rtc.getInfo(nil) eq(info.day, 28, "rollover to next day") eq(info.hour, 0, "rollover hour") Rtc.reset() Rtc.setHostClock(function() return { year = 2004, month = 2, day = 28, hour = 23, minute = 0, second = 0 } end) Rtc.advance(60) eq(Rtc.getInfo(nil).day, 29, "2004-02-29 exists") Rtc.advance(60 * 24) eq(Rtc.getInfo(nil).month, 3, "leap day rolls to March") fresh("2005-06-01T10:00:00") s = session() Rtc.initLocalTimeOffset(s, 10, 0) s.lastBerryTreeUpdate = Rtc.newTime(0, 10, 0, 0) Rtc.setFixed("2005-06-20T18:00:00") Rtc.anchorToLastUpdate(s) lt = Rtc.calcLocalTime(s) eq(lt.days, 0, "import anchor resumes at lastBerryTreeUpdate day") eq(lt.hours, 10, "import anchor resumes at lastBerryTreeUpdate hour") TimeEvents.reset() TimeEvents.setPokemonCenterCheck(function() return false end) fresh("2005-06-01T10:00:00") s = session() local ran = TimeEvents.run(s) check(not ran, "no events before FLAG_SYS_CLOCK_SET") Rtc.initLocalTimeOffset(s, 9, 0) TimeEvents.init(s) check(Flags.getFlag(s.store, nil, CLOCK_SET), "init sets FLAG_SYS_CLOCK_SET") eq(Flags.getVar(s.store, nil, VAR_DAYS), 0, "init stores VAR_DAYS") local dayCalls, minuteCalls, order = {}, {}, {} TimeEvents.onDay("UpdateTVShowsPerDay", function(_, n) dayCalls[#dayCalls + 1] = n; order[#order + 1] = "tv" end) TimeEvents.onDay("UpdateDewfordTrendPerDay", function() order[#order + 1] = "dewford" end) TimeEvents.onMinute("BerryTreeTimeUpdate", function(_, n) minuteCalls[#minuteCalls + 1] = n end) Flags.setFlag(s.store, nil, DAILY_LO + 3, true) ran = TimeEvents.run(s) check(ran, "runs once clock set") eq(#dayCalls, 0, "no per-day before a day passes") eq(#minuteCalls, 0, "no per-minute when 0 minutes passed") Rtc.advance(125) TimeEvents.run(s) eq(minuteCalls[1], 125, "per-minute gets elapsed minutes") eq(#dayCalls, 0, "still same day") Rtc.advance(60 * 24 * 2) TimeEvents.run(s) eq(dayCalls[1], 2, "per-day gets days since") eq(order[1], "dewford", "pret order: Dewford before TV") check(not Flags.getFlag(s.store, nil, DAILY_LO + 3), "ClearDailyFlags cleared a daily flag") eq(Flags.getVar(s.store, nil, VAR_DAYS), 2, "VAR_DAYS advanced") TimeEvents.run(s) eq(#dayCalls, 1, "per-day fires once per day change") TimeEvents.setPokemonCenterCheck(function() return true end) Rtc.advance(60 * 24) check(not TimeEvents.run(s), "skipped inside a Pokemon Center") TimeEvents.setPokemonCenterCheck(function() return false end) s.lastBerryTreeUpdate = Rtc.newTime(99, 0, 0, 0) minuteCalls = {} TimeEvents.run(s) eq(#minuteCalls, 0, "negative minute delta ignored (pret minutes >= 0)") local fr = { version = "firered", store = { flags = {}, vars = {} } } check(not Rtc.enabled(fr), "FireRed has no RTC") check(Rtc.enabled(s), "Emerald has an RTC") check(not TimeEvents.run(fr), "time events never run on FireRed") -- pokeemerald/src/field_tasks.c:150 TimeEvents.reset() TimeEvents.setPokemonCenterCheck(function() return false end) fresh("2005-06-01T10:00:00") s = session() Rtc.initLocalTimeOffset(s, 10, 0) TimeEvents.init(s) local ticks = 0 TimeEvents.onMinute("BerryTreeTimeUpdate", function() ticks = ticks + 1 end) Rtc.advance(5) check(not TimeEvents.tick(s, 100), "no run while bit 12 is clear") check(TimeEvents.tick(s, 4096), "runs when bit 12 rises") Rtc.advance(5) check(not TimeEvents.tick(s, 4097), "only once while bit 12 stays set") check(not TimeEvents.tick(s, 8192), "re-arms when bit 12 clears") check(TimeEvents.tick(s, 12288), "runs again on the next rise") eq(ticks, 2, "per-minute handler ran twice") TimeEvents.reset() TimeEvents.setPokemonCenterCheck(function() return false end) Rtc.reset() T.finish("game3 rtc")