User File #638643807847186809

Upload All User Files

#638643807847186809 - Data-oriented savestate manipulation in Lua, as used in 6203M (Kwirk "Heading Out? all floors")

tas.lua
Game: Kwirk ( GB, see all files )
6 downloads
Uploaded 19 days ago by Sand (see all 4)
Lua script that plays Kwirk "Heading Out? all floors" from beginning to end. This is the same script that was used for [6203] GB Kwirk "Heading Out?, all floors" by Sand in 25:15.19, with some additional comments and edits for clarity. The reason for posting this is to demonstrate the savestate manipulation support code included in the script (from BASE_SAVESTATE to earliest_effective_frame). See Forum/Topics/25755.
This is tas.lua in the Git repository at https://www.bamsoftware.com/git/kwirk.git at commit ef2355417be15e9473ff6d776a3e85247f58db5f.
-- ./EmuHawkMono.sh --lua=Lua/tas.lua kwirk.gb

SOLUTIONS = {
	-- Easy
	[ "30" ] = { frames =  222, steps =  24, moves = "WWWWWWNWSWNWSWNWSWWWWWWW" },
	[ "30i"] = { frames =  240, steps =  26, moves = "WWSWWWWNWSWNWSWNWSWWWWNWWW" },
	[ "31" ] = { frames =  429, steps =  44, moves = "WWWWWNWWWWWWSSWSEEEEESENNNENWWWWWWNWSSWWWWWW" },
	[ "31i"] = { frames =  447, steps =  46, moves = "WWSWWWWSWWWWWNNWNEEEEENESSSESWWWWWWSWNNWWWNWWW" },
	[ "32" ] = { frames =  228, steps =  24, moves = "WWWWWWNWWWSENWWWSWWWWWWW" },
	[ "32i"] = { frames =  246, steps =  26, moves = "WWSWWWWNWWWSENWWWSWWWWNWWW" },
	[ "33" ] = { frames =  252, steps =  26, moves = "WWNWWWWSWWNNWWSSWWNWWWSWWW" },
	[ "33i"] = { frames =  270, steps =  28, moves = "WWSSWWWWSWWNNWWSSWWNWWWNNWWW" },
	[ "34" ] = { frames =  381, steps =  41, moves = "WWWWWWWWWWSEEEEENNWWWWWWNEEEESWWWWWWWWWWW" },
	[ "34i"] = { frames =  399, steps =  43, moves = "WWSWWWWWWWWNEEEEESSWWWWWWSEEEENWWWWWWWWNWWW" },
	[ "35" ] = { frames =  383, steps =  42, moves = "WWSSSWWWWWNNEWNENWNWWWWWSSSEESSWWWWWNNNWWW" },
	[ "35i"] = { frames =  365, steps =  40, moves = "WWNNWWWWWSSEWSESWSWWWWWNNNEENNWWWWWSSWWW" },
	[ "36" ] = { frames =  257, steps =  28, moves = "WWWWWSSSWWWWWWNENNNWSWWWWWWW" },
	[ "36i"] = { frames =  275, steps =  30, moves = "WWSWWWNNNWWWWWWSESSSWNWWWWNWWW" },
	[ "37" ] = { frames =  247, steps =  27, moves = "WWWWWWNNWSENNWWWSSWWWWWWWWW" },
	[ "37i"] = { frames =  265, steps =  29, moves = "WWSWWWWSSWNESSWWWNNWWWWWWNWWW" },
	[ "38" ] = { frames =  330, steps =  34, moves = "WWNNWWWSSSSSWNNENNNWWWWWWWWWWSSWWW" },
	[ "38i"] = { frames =  348, steps =  36, moves = "WWSSSWWWNNNNNWSSESSSWWWWWWWWWWNNNWWW" },
	[ "39" ] = { frames =  288, steps =  30, moves = "WWNNWWWWSSWWSSWNNWWNNWWWWSSWWW" },
	[ "39i"] = { frames =  306, steps =  32, moves = "WWSSSWWWWNNWWNNWSSWWSSWWWWNNNWWW" },
	[ "40" ] = { frames =  273, steps =  30, moves = "WWSWWWWWNNWWEESSSWWNWWWWWWNWWW" },
	[ "40i"] = { frames =  255, steps =  28, moves = "WWWWWWWSSWWEENNNWWSWWWWWWWWW" },
	[ "41" ] = { frames =  369, steps =  40, moves = "WWWWWNNWWWWWSWEENEEEESSSSWWWWWNWWNWWWWWW" },
	[ "41i"] = { frames =  387, steps =  42, moves = "WWSWWWSSWWWWWNWESEEEEENNNNWWWWSWWWSWWWNWWW" },
	[ "42" ] = { frames =  278, steps =  30, moves = "WWWWWNNWWWWWWSENSWNESSWWWWWWWW" },
	[ "42i"] = { frames =  296, steps =  32, moves = "WWSWWWNNWWWWWWSENSWNESSWWWWWNWWW" },
	[ "43" ] = { frames =  406, steps =  42, moves = "WWNNWWWWSESWNWSESWSWWSWNENWSWNNNNWWWWSSWWW" },
	[ "43i"] = { frames =  424, steps =  44, moves = "WWSSSWWWWNENWSWNENWNWWNWSESWNWSSSSWWWWNNNWWW" },
	[ "44" ] = { frames =  363, steps =  38, moves = "WWSSSWWWWWWNESSWWNWWSSEENNNNWWWWWSSWWW" },
	[ "44i"] = { frames =  363, steps =  38, moves = "WWNNWWWWWWSENNWWSWWNNEESSSSWWWWWNNNWWW" },
	[ "45" ] = { frames =  258, steps =  28, moves = "WWWWWWWNSSSWWNENNWSWWWWWWWWW" },
	[ "45i"] = { frames =  276, steps =  30, moves = "WWSWWWWSNWNNWWSESSWNWWWWWWNWWW" },
	[ "46" ] = { frames =  260, steps =  28, moves = "WWWWWWWWENNWSWWESSWNWWWWWWWW" },
	[ "46i"] = { frames =  278, steps =  30, moves = "WWSWWWWWWESSWNWWENNWSWWWWWNWWW" },
	[ "47" ] = { frames =  328, steps =  36, moves = "WWNNWWWWSSSSWWWEENNNWSWWWNNWWWWSSWWW" },
	[ "47i"] = { frames =  346, steps =  38, moves = "WWSSSWWWWNNNNWWWEESSSWNWWWSSWWWWNNNWWW" },
	[ "48" ] = { frames =  459, steps =  50, moves = "WWNWWWWWNWWWWWSEEEEEEWWWWWSSEENEESSSWWWWWWWWNNNWWW" },
	[ "48i"] = { frames =  459, steps =  50, moves = "WWSSWWWWWSWWWWWNEEEEEEWWWWWNNEESEENNNWWWWWWWWSSWWW" },
	[ "49" ] = { frames =  345, steps =  37, moves = "WWWWWWWWEENNWSSESSWNENNNWWWSWSWWWWWWW" },
	[ "49i"] = { frames =  363, steps =  39, moves = "WWSWWWWWWEENNNWSSESSWNESSWWWWNNWWWWNWWW" },
	[ "50" ] = { frames =  296, steps =  32, moves = "WWWWWWWWNNWSESSSSWWNENWWNWWWWWWW" },
	[ "50i"] = { frames =  314, steps =  34, moves = "WWSWWWWWWSSWNENNNNWWSESWWSWWWWNWWW" },
	[ "51" ] = { frames =  324, steps =  34, moves = "WWSSSWWWWWNNNWSENWWWWSSSWWWWNNNWWW" },
	[ "51i"] = { frames =  306, steps =  32, moves = "WWNNWWWWWSSSWNESWWWWNNNWWWWSSWWW" },
	[ "52" ] = { frames =  510, steps =  54, moves = "WWNWWWSWWWWWNEEEEESSWNESSWWWNEEESSWNESSWWWWWWWNWWWSWWW" },
	[ "52i"] = { frames =  528, steps =  56, moves = "WWSSWWWNWWWWWSEEEEENNWSENNWWWSEEENNWSENNWWWWWWWSWWWNNWWW" },
	[ "53" ] = { frames =  324, steps =  34, moves = "WWNNWWWWWWWSEESSWSWNESWWWWWWNNNWWW" },
	[ "53i"] = { frames =  324, steps =  34, moves = "WWSSSWWWWWWWNEENNNWWSENWWWWWWSSWWW" },
	[ "54" ] = { frames =  293, steps =  32, moves = "WWNNWWWSSSWNWNWWWSSWSWSWWWNNNWWW" },
	[ "54i"] = { frames =  293, steps =  32, moves = "WWSSSWWWNNNWSWSWWWNNWNWNWWWSSWWW" },
	[ "55" ] = { frames =  277, steps =  30, moves = "WWSWWWWWWNNWWSENWWSSSWWWWWNWWW" },
	[ "55i"] = { frames =  259, steps =  28, moves = "WWWWWWWWSSWWNESWWNNNWWWWWWWW" },
	[ "56" ] = { frames =  300, steps =  30, moves = "WWWWWWWSWESEENWWWWNSWWWNWWWWWW" },
	[ "56i"] = { frames =  318, steps =  32, moves = "WWSWWWWWNWENEESWWWWSNWWWSWWWNWWW" },
	[ "57" ] = { frames =  272, steps =  30, moves = "WWNNWWWWWWWWSWSSESSWWWWWNNNWWW" },
	[ "57i"] = { frames =  272, steps =  30, moves = "WWSSSWWWWWWWWNWNNENNWWWWWSSWWW" },
	[ "58" ] = { frames =  495, steps =  51, moves = "WWWWWWSWWSWWWNENNEESSESWWWWEENNWSSSWWWNNNNNWWWSSWWW" },
	[ "58i"] = { frames =  513, steps =  53, moves = "WWSWWWWNWWWNWWSESSEENNENWWWWEESSWNNWNWSSSSSWWWWNNNWWW" },
	[ "59" ] = { frames =  399, steps =  43, moves = "WWSWWWWWSWWEENNWSENNWWNEEWWSENWWSSWWWWWNWWW" },
	[ "59i"] = { frames =  381, steps =  41, moves = "WWWWWWWNWWEESSWNESSWWSEEWWNESWWNNWWWWWWWW" },

	-- Average
	[ "60" ] = { frames =  479, steps =  48, moves = "WWWWWNNWWSSNNEESSSSWNENWWWWEEESSWNENWWWWWWWWWWWW" },
	[ "60i"] = { frames =  497, steps =  50, moves = "WWSWWWNNWWSSNNEESSSSWNENWWWWEEESSWNENWWWWWWWWWNWWW" },
	[ "61" ] = { frames =  435, steps =  46, moves = "WWWWWWNWWWNEESESESNNWWNWWSEEESSSSWWNWWWNWWWWWW" },
	[ "61i"] = { frames =  453, steps =  48, moves = "WWSWWWWSWWWSEENNEENSSWWSWWNEEENNNNWWSWWWSWWWNWWW" },
	[ "62" ] = { frames =  585, steps =  63, moves = "WWSWWWNWWNEESSWWWSWENEEENNWWSEENNWWWWSSESEEENNNNWWWWSWWWWWWSWWW" },
	[ "62i"] = { frames =  585, steps =  63, moves = "WWWWWWSWSEENNWWWNWESEEESSWWNEESSWWWWNNENEEESSSSWWWWNWWWWWWNNWWW" },
	[ "63" ] = { frames =  476, steps =  52, moves = "WWNNWWWWSSSSSWNNNNWWNWWSSSNNNEESWSSEESSWWWWWWWNNNWWW" },
	[ "63i"] = { frames =  476, steps =  52, moves = "WWSSSWWWWNNNNNWSSSSWWSWWNNNSSSEENWNNEENNWWWWWWWSSWWW" },
	[ "64" ] = { frames =  328, steps =  35, moves = "WWNNWWWWSWWWEEESSSWNWWSWWWWWWNNNWWW" },
	[ "64i"] = { frames =  328, steps =  35, moves = "WWSSSWWWWNWWWEEENNNWSWWNWWWWWWSSWWW" },
	[ "65" ] = { frames =  342, steps =  36, moves = "WWWWWWWNNWWSEWSENWWNEESSSWWNWWWWWWWW" },
	[ "65i"] = { frames =  360, steps =  38, moves = "WWSWWWWWSSWWNEWNESWWSEENNNWSWWWWWWNWWW" },
	[ "66" ] = { frames =  246, steps =  26, moves = "WWSWWWWWWWWNEWWNESWWWWWWWW" },
	[ "66i"] = { frames =  246, steps =  26, moves = "WWWWWWWWWWSEWWSENWWWWWNWWW" },
	[ "67" ] = { frames =  317, steps =  34, moves = "WWNNWWWSWSSWSWWSWWNNEENNWWWWWSSWWW" },
	[ "67i"] = { frames =  335, steps =  36, moves = "WWSSSWWWNWNNWNWWNWWSSEESSWWWWWNNNWWW" },
	[ "68" ] = { frames =  243, steps =  26, moves = "WWWWWWNWWSWNWWESSWNWWWWWWW" },
	[ "68i"] = { frames =  261, steps =  28, moves = "WWSWWWWSWWNWSWWENNWSWWWWNWWW" },
	[ "69" ] = { frames =  320, steps =  34, moves = "WWNWWWWSWNSWSWNEENENWWWWSWWWWWSWWW" },
	[ "69i"] = { frames =  338, steps =  36, moves = "WWSSWWWNWWSNWNWSEESESWWWWNWWWWWNNWWW" },
	[ "70" ] = { frames =  393, steps =  42, moves = "WWNNWWWSSSSSWWNENENNWSNNWWWWWSSSWWWWNNNWWW" },
	[ "70i"] = { frames =  393, steps =  42, moves = "WWSSSWWWNNNNNWWSEESSSWNSSWWWWWNNWNWWWSSWWW" },
	[ "71" ] = { frames =  382, steps =  42, moves = "WWNNWWWSSSSWWNNENNWWWSSESSWWWNNNNWWWWSSWWW" },
	[ "71i"] = { frames =  400, steps =  44, moves = "WWSSSWWWNNNNWWSSESSWWWNNENNWWWSSSSWWWWNNNWWW" },
	[ "72" ] = { frames =  360, steps =  39, moves = "WWSWWWWWSWWEENNNNWWSESSWNESSWWWWWWWNWWW" },
	[ "72i"] = { frames =  342, steps =  37, moves = "WWWWWWWNWWEESSSSWWNENNWSENNWWWWWWWWWW" },
	[ "73" ] = { frames =  353, steps =  36, moves = "WWWWWWNWWSENENWWWEESSWNNWWSSWWWWWWWW" },
	[ "73i"] = { frames =  371, steps =  38, moves = "WWSWWWWSWWNESESWWWEENNWSSWWNNWWWWWNWWW" },
	[ "74" ] = { frames =  429, steps =  43, moves = "WWWWWSSWSWWWNNESWNNWWNEEENEESSWWWWWWWWWWWWW" },
	[ "74i"] = { frames =  447, steps =  45, moves = "WWSWWWNNNWWWWSSENWSSWWSEEESEENNWWWWWWWWWWNWWW" },
	[ "75" ] = { frames =  390, steps =  42, moves = "WWNNWWWWWWWWSSEEESENNNWWWWSSESWSWWWWNNNWWW" },
	[ "75i"] = { frames =  390, steps =  42, moves = "WWSSSWWWWWWWWNNEEENESSSWWWWNNENWNWWWWSSWWW" },
	[ "76" ] = { frames =  238, steps =  26, moves = "WWWWWWWSWNENNWWWSSWWWWWWWW" },
	[ "76i"] = { frames =  256, steps =  28, moves = "WWSWWWWWNWSESSWWWNNWWWWWNWWW" },
	[ "77" ] = { frames =  306, steps =  33, moves = "WWWWWWSSWWNNNEESSSSWWWWNWWNWWWWWW" },
	[ "77i"] = { frames =  324, steps =  35, moves = "WWSWWWNNWWWSSSEENNNNWWWWWSWSWWWNWWW" },
	[ "78" ] = { frames =  282, steps =  30, moves = "WWWWWSSSWWWEENNWWNWWWSWWWWNWWW" },
	[ "78i"] = { frames =  282, steps =  30, moves = "WWSWWWNNNWWWEESSWWSWWWNWWWWWWW" },
	[ "79" ] = { frames =  314, steps =  34, moves = "WWWWWWNNWWWEEESSSSWNWWWSWNNWWWWWWW" },
	[ "79i"] = { frames =  332, steps =  36, moves = "WWSWWWWSSWWWEEENNNNWSWWWNWSSWWWWNWWW" },
	[ "80" ] = { frames =  314, steps =  30, moves = "WWNNWWWSSSWWWNWNWWSSWSWWWNNWWW" },
	[ "80i"] = { frames =  314, steps =  30, moves = "WWSSSWWWNNNWWWSWSWWNNWNWWWSWWW" },
	[ "81" ] = { frames =  507, steps =  54, moves = "WWNNWWWSSSWSSWWNWSEENNEENNNWWSSSSWWNESWWNNWSSWWWNNNWWW" },
	[ "81i"] = { frames =  507, steps =  54, moves = "WWSSSWWWNNNWNNWWSWNEESSEESSSWWNNNNWWSENWWSSWNNWWWSSWWW" },
	[ "82" ] = { frames =  276, steps =  30, moves = "WWWWWWNNWWWEESSWNWWWSSWWWWNWWW" },
	[ "82i"] = { frames =  276, steps =  30, moves = "WWSWWWWSSWWWEENNWSWWWNNWWWWWWW" },
	[ "83" ] = { frames =  259, steps =  28, moves = "WWWWWWWNWESSWNWNSSWWWNWWWWWW" },
	[ "83i"] = { frames =  277, steps =  30, moves = "WWSWWWWWNWESSWNWNSSWWWNWWWNWWW" },
	[ "84" ] = { frames =  371, steps =  36, moves = "WWWWWWWWWSNNESEESSWWNNENWWWWSWWWWWWW" },
	[ "84i"] = { frames =  389, steps =  38, moves = "WWSWWWWWWWSNNESEESSWWNNENWWWWSWWWWNWWW" },
	[ "85" ] = { frames =  353, steps =  36, moves = "WWWWWSSWWNWWWEEENEENNWWSWWWSWWWWWWWW" },
	[ "85i"] = { frames =  371, steps =  38, moves = "WWSWWWSSWWNWWWEEENEENNWWSWWWSWWWWWNWWW" },
	[ "86" ] = { frames = 1218, steps = 128, moves = "WWNNWWWWSSWNESSWWWNEEESSWNESSWWWWWNEEEEESSWNESSWWWNEEESSWNESSSSWNESSWWWNEEESSWNESSWWWWWNEEEEESSWNESSWWWNEEESSWNESSWWWWWWWWWNNWWW" },
	[ "86i"] = { frames = 1218, steps = 128, moves = "WWSSSWWWWNNWSENNWWWSEEENNWSENNWWWWWSEEEEENNWSENNWWWSEEENNWSENNNNWSENNWWWSEEENNWSENNWWWWWSEEEEENNWSENNWWWSEEENNWSENNWWWWWWWWWSWWW" },
	[ "87" ] = { frames =  486, steps =  52, moves = "WWWWWNNWWEESSSSWWEENNWSNNWSENNWWWSWWNESWWNNWSSWWWWWW" },
	[ "87i"] = { frames =  504, steps =  54, moves = "WWSWWWNNWWEESSSSWWEENNWNSSWNESSWWWNWWSENWWSSWNNWWWNWWW" },
	[ "88" ] = { frames =  884, steps =  90, moves = "WWNNWWWWWSSNNWWSSSSSWWWNNEEWWSSEEENNNNSSSSEENENWWENNNWWWEEESSSSSWWNNNNSSEENNNWWWWWWWWSSWWW" },
	[ "88i"] = { frames =  902, steps =  92, moves = "WWSSSWWWWWNNSSWWNNNNNEESESWWENNWWSSSSNNEESSSWWWENNNNNWWWSSEEWWNNEEESSSSNNEESSSWWWWWWWWNNNWWW" },
	[ "89" ] = { frames =  577, steps =  60, moves = "WWNNWWWSWWWSWWNEEESEENNWSESWWWWNWSSSWSEEENEESWWWWWWWWWNNNWWW" },
	[ "89i"] = { frames =  577, steps =  60, moves = "WWSSSWWWNWWWNWWSEEENEESSWNENWWWWSWNNNWNEEESEENWWWWWWWWWSSWWW" },
	[ "90" ] = { frames =  388, steps =  41, moves = "WWNNWWWWSSSSSWWWNWSEEENENNWWSWWNWWWWSSWWW" },
	[ "90i"] = { frames =  406, steps =  43, moves = "WWSSSWWWWNNNNNWWSWWNEESEESSWWNWWSWWWWNNNWWW" },
	[ "91" ] = { frames =  604, steps =  62, moves = "WWWWWNNWWWWWWSSESSSEEEENNWEENNWNWSSNEESSWWWWESSWWNNENWWWWWWWWW" },
	[ "91i"] = { frames =  622, steps =  64, moves = "WWSWWWSWSWWWWWNNENNNEEEESSWEESSSWWNNSEENNWWWWENNWWSSESWWWWWWNWWW" },
	[ "92" ] = { frames =  483, steps =  46, moves = "WWWWWNNWWSSWSWSEEENENWWWWWSESEENENWWWWWWWWWWWW" },
	[ "92i"] = { frames =  501, steps =  48, moves = "WWSWWWNNWWSSWSWSEEENENWWWWWESESENENWWWWWWWWWNWWW" },
	[ "93" ] = { frames =  296, steps =  31, moves = "WWSWWWWWSSWWNENNWWNESWWSWWWNWWW" },
	[ "93i"] = { frames =  278, steps =  29, moves = "WWWWWWWNNWWSESSWWSENWWNWWWWWW" },
	[ "94" ] = { frames =  320, steps =  34, moves = "WWWWWWNWWEESSWWNWSWENNWSWNWSWWWWWW" },
	[ "94i"] = { frames =  338, steps =  36, moves = "WWSWWWWNWWEESSWWNWSWENNWSWNWSWWWNWWW" },
	[ "95" ] = { frames =  319, steps =  34, moves = "WWWWWNNWWSESNNWWSSWWSSWSWNNNWWWWWW" },
	[ "95i"] = { frames =  337, steps =  36, moves = "WWSWWWSSWWNENSSWWNNWWNNWNWSSSWWWNWWW" },
	[ "96" ] = { frames =  502, steps =  51, moves = "WWNNWWWWSSWESNNNWWWWSSENSWWWSNNNEEESSWSSWWWWWNNNWWW" },
	[ "96i"] = { frames =  502, steps =  51, moves = "WWSSSWWWWNNWENSSSWWWWNNESNWWWNSSSEEENNWNNWWWWWSSWWW" },
	[ "97" ] = { frames =  365, steps =  38, moves = "WWNNWWWSSSWWSWWNNNEENNWSENNWWWWWWSSWWW" },
	[ "97i"] = { frames =  383, steps =  40, moves = "WWSSSWWWNNNWWNWWSSSEESSWNESSWWWWWWNNNWWW" },
	[ "98" ] = { frames =  370, steps =  40, moves = "WWWWWWNNWWSESESSWNNNSSSWWWNENNWSWWWWWWWW" },
	[ "98i"] = { frames =  388, steps =  42, moves = "WWSWWWWSSWWNENENNWSSSNNWNWWSESSWNWWWWWNWWW" },
	[ "99" ] = { frames =  652, steps =  66, moves = "WWNNWWWSWSWSWWWNNWSESSWWNEEEESENNWNEENESSSWSWWWWSWNNNENWWNWWWSSWWW" },
	[ "99i"] = { frames =  670, steps =  68, moves = "WWSSSWWWNWNWNWWWSSWNENNWWSEEEENESSWSEESENNNWNWWWWNWSSSESWWSWWWNNNWWW" },
	["100" ] = { frames =  499, steps =  48, moves = "WWWWWNWNWSSNWEEESWWWNWSESWENEESWWWWWWSSWWWNNNWWW" },
	["100i"] = { frames =  499, steps =  48, moves = "WWSWWWSWSWWENNSEENWWWSWNENWSEEENWWWWWWNNWWWSSWWW" },
	["101" ] = { frames =  873, steps =  82, moves = "WWNNWWWSSWWSWWNWESEENWWWWEESWESSWNESENNENWWWWEESWWWNSEESSEEENNENWWWWWSWNNNWWWSSWWW" },
	["101i"] = { frames =  891, steps =  84, moves = "WWSSSWWWNNWWNWWSWENEESWWWWEENWENNWSENESSESWWWWEENWWWSNEEENNEESSESWWWWWNWSSSWWWNNNWWW" },
	["102" ] = { frames =  755, steps =  74, moves = "WWSSSWWWNNNWWSSEENNNWWSEWWWNNEEEESSSWWWWNWWNNWSSEEWWSSENENWNWSSSSWWWNNNWWW" },
	["102i"] = { frames =  737, steps =  72, moves = "WWNNWWWSSSWWNNEESSSWWNEWWWSSEEEENNNWWWWSWWSSWNNEEWWNNESESWSWNNNNWWWSSWWW" },
	["103" ] = { frames =  692, steps =  73, moves = "WWSSSWWWWWWWNNNEEENNWWWSSWWSSSEENNSSWWNNNEEWWNSSSSEENNNNEEESSWWWWWWWWWWWW" },
	["103i"] = { frames =  692, steps =  73, moves = "WWNNWWWWWWWSSSEEESSWWWNNWWNNNEESSNNWWSSSEEWWSNNNNEESSSSEEENNWWWWWWWWWNWWW" },
	["104" ] = { frames =  600, steps =  60, moves = "WWSSSWWWNWWWWWSWWNEEEEESEENWSWNNNWWWEEENNWSESWWWWWNNWWWSSWWW" },
	["104i"] = { frames =  600, steps =  60, moves = "WWNNWWWSWWWWWNWWSEEEEENEESWNWSSSWWWEEESSWNENWWWWWSSWWWNNNWWW" },
	["105" ] = { frames =  423, steps =  43, moves = "WWSWWWNNWWWSNWWSSWEEESEESNWWSNWWSWWNWWWNWWW" },
	["105i"] = { frames =  405, steps =  41, moves = "WWWWWSSWWWNNWENEENSWWNNWWSWESSSNWWNWWWWWW" },
	["106" ] = { frames =  464, steps =  50, moves = "WWNNWWWSWSWSWNESSWWSWWEENEENNWNWWSSSWNESSWWWWWNWWW" },
	["106i"] = { frames =  464, steps =  50, moves = "WWSSSWWWNWNWNWSENNWWNWWEESEESSSWWWNNNWSENNWWWWWWWW" },
	["107" ] = { frames =  544, steps =  58, moves = "WWSWWWWSNNWWWNNWWWSENEEEEESSWNESSWWWNSESWWNESSWWSWWWWNNWWW" },
	["107i"] = { frames =  526, steps =  56, moves = "WWWWWWNSSWWWSSWWWNESEEEEENNWSENNWWWSNENWWSENNWWNWWWWSWWW" },
	["108" ] = { frames =  314, steps =  34, moves = "WWSWWWWSWWWEEENNNWWSWNSSWNWWWWWWWW" },
	["108i"] = { frames =  314, steps =  34, moves = "WWWWWWNWWWEEESSSWWNWSNNWSWWWWWNWWW" },
	["109" ] = { frames = 1083, steps = 115, moves = "WWWWWWWWWEEEESSWNESSWWWNEEESSWNESSWWWWWEEEEENNWSENNWWWSEEENNWSENNWWWWWSWENEEEEENNWSENNWWWSEEENNWSENNWWWWWSWSWWWWWWW" },
	["109i"] = { frames = 1101, steps = 117, moves = "WWSWWWWWWWEEEESSWNESSWWWNEEESSWNESSWWWWWEEEEENNWSENNWWWSEEENNWSENNWWWWWSWENEEEEENNWSENNWWWSEEENNWSENNWWWWWSWSWWWWNWWW" },

	-- Hard
	["110" ] = { frames =  502, steps =  46, moves = "WWWWWNWNWWWSSSSSWWNESENSEENWWWSWNENWSWNNWWWWWW" },
	["110i"] = { frames =  520, steps =  48, moves = "WWSWWWSWSWWWNNNNNWWSENESNEESWWWNWSESWNWSSWWWNWWW" },
	["111" ] = { frames =  551, steps =  55, moves = "WWWWWWSWWSWEEESWWNNEENNWWSSSWWWENENNNWWWSESSENNWWWWWWWW" },
	["111i"] = { frames =  569, steps =  57, moves = "WWSWWWWNWWNWNESEESSSWNNENWSWNWWWESESSSWWWNENNESSWWWWWNWWW" },
	["112" ] = { frames =  344, steps =  36, moves = "WWWWWWNNWWESESSWNNENWWWSSSWWNWWWWWWW" },
	["112i"] = { frames =  362, steps =  38, moves = "WWSWWWWNNWWESESSWNNENWWWSSSWWNWWWWNWWW" },
	["113" ] = { frames =  542, steps =  57, moves = "WWSWWWNWWWWSEEESSWNNWWSWWSSEEENNNEENNWSESSWWNNNWWSSWWWWWW" },
	["113i"] = { frames =  542, steps =  57, moves = "WWWWWSWWWWNEEENNWSSWWNWWNNEEESSSEESSWNNENWWSSSWWNNWWWNWWW" },
	["114" ] = { frames =  351, steps =  32, moves = "WWSWWWNNWWSWWNWWSESWSSWNNWWWNWWW" },
	["114i"] = { frames =  333, steps =  30, moves = "WWWWWSSWWNWWSWWNENWNNWSSWWWWWW" },
	["115" ] = { frames =  578, steps =  58, moves = "WWSSSWWWNNWWWEEESSWNENWWWWNWSEEEESSWNENWWWSWNNNNWWWWWSSWWW" },
	["115i"] = { frames =  578, steps =  58, moves = "WWNNWWWSSWWWEEENNWSESWWWWSWNEEEENNWSESWWWNWSSSSWWWWWNNNWWW" },
	["116" ] = { frames =  466, steps =  49, moves = "WWWWWSSSWWWWWNSEEEEENNNNNWWWSSWWWNEEESSWWWWWWWWWW" },
	["116i"] = { frames =  484, steps =  51, moves = "WWSWWWNNNWWWWWSNEEEEESSSSSWWWNNWWWSEEENNWWWWWWWNWWW" },
	["117" ] = { frames =  474, steps =  49, moves = "WWNNWWWWWSEESWWSNWWSNNNWWSSSENWWNNEESSSWWNWWWNWWW" },
	["117i"] = { frames =  474, steps =  49, moves = "WWSSSWWWWWNEENWWNSWWNSSSWWNNNESWWSSEENNNWWSWWWWWW" },
	["118" ] = { frames =  445, steps =  46, moves = "WWSWWWWSWWWWEEEENESWWWWWENNNNWESSWSNNWWSWWWWWW" },
	["118i"] = { frames =  445, steps =  46, moves = "WWWWWWNWWWWEEEESENWWWWWESSSSWENNWNSSWWNWWWNWWW" },
	["119" ] = { frames =  545, steps =  58, moves = "WWNNWWWWWWSSNNWWWSENEEESWNEESSENNSSWNESSWWWSSWWWWWWWNNNWWW" },
	["119i"] = { frames =  545, steps =  58, moves = "WWSSSWWWWWWNNSSEENWSWWWNESEEENNESSNNWSENNWWWNNWWWWWWWSSWWW" },
	["120" ] = { frames =  333, steps =  32, moves = "WWWWWSSSWWWNNNSEENNWWWWWWSWWWWWW" },
	["120i"] = { frames =  351, steps =  34, moves = "WWSWWWNNNWWWSSSNEESSWWWWWWNWWWNWWW" },
	["121" ] = { frames =  629, steps =  58, moves = "WWNNWWWWWWWWSWSENEEEEESSSSWNNENNWWWWNWSESWNWSSSWSWWWNNNWWW" },
	["121i"] = { frames =  629, steps =  58, moves = "WWSSSWWWWWWWWNWNESEEEEENNNNWSSESSWWWWSWNENWSWNNNWNWWWSSWWW" },
	["122" ] = { frames =  462, steps =  48, moves = "WWWWWWWWNWWNWESESESSESEENWWSWNNSEENNWWWWWWWWWWWW" },
	["122i"] = { frames =  480, steps =  50, moves = "WWSWWWWWWSWWSWENENENNENEESWWNWSSNEESSWWWWWWWWWNWWW" },
	["123" ] = { frames =  624, steps =  64, moves = "WWSSSWWWNNSSWWNNNESSSSWWNNWSEENNNWWSSSWSENEESSWNENWWWWSWNNWWWWWW" },
	["123i"] = { frames =  624, steps =  64, moves = "WWNNWWWSSNNWWSSSENNNNWWSSWNEESSSWWNNNWNESEENNWSESWWWWNWSSWWWNWWW" },
	["124" ] = { frames =  624, steps =  62, moves = "WWWWWSSSWWWWWNSEENWSEENNNNNWESEESSWWEESSWWWNNNNENWWWWWSSWWWWWW" },
	["124i"] = { frames =  642, steps =  64, moves = "WWSWWWNNNWWWWWSNEESWNEESSSSSWENEENNWWEENNWWWSSSSESWWWWWNNWWWNWWW" },
	["125" ] = { frames = 1207, steps = 118, moves = "WWWWWNNWWWSSESWNNNESSNNEESSWWEESSSWNNWWWEENNNWSESSWWWEENNNEESESSWWWWWWEEEEESSWNENWWWWWNWSEEEEESSWNENWWWWNWSSSWWWNNNWWW" },
	["125i"] = { frames = 1207, steps = 118, moves = "WWSWWWSSWWWNNENWSSSENNSSEENNWWEENNNWSSWWWEESSSWNENNWWWEESSSEENENNWWWWWWEEEEENNWSESWWWWWSWNEEEEENNWSESWWWWSWNNNWWWSSWWW" },
	["126" ] = { frames =  534, steps =  36, moves = "WWWWWNNWWWWWWSSSNNNEESSSSWWWNNWWWWWW" },
	["126i"] = { frames =  552, steps =  38, moves = "WWSWWWSSWWWWWWNNNSSSEENNNNWWWSSWWWNWWW" },
	["127" ] = { frames =  595, steps =  60, moves = "WWWWWSSSWWNWWNWSENEEENNNWSWWSWEEEESWSSEENWSWNNENWWWWWWWWWWWW" },
	["127i"] = { frames =  613, steps =  62, moves = "WWSWWWNNNWWSWWSWNSEEEESSSWNWWNWEEEENWNNEESWNWSSESWWWWWWWWWNWWW" },
	["128" ] = { frames =  765, steps =  71, moves = "WWNNWWWSWWNWWSSSEEENNWSSWWWSWNNNEEENESSESSESWWWNENESSESWWWWWWWWWWNNNWWW" },
	["128i"] = { frames =  765, steps =  71, moves = "WWSSSWWWNWWSWWNNNEEESSWNNWWWNWSSSEEESENNENNENWWWSESENNENWWWWWWWWWWSSWWW" },
	["129" ] = { frames =  601, steps =  60, moves = "WWWWWNWWWWWWSENEEEESESSWWWWSWWNEEEESENNNENWWWWWSWNNWWWWSSWWW" },
	["129i"] = { frames =  619, steps =  62, moves = "WWSWWWSWWWWNWSEEEENNENWWWWNWWSEEEENESSSESWWWWNWSESWWWWWWNNNWWW" },
	["130" ] = { frames =  611, steps =  56, moves = "WWWWWNNWWWWSSNNEESSWWWSEESSEENENWWWWWEESSWWNNENWWWWWWWWW" },
	["130i"] = { frames =  629, steps =  58, moves = "WWSWWWSSWWWWNNSSEENNWWWNEENNEESESWWWWWWWNNEESSESWWWWWWNWWW" },
	["131" ] = { frames =  318, steps =  34, moves = "WWSWWWNWNWSWSSESWWWNENNWWWSWWWNWWW" },
	["131i"] = { frames =  300, steps =  32, moves = "WWWWWSWSWNWNNENWWWSESSWWWNWWWWWW" },
	["132" ] = { frames =  518, steps =  48, moves = "WWWWWWSWWWEENENWNWWSSNNEESSWSWWNNESESWWWWWWWNWWW" },
	["132i"] = { frames =  518, steps =  48, moves = "WWSWWWWNWWWEESESWSWWNNSSEENNWNWWSSENENWWWWWWWWWW" },
	["133" ] = { frames =  811, steps =  81, moves = "WWWWWWSSSWWWWWNENEESSEENWSEENNNWWNESNWSENNWWWSWWEESESWNNNWWSSSSEENNWNNWWSSWWWWWWW" },
	["133i"] = { frames =  829, steps =  83, moves = "WWSWWWWNNNWWWWWESSEENNEESWNEESSSWWSENSWNESSWWWNWWEENENWSSSWWNNNNEESSWSSWWNNWWWWNWWW" },
	["134" ] = { frames =  597, steps =  60, moves = "WWWWWSSSWWWNNEWWNESEEENWWSWNENWWSSSENENENWSWNENWWWWWSSWWWWWW" },
	["134i"] = { frames =  615, steps =  62, moves = "WWSWWWNNNWWWSSEWWSENEEESWWNWSESWWNNNESESESWNWSESWWWWWNNWWWNWWW" },
	["135" ] = { frames =  665, steps =  68, moves = "WWNNWWWSWWWNWWSEENESWSSSNNENEESSWENNWSSSSWWNNENENWSWNENWWWWWWWWSSWWW" },
	["135i"] = { frames =  683, steps =  70, moves = "WWSSSWWWNWWWSWWNEESENWNNNSSESEENNWESSWNNNNWWSSESESWNWSESWWWWWWWWNNNWWW" },
	["136" ] = { frames =  741, steps =  76, moves = "WWSSSWWWWWWWWNNWWNNENEESEWWSWWSENENNWSSSNNEEEEEESSWNENWWWWWNWSSSSSWWWWNNNWWW" },
	["136i"] = { frames =  723, steps =  74, moves = "WWNNWWWWWWWWSSWWSSESEENEWWNWWNESESSWNNNSSEEEEEENNWSESWWWWWSWNNNNNWWWWSSWWW" },
	["137" ] = { frames = 1079, steps = 109, moves = "WWSSSWWWNNNNNWWWSESSSSWNENNNEESSSSWESWWWWWWEEEENNWSENNNENNWWSSENENNWWSWNEEWWSENWWSSSNNNWSEENEEESWWWWWWSWWWWWW" },
	["137i"] = { frames = 1079, steps = 109, moves = "WWNNWWWSSSSSWWWNENNNNWSESSSEENNNNWENWWWWWWEEEESSWNESSSESSWWNNESESSWWNWSEEWWNESWWNNNSSSWNEESEEENWWWWWWNWWWNWWW" },
	["138" ] = { frames =  671, steps =  70, moves = "WWSSSWWWWWNNNNENWSSSSSEENWSWNNNNESWSSWWWNEEEWWWSENSWNEEWWSENNWWWWWWWWW" },
	["138i"] = { frames =  671, steps =  70, moves = "WWNNWWWWWSSSSESWNNNNNEESWNWSSSSENWNNWWWSEEEWWWNESNWSEEWWNESSWWWWWWNWWW" },
	["139" ] = { frames =  883, steps =  94, moves = "WWWWWWWWWNNWSSEEESSSENNWNNEESWWWWEESSSWWNNSSWWNESEEENNNNNWWWSSSNNNEEESSWWEESSSWWNWWSWNNNWWWWWW" },
	["139i"] = { frames =  901, steps =  96, moves = "WWSWWWWWWWSSWNNEEENNNESSWSSEENWWWWEENNNWWSSNNWWSENEEESSSSSWWWNNNSSSEEENNWWEENNNWWSWWNWSSSWWWNWWW" },
	["140" ] = { frames =  559, steps =  55, moves = "WWWWWNNWWWSSNNWWWESESSEESEENWWNNWWWEEESSWNENWWWWWWWWWWW" },
	["140i"] = { frames =  577, steps =  57, moves = "WWSWWWSSWWWNNSSWWWENENNEENEESWWSSWWWEEENNWSESWWWWWWWWNWWW" },
	["141" ] = { frames =  659, steps =  65, moves = "WWWWWSSWWNNNSSSWWWNEESSEENNNWWWWEESSWNESSWSSENNNNENWWWWSSWWWWWWWW" },
	["141i"] = { frames =  677, steps =  67, moves = "WWSWWWNNWWSSSNNNWWWSEENNEESSSWWWWEENNWSENNWNNESSSSESWWWWNNWWWWWNWWW" },
	["142" ] = { frames =  847, steps =  82, moves = "WWWWWSWENNNWSSSESWNWSESWWWWNWNESSENSEENNWWSWNEEEENNWNWWSSNNEEESSSWWWSWNENWWWWWWWWW" },
	["142i"] = { frames =  865, steps =  84, moves = "WWSWWWNWSESSWNNNENWSWNENWWWWSWSENNESNEESSWWNWSEEEESSWSWWNNSSEENENNWWWNWSESWWWWWWNWWW" },
	["143" ] = { frames =  709, steps =  74, moves = "WWNNWWWWWSSENSEESSSWWWNNWWNNNEESNWWSSSEEWWSNNNNEESSSSEEENNNWWSWWWNWWWWWWWW" },
	["143i"] = { frames =  727, steps =  76, moves = "WWSSSWWWWWNNESNEENNNWWWSSWWSSSEENSWWNNNEEWWNSSSSEENNNNEEESSSWWNWWWWWSWWWNWWW" },
	["144" ] = { frames = 1051, steps = 107, moves = "WWWWWWSSSWWWWWNNENENWSSWSSEEENEEENNWWWESSWSWWWNNENSWSSEEEEENNNNNWSESWWESSSWWWWNNENEEEESSWWNSEENNWWWWWWWWWWW" },
	["144i"] = { frames = 1069, steps = 109, moves = "WWSWWWWNNNWWWWWSSESESWNNWNNEEESEEESSWWWENNWNWWWSSESNWNNEEESESSESSWNENWWENNNWWWWSSESEEEENNWWSNEESSWWWWWWWWNWWW" },
	["145" ] = { frames = 1129, steps = 115, moves = "WWWWWNNWWWWSENWWSSSNNEESSSSEENNWNWSNNNWWSWSENESENENWWWWEESSEESSWWNNNENNWWWSWWESNESWNNWEEENEESWWWWWEESSWNNWWSSWWWWWW" },
	["145i"] = { frames = 1147, steps = 117, moves = "WWSWWWSSWWWWNESWWNNNSSEENNNNEESSWSWNSSSWWWNNESENESESWWWWWWENSEEENEENNNWWSSSESSWWWNNWSSWESEEEENWWWWWEENNWSSWWNNWWWNWWW" },
	["146" ] = { frames = 1022, steps = 104, moves = "WWWWWSSWSWWEENENNNWNWWEESSWNESSWWWNEEWWSENWSSWEEEENNWWWEEESSWSSENNWWWSEEEWWNNWSENNEWNENESESWWWWWWWWWWWWW" },
	["146i"] = { frames = 1040, steps = 106, moves = "WWSWWWNNWNWWEESESSSWSWWEENNWSENNWWWSEEWWNESWNNWEEEESSWWWEEENNWNNESSWWWNEEEWWSSWNESSEWSESENENWWWWWWWWWWNWWW" },
	["147" ] = { frames =  968, steps = 100, moves = "WWSSSWWWNNNWWSWENEESSSWWNNEESSSWWWWNNWWSENEEEESSEENWSWNNWNNWSESEENWWWESWSSWWNESENNNENWSWNNWWWWWSSWWW" },
	["147i"] = { frames =  968, steps = 100, moves = "WWNNWWWSSSWWNWSEEENNNWWSSEENNNWWWWSSWWNESEEESSWNENWNNWWSENESSESWWNEEENNEESWNWSSESWWWNWSSSWWWWWNNNWWW" },
	["148" ] = { frames = 1239, steps = 132, moves = "WWSSSWWWWENNNNNWWWSSEENNWWWWWWSSEESSNNEENNEEESSWWWEESSWWNNEENNWWWWWWSSEEEEEWWWWWNNEEEESNWWWWSSEEEESSEENNWWWWWEEENNWWWWSSSSSWWWNNNWWW" },
	["148i"] = { frames = 1221, steps = 130, moves = "WWNNWWWWESSSSSWWWNNEESSWWWWWWNNEENNSSEESSEEENNWWWEENNWWSSEESSWWWWWWNNEEEEEWWWWWSSEEEENSWWWWNNEEEENNEESSWWWWWEEESSWWWWNNNNNWWWSSWWW" },
	["149" ] = { frames =  903, steps =  94, moves = "WWWWWSSWWWSWWWWNEENSSEEEENWWWEEEENNWSENNWWWSSEENENNWWWWWSSNNWNSEENNESWSESSSEENENNWWWWWWWWWWWWW" },
	["149i"] = { frames =  921, steps =  96, moves = "WWSWWWNNWWWNWWWWSEESNNEEEESWWWEEEESSWNESSWWWNNEESESSWWWWWNNSSWSNEESSENWNENNNEESESSWWWWWWWWWWNWWW" },
}

-- Code addresses.
local TITLE_SCREEN_ADDR = 0x02bc
local SWITCH_PLAYERS_ADDR = 0x1400 -- when switching players with Select, or starting a floor in Heading Out?
local FLOOR_END_ADDR = 0x17dc -- WAY TO GO / TRY AGAIN subroutine
local MENU_ADDR = 0x2cc0 -- inside the menu subroutine, after initialization of MENU_SELECTED_ADDR, MENU_MAX_COL_ADDR, and MENU_MAX_ROW_ADDR
local MENU_RET_ADDR = 0x2df0 -- return from the menu subroutine
local COURSE_MENU_DIGIT_ADDR = 0x3cec -- code that changes the selected digit in the SELECT COURSE menu
local COURSE_MENU_RET_ADDR = 0x3d0d -- return from the SELECT COURSE menu subroutine

-- Variable addresses.
local NUM_FLOORS_ADDR = 0xc2b9
local DISPLAY_ADDR = 0xc2be
local SKILL_ADDR = 0xc2c0
local PLAYER1_POS_INDEX_ADDR = 0xc2d4
local FRAME_COUNTER_ADDR = 0xc2ec
local RNG_SEED_ADDR = 0xc377
local MENU_MAX_COL_ADDR = 0xcf11
local MENU_MAX_ROW_ADDR = 0xcf12
local MENU_SELECTED_ADDR = 0xcf19
local JOYPAD_INPUT_ADDR = 0xff8b -- the current state of joypad buttons as of the most recent poll
local JOYPAD_PRESSED_ADDR = 0xff8c -- just the joypad buttons that became newly pressed in the most recent poll

-- Constants.
local JOYPAD_BUTTON_A = 0x01
local JOYPAD_BUTTON_START = 0x08
local SKILL_EASY = 0
local SKILL_AVERAGE = 1
local SKILL_HARD = 2
local SKILL_LABELS = { [SKILL_EASY] = "easy", [SKILL_AVERAGE] = "average", [SKILL_HARD] = "hard", }
local DISPLAY_DIAGONAL = 0
local DISPLAY_BIRDSEYE = 1
local DISPLAY_LABELS = { [DISPLAY_DIAGONAL] = "diagonal", [DISPLAY_BIRDSEYE] = "birdseye", }

-- BASE_SAVESTATE is a constant (assigned in the "do" block below) that
-- represents the state of the emulator at the point at which this script is
-- run. All other savestates derive from BASE_SAVESTATE.
local BASE_SAVESTATE

-- The contents of this "do" block define a data-oriented immutable savestate
-- manipulation library. The core concept is that of a savestate object, which
-- represents the state of the emulator at a certain point. Once created, a
-- savestate object never changes. The only way to get a new savestate object
-- is to start from an existing savestate object and pass it to one of the
-- functions defined in this block (or a function defined in terms of those
-- functions, etc.). The important thing is that there is no implicit
-- dependency on the current emulator state. The idea is to remove the emulator
-- state as a hidden "global variable", mutable and available to all code, and
-- instead to make function outputs depend only on explicit inputs.
--
-- The "local" functions in the block are for internal use. They are the only
-- ones that are allowed to directly mutate the current emulator state. The
-- non-local functions exported from the block, and all other code, may change
-- the emulator state only indirectly, as an invisible side effect using the
-- safe primitives exported from this block. For example, the local function
-- run_until_event_raw effectively takes the emulator state as one of its
-- inputs and mutates the emulator state as one of its outputs; whereas the
-- non-local function run_until_event takes a savestate object as input and
-- returns a different savestate object as output.
do
	-- Hide some modules that can manipulate the emulator state and should
	-- not be used outside the savestate functions. This is not meant to be
	-- bulletproof, only to safeguard against accidental misuse.
	local joypad = joypad
	local memory = memory
	local memorysavestate = memorysavestate
	local savestate_module = savestate
	do
		local env = _ENV
		local t = {}
		setmetatable(t, {__index = function (t, key)
			if ({
				joypad = true,
				memory = true,
				memorysavestate = true,
				savestate = true,
			})[key] then
				return nil
			end
			return env[key]
		end})
		_ENV = t
	end

	-- Define SAVESTATE_DIRECTORY to save temporary savestates to disk
	-- rather than using in-memory core savestates. If you want to save the
	-- results of program execution as a movie file, you have to use actual
	-- on-disk savestates, not in-memory core savestates. This is because
	-- the latter do not store input logs. There is no function to delete
	-- the files when they are no longer used, so you will have to delete
	-- them yourself.
	local SAVESTATE_DIRECTORY = nil -- "/tmp"

	local save_emu_savestate, load_emu_savestate, delete_emu_savestate
	if SAVESTATE_DIRECTORY ~= nil then
		-- Generate temporary savestate filenames from a random
		-- basename and an incrementing counter. We're relying on
		-- math.random being somehow reasonably seeded.
		local digits = {}
		for i = 1, 16 do
			local r = math.random(16)
			digits[i] = string.sub("0123456789abcdef", r, r)
		end
		digits = table.concat(digits)
		local counter = 0
		local function generate_emu_savestate_path()
			local path = string.format("%s/tempsavestate-%s-%010d",
				SAVESTATE_DIRECTORY, digits, counter)
			counter = counter + 1
			return path
		end
		-- BizHawk does not have an API for deleting savestate files,
		-- or deleting files in general. While we cannot delete
		-- savestates that are no longer used (that have had their __gc
		-- metamethod called), we can, when creating a new savestate
		-- file, prefer to overwrite an existing, no-longer-used file,
		-- rather than create a new one. The recycle table holds a set
		-- of filenames of previously created savestate files that are
		-- no longer needed. save_emu_savestate tries to pull a name
		-- from the recycle table before creating a new file.
		local recycle = {}
		save_emu_savestate = function ()
			local path = next(recycle)
			if path ~= nil then
				recycle[path] = nil
			else
				path = generate_emu_savestate_path()
			end
			savestate_module.save(path, true)
			return path
		end
		load_emu_savestate = function (id)
			assert(savestate_module.load(id, true))
		end
		delete_emu_savestate = function (id)
			recycle[id] = true
		end
	else
		save_emu_savestate = memorysavestate.savecorestate
		load_emu_savestate = memorysavestate.loadcorestate
		delete_emu_savestate = memorysavestate.removestate
	end

	-- The metatable for all savestate objects. The important metamethod is
	-- __gc, which tells BizHawk to reclaim the resources of savestates
	-- that are no longer referenced.
	local savestate_mt = {
		__gc = function(savestate)
			delete_emu_savestate(savestate.id)
		end,
		__tostring = function(savestate)
			return string.format("savestate{%s}", savestate.id)
		end,
	}

	-- Create a new savestate object from the current state of the
	-- emulator. It is the caller's responsibility to ensure that the
	-- emulator state is frame-aligned.
	local function new_savestate()
		-- A savestate object consists of the identifier of an
		-- emulator-native savestate, and the joypad state.
		local savestate = {
			id = save_emu_savestate(),
			buttons = joypad.get(),
		}
		-- Keep the keys from joypad.get, but set all the values to false.
		for k in pairs(savestate.buttons) do
			savestate.buttons[k] = false
		end
		setmetatable(savestate, savestate_mt)
		return savestate
	end

	-- Run the emulator from its current state to the next onframestart
	-- event. The resulting aligned emulator state is safe to make or load
	-- a savestate from. See discussion starting at
	-- https://github.com/TASEmulators/BizHawk/issues/1430#issuecomment-1987446287.
	local function frame_align()
		local co = coroutine.running()
		local event_id
		event_id = event.onframestart(function ()
			event.unregisterbyid(event_id)
			assert(coroutine.resume(co))
		end)
		coroutine.yield()
	end

	-- Make the emulator state match the given savestate object. The
	-- emulator state does not need to be frame-aligned at the point when
	-- this function is called.
	local function restore_savestate(savestate)
		-- Set the current state of the joypad to what is in the
		-- savestate.
		joypad.set(savestate.buttons)

		-- The emulator state must be aligned at a frame boundary
		-- before loading a savestate. The emulator's current state is
		-- otherwise unimportant, because we throw it away.
		frame_align()
		load_emu_savestate(savestate.id)
	end

	-- Return a copy of the given savestate object.
	local function clone_savestate(savestate)
		restore_savestate(savestate)
		local new = new_savestate()
		for k, v in pairs(savestate.buttons) do
			new.buttons[k] = v
		end
		return new
	end

	-- Assume that at the point at which this script is run, it's a safe
	-- time to make a savestate (i.e., we're frame-aligned).
	BASE_SAVESTATE = new_savestate()

	-- Return a new savestate object that is a copy of a given savestate,
	-- with modified joypad buttons. The state of each button named by the
	-- keys of the buttons table is set to the corresponding value. The
	-- state of any button not represented in the buttons table is left
	-- unmodified.
	function savestate_joypad_set(savestate, buttons)
		local new = clone_savestate(savestate)
		-- Override the buttons.
		for k, v in pairs(buttons) do
			new.buttons[k] = v
		end
		return new
	end

	-- Run the emulator until the event registered by the register_event
	-- function happens, or frame_limit frames elapse, whichever happens
	-- first. If the register_event event happens first, return true; if
	-- frame_limit is exceeded first, return false. frame_limit may be an
	-- integer, or nil to mean "no limit". register_event should be a
	-- callback registration function such as event.on_bus_exec,
	-- event.onframeend, etc. The ... variable arguments are passed to
	-- register_event.
	local function run_until_event_raw(frame_limit, register_event, ...)
		if frame_limit ~= nil and frame_limit <= 0 then
			return false
		end
		local co = coroutine.running()
		-- We register two event callbacks: one for the event the
		-- caller is interested in (event_id), and one to count frames
		-- and enforce frame_limit (limit_id). We register both
		-- callbacks, then yield to let the emulator run until one of
		-- the two callbacks calls coroutine.resume. The event_id
		-- callback resumes with the value true; the limit_id callback
		-- resumes with the value false. After being resumed, we
		-- dispose of the callback that was not called. We register
		-- event_id before limit_id so that, if both callbacks happen
		-- for the same event, the caller's event takes precedence.
		local event_id = register_event(function ()
			if co ~= nil then
				assert(coroutine.resume(co, true))
			end
		end, ...)
		local frame_count = 0
		local limit_id = event.onframeend(function ()
			frame_count = frame_count + 1
			if frame_limit ~= nil and frame_count >= frame_limit then
				if co ~= nil then
					assert(coroutine.resume(co, false))
				end
			end
		end)
		local event_occurred = coroutine.yield()
		assert(event.unregisterbyid(event_id))
		assert(event.unregisterbyid(limit_id))
		-- We want only one of the two callbacks above to run
		-- coroutine.resume. At this point, one of them has done so --
		-- but just unregistering both callback IDs does not guarantee
		-- that the other will not also run coroutine.resume. That is
		-- because both callbacks may be looking for the very same
		-- event; i.e. register_event is event.onframeend and
		-- frame_limit is 1). BizHawk runs all callbacks that were
		-- registered at the time the event occurred, even if, in the
		-- course of working through the list of callbacks, a later one
		-- gets unregistered by an earlier one. We set co to nil as a
		-- signal to both callbacks that if they run, they should not
		-- call coroutine.resume.
		co = nil
		return event_occurred
	end

	-- Starting from the given savestate, return a new savestate that is
	-- the result of running until the event registered by register_event
	-- happens; or return nil if frame_limit frames elapse without the
	-- event happening. frame_limit may be nil for no limit. register_event
	-- should be a callback registration function such as
	-- event.on_bus_exec, event.onframeend, etc. The ... variable arguments
	-- are passed to register_event.
	function run_until_event(savestate, frame_limit, register_event, ...)
		restore_savestate(savestate)
		local event_occurred = run_until_event_raw(frame_limit, register_event, ...)
		if event_occurred then
			-- The event may not have occurred at a frame boundary,
			-- so align the emulator state before creating the new
			-- savestate.
			frame_align()
			return new_savestate()
		else
			return nil
		end
	end

	-- Return the result of calling f with no arguments, after making the
	-- current emulator state match the given savestate.
	local function with_savestate(savestate, f)
		restore_savestate(savestate)
		return f()
	end

	-- Return the frame count of the given savestate.
	function savestate_frame_count(savestate)
		return with_savestate(savestate, function () return emu.framecount() end)
	end

	-- Return the value of the u8 at addr in the given savestate.
	function savestate_read_u8(savestate, addr, domain)
		return with_savestate(savestate, function () return memory.read_u8(addr, domain) end)
	end
end

-- Return a new savestate one frame after the given savestate.
local function run_one_frame(savestate)
	-- It should be impossible for run_until_event to return nil
	-- (indicating that the frame_limit was exceeded) with these
	-- parameters, since event.onframeend.is the same event that
	-- run_until_event uses to count frames.
	return assert(run_until_event(savestate, 1, event.onframeend))
end

-- Return a new savestate representing the frame boundary after the address
-- addr is executed, or nil if frame_limit is reached.
local function run_until_exec(savestate, frame_limit, addr)
	return run_until_event(savestate, frame_limit, event.on_bus_exec, addr)
end

-- Return a new savestate representing the frame boundary after the address
-- addr is written, or nil if frame_limit is reached.
local function run_until_write(savestate, frame_limit, addr)
	return run_until_event(savestate, frame_limit, event.on_bus_write, addr)
end

-- Return a new savestate representing the frame boundary after the address
-- addr is written to and the written u8 value satisfies the test predicate, or
-- nil if frame_limit is reached.
local function run_until_u8_becomes_p(savestate, frame_limit, addr, test)
	repeat
		local frames = savestate_frame_count(savestate)
		savestate = run_until_event(savestate, frame_limit, event.on_bus_write, addr)
		if savestate == nil then
			return nil
		end
		local new_frames = savestate_frame_count(savestate)
		assert(new_frames >= frames)
		if frame_limit ~= nil then
			frame_limit = frame_limit - (new_frames - frames)
		end
	until test(savestate_read_u8(savestate, addr))
	return savestate
end

-- Return a new savestate representing the frame boundary after the given u8
-- value is written to the address addr, or nil if frame_limit is reached.
local function run_until_u8_becomes(savestate, frame_limit, addr, value)
	return run_until_u8_becomes_p(savestate, frame_limit, addr, function (v) return v == value end)
end

-- Return a new savestate representing the frame boundary after the address
-- addr contains a u8 value that satisfies the test predicate, or nil if
-- frame_limit is reached. This may be the same as the input savestate, if addr
-- in the input savestate already contains a satisfactory value.
local function run_until_u8_is_p(savestate, frame_limit, addr, test)
	if test(savestate_read_u8(savestate, addr)) then
		return savestate
	end
	return run_until_u8_becomes_p(savestate, frame_limit, addr, test)
end

-- Return a new savestate representing the frame boundary after the address
-- addr contains a the given u8 value, or nil if frame_limit is reached. This
-- may be the same as the input savestate, if addr in the input savestate
-- already contains the given value.
local function run_until_u8_is(savestate, frame_limit, addr, value)
	return run_until_u8_is_p(savestate, frame_limit, addr, function (v) return v == value end)
end

-- Find the earliest frame at which running an action function on a savestate
-- produces a savestate that satisfies a test predicate.
--
-- The action function takes a savestate as input and returns a savestate as
-- output. The test function takes a savestate as input and returns a Boolean
-- output (nil|false or otherwise). This function runs the action function on
-- the given savestate, then runs the test predicate on the resulting
-- savestate. If the test function's return value is not nil and not false, it
-- returns two values: the return value of action and the return value of test.
-- If the test function returns nil or false, it goes back to the original
-- savestate, advances one frame, and tries the action and test again. The
-- process continues, advancing one frame at a time, until the test predicate
-- succeeds.
--
-- It is common to call this function with a test predicate whose return value
-- is either a savestate or nil. In that case, you can regard the two return
-- values as "savestate after action performed" and "savestate after test
-- passed".
local function earliest_effective_frame(savestate, action, test)
	local action = action or function (savestate) return savestate end
	local frames = savestate_frame_count(savestate)
	while true do
		local action_performed = action(savestate)
		local test_result = test(action_performed)
		if test_result then
			return action_performed, test_result
		end
		-- No luck with this frame. Try doing the action one frame later.
		savestate = run_one_frame(savestate)
		local new_frames = savestate_frame_count(savestate)
		assert(new_frames == frames + 1, string.format("frames=%d new_frames=%d", frames, new_frames))
		frames = new_frames
	end
end

local function menu_confirm_button(savestate)
	-- Menus may be confirmed by pressing either A or Start. The button
	-- must be newly pressed in the most recent poll of the joypad; i.e.,
	-- there must have been another poll in which it was released since the
	-- last time it was pressed. There are no joypad polls between menus,
	-- so when a menu requires no directional inputs, the button used to
	-- confirm the previous menu still counts as pressed. Therefore if one
	-- of the two buttons is already pressed, we want to press the other
	-- one, to avoid having to release and re-press the first button.
	local joypad_pressed = savestate_read_u8(savestate, JOYPAD_PRESSED_ADDR)
	return ({
		[0] = "A", -- if nothing pressed, default to A
		[JOYPAD_BUTTON_START] = "A", -- if Start pressed, press A
		[JOYPAD_BUTTON_A] = "Start", -- if A pressed, press Start
		[JOYPAD_BUTTON_A | JOYPAD_BUTTON_START] = "A", -- if both pressed, default to A
	})[joypad_pressed & (JOYPAD_BUTTON_A | JOYPAD_BUTTON_START)]
end

-- Return the value x of the array t that minimizes key(x).
local function min_by_key(t, key)
	local min, min_key
	for _, x in ipairs(t) do
		local k = key(x)
		if min_key == nil or k < min_key then
			min = x
			min_key = k
		end
	end
	return min
end

local function select_menu_item(savestate, target)
	-- A menu is a 2d grid. (Most menus have just 1 column, but the SELECT
	-- FLOOR menu in Going Up? has 2 columns.) Items are indexed starting
	-- at 0, incrementing down the first column, then the second column,
	-- and so on.

	-- Seek ahead from wherever we are, until MENU_ADDR is executed and we
	-- know the menu variables are initialized. Then go back to the
	-- original savestate to start the seek forward.
	local selected, num_cols, num_rows
	do
		local menu_savestate = assert(run_until_exec(savestate, nil, MENU_ADDR))
		selected = savestate_read_u8(menu_savestate, MENU_SELECTED_ADDR)
		num_cols = savestate_read_u8(menu_savestate, MENU_MAX_COL_ADDR) + 1
		num_rows = savestate_read_u8(menu_savestate, MENU_MAX_ROW_ADDR) + 1
	end
	assert(0 <= selected and selected < num_cols * num_rows)
	assert(0 <= target and target < num_cols * num_rows)
	local selected_col = selected // num_rows
	local selected_row = selected % num_rows
	local target_col = target // num_rows
	local target_row = target % num_rows
	-- You can wrap both vertically and horizontally, either of which may
	-- be faster than not wrapping.
	local v_candidates = {
		{ button = "Down",  row_inc =  1, count = (target_row - selected_row) % num_rows },
		{ button = "Up",    row_inc = -1, count = (selected_row - target_row) % num_rows },
	}
	local h_candidates = {
		{ button = "Right", col_inc =  1, count = (target_col - selected_col) % num_cols },
		{ button = "Left",  col_inc = -1, count = (selected_col - target_col) % num_cols },
	}
	local v = min_by_key(v_candidates, function (x) return x.count end)
	local h = min_by_key(h_candidates, function (x) return x.count end)
	-- We can move the cursor once every 3 frames. We want to alternate
	-- vertical and horizontal movement as long as we have both horizontal
	-- and vertical distance to cover. After that we have to alternate with
	-- blank frames for the remaining direction. We want to start with
	-- whichever direction has more distance to cover. For example, DRDRD_D
	-- is faster than RDRD_D_D.
	local first, second
	if v.count > h.count then
		first = v
		second = h
	else
		first = h
		second = v
	end
	local function expected_update(expected, step)
		local col = expected // num_rows
		local row = expected % num_rows
		col = (col + (step.col_inc or 0)) % num_cols
		row = (row + (step.row_inc or 0)) % num_rows
		return row * num_cols + col
	end
	local function selected_becomes(expected)
		return function (savestate)
			return run_until_u8_becomes(savestate, 20, MENU_SELECTED_ADDR, expected)
		end
	end
	local expected = selected -- Keep track of the selection we expect.
	local plan = {}
	while first.count > 0 do
		first.count = first.count - 1
		expected = expected_update(expected, first)
		plan[#plan+1] = { button = first.button, test = selected_becomes(expected) }
		if second.count > 0 then
			second.count = second.count - 1
			expected = expected_update(expected, second)
			plan[#plan+1] = { button = second.button, test = selected_becomes(expected) }
		end
	end
	for _, step in ipairs(plan) do
		savestate = earliest_effective_frame(savestate, function (s)
			return savestate_joypad_set(s, {[step.button] = true})
		end, step.test)
	end
	if #plan == 0 then
		-- If #plan == 0, in other words if target == 0 (the default
		-- selection for any menu), we may actually still be in a
		-- *previous* menu that had already selected 0. Because no
		-- directional inputs are required, waiting just for the menu
		-- return MENU_RET_ADDR would find the return of the previous
		-- menu. So in this case, we wait until a few frames before
		-- *entering* MENU_ADDR, before waiting for the next
		-- MENU_RET_ADDR.
		savestate = earliest_effective_frame(savestate, nil, function (s)
			return run_until_exec(s, 2, MENU_ADDR)
		end)
	end
	local _, savestate = earliest_effective_frame(savestate, function (s)
		return savestate_joypad_set(s, {[menu_confirm_button(s)] = true})
	end, function (s)
		s = run_until_u8_is(s, 20, MENU_SELECTED_ADDR, expected)
		if s then
			s = run_until_exec(s, 20, MENU_RET_ADDR)
		end
		return s
	end)
	return savestate
end

local function cancel_menu(savestate)
	local _, savestate = earliest_effective_frame(savestate, function (s)
		return savestate_joypad_set(s, {B = true})
	end, function (s)
		local t = run_until_exec(s, 20, MENU_RET_ADDR)
		if t == nil then
			t = run_until_exec(s, 20, COURSE_MENU_RET_ADDR)
		end
		return t
	end)
	return savestate
end

local function arrays_concat(...)
	local result = {}
	for _, a in ipairs({...}) do
		table.move(a, 1, #a, #result + 1, result)
	end
	return result
end

local function select_num_floors(savestate, target)
	-- The SELECT COURSE menu in Heading Out? gives you two digits to
	-- manipulate. The variable NUM_FLOORS_ADDR tracks the state of the
	-- digits and is persistent, remaining set to the same value even
	-- across Heading Out? runs.
	local num_floors = savestate_read_u8(savestate, NUM_FLOORS_ADDR)
	local tens_digit = num_floors // 10
	local ones_digit = num_floors % 10
	local target_tens_digit = target // 10
	local target_ones_digit = target % 10
	-- For each digit, it may be faster to go up or down.
	local tens_candidates = {
		{ button = "Up",   inc =  10, count = (target_tens_digit - tens_digit) % 10 },
		{ button = "Down", inc = -10, count = (tens_digit - target_tens_digit) % 10 },
	}
	local ones_candidates = {
		{ button = "Up",   inc =   1, count = (target_ones_digit - ones_digit) % 10 },
		{ button = "Down", inc =  -1, count = (ones_digit - target_ones_digit) % 10 },
	}
	if target == 1 then
		-- In the special case of a target of 01, we also have the
		-- option of going to 00, which the game converts to 01.
		ones_candidates = arrays_concat(ones_candidates, {
			{ button = "Up",   inc =  1, count = (0 - ones_digit) % 10 },
			{ button = "Down", inc = -1, count = (ones_digit - 0) % 10 },
		})
	end
	local tens = min_by_key(tens_candidates, function (x) return x.count end)
	local ones = min_by_key(ones_candidates, function (x) return x.count end)
	local function num_floors_becomes(expected)
		return function (savestate)
			return run_until_u8_becomes(savestate, 20, NUM_FLOORS_ADDR, expected)
		end
	end
	local expected = num_floors -- Keep track of the state of num_floors we expect at each step.
	local plan = {}
	for _ = 1, tens.count do
		expected = (expected + tens.inc) // 10 * 10 + expected % 10
		plan[#plan+1] = { button = tens.button, test = num_floors_becomes(expected ) }
	end
	if ones.count > 0 then
		plan[#plan+1] = { button = "Right", test = function (s) return run_until_exec(s, 20, COURSE_MENU_DIGIT_ADDR) end }
		for _ = 1, ones.count do
			expected = expected // 10 * 10 + (expected % 10 + ones.inc) % 10
			plan[#plan+1] = { button = ones.button, test = num_floors_becomes(expected ) }
		end
	end
	for _, step in ipairs(plan) do
		savestate = earliest_effective_frame(savestate, function (s)
			return savestate_joypad_set(s, {[step.button] = true})
		end, step.test)
	end
	local _, savestate = earliest_effective_frame(savestate, function (s)
		return savestate_joypad_set(s, {[menu_confirm_button(s)] = true})
	end, function (s)
		s = run_until_u8_is(s, 20, NUM_FLOORS_ADDR, expected)
		if s then
			s = run_until_exec(s, 20, COURSE_MENU_RET_ADDR)
		end
		return s
	end)
	return savestate
end

-- Run until the byte at PLAYER1_POS_INDEX_ADDR is written to.
local function run_until_player1_moves(savestate, frame_limit)
	return run_until_write(savestate, frame_limit, PLAYER1_POS_INDEX_ADDR)
end

local function play_floor(savestate, schedule)
	savestate = run_until_exec(savestate, nil, SWITCH_PLAYERS_ADDR)
	local input_savestate = savestate
	local after_savestate = savestate
	local frame_count_start = nil
	local num_steps = 0
	for i = 1, string.len(schedule) do
		local code = string.sub(schedule, i, i)
		local button = ({
			N = "Up",
			E = "Right",
			S = "Down",
			W = "Left",
		})[code]
		assert(button, string.format("unknown code %q", code))

		input_savestate, after_savestate = earliest_effective_frame(after_savestate, function (s)
			return savestate_joypad_set(s, {[button] = true})
		end, function (s)
			return run_until_player1_moves(s, 16)
		end)

		if frame_count_start == nil then
			-- Start counting frames at the first button press
			-- (ignoring the SWITCH_PLAYERS_ADDR startup cost
			-- above).
			frame_count_start = savestate_frame_count(input_savestate)
		end
		num_steps = num_steps + 1
	end
	-- Run to the end of the frame to record the final input and get the
	-- expected frame count.
	input_savestate = run_one_frame(input_savestate)
	after_savestate = run_one_frame(after_savestate)
	-- Count frames based on after_savestate, which is when the player has
	-- fully moved. But return input_savestate, from the moment of input.
	-- This is needed to avoid empty frames at the end of the final floor.
	local num_frames = savestate_frame_count(after_savestate) - (frame_count_start or savestate_frame_count(after_savestate))
	return input_savestate, num_frames, num_steps
end

-- Starting at the "Way to go!" post-run screen, use the menus to set the
-- desired settings.
local function post_run_reconfigure(savestate, skill, num_floors, display)
	-- "Way to go!" screen.
	savestate = earliest_effective_frame(savestate, function (s)
		return savestate_joypad_set(s, {[menu_confirm_button(s)] = true})
	end, function (s)
		return run_until_exec(s, 32, MENU_ADDR)
	end)

	local cur_skill = savestate_read_u8(savestate, SKILL_ADDR)
	local cur_num_floors = savestate_read_u8(savestate, NUM_FLOORS_ADDR)
	-- The display variable uses the encoding 0 = DISPLAY_BIRDSEYE,
	-- 1 = DISPLAY_DIAGONAL which is the opposite of our convention (which
	-- uses the order in which the options appear in the menu).
	local cur_display = assert(({[0] = 1, [1] = 0})[savestate_read_u8(savestate, DISPLAY_ADDR)])

	-- There are two ways to navigate the menus: jumping back to a specific
	-- menu using the End menu, or stepping back through the menus one by
	-- one with the B Button. We try both, and keep whichever's faster.
	-- https://tasvideos.org/Forum/Posts/529194

	-- Try the End menu.
	local menu_savestate = savestate
	if cur_display ~= display or cur_num_floors ~= num_floors or cur_skill ~= skill then
		menu_savestate = select_menu_item(menu_savestate, 1) -- End
		if cur_skill ~= skill then
			menu_savestate = select_menu_item(menu_savestate, 2) -- Select skill
			menu_savestate = select_menu_item(menu_savestate, skill)
		else
			menu_savestate = select_menu_item(menu_savestate, 1) -- Select course
		end
		menu_savestate = select_num_floors(menu_savestate, num_floors)
		menu_savestate = select_menu_item(menu_savestate, display)
	end

	-- Try the B Button.
	local b_savestate = savestate
	if cur_display ~= display or cur_num_floors ~= num_floors or cur_skill ~= skill then
		b_savestate = cancel_menu(b_savestate)
		if cur_num_floors ~= num_floors or cur_skill ~= skill then
			b_savestate = cancel_menu(b_savestate)
			if cur_skill ~= skill then
				b_savestate = cancel_menu(b_savestate)
				b_savestate = select_menu_item(b_savestate, skill)
			end
			b_savestate = select_num_floors(b_savestate, num_floors)
		end
		b_savestate = select_menu_item(b_savestate, display)
	end

	-- Break ties in favor of the End menu, because that is usually faster.
	if savestate_frame_count(menu_savestate) <= savestate_frame_count(b_savestate) then
		savestate = menu_savestate
	else
		savestate = b_savestate
	end

	return savestate
end

assert(coroutine.resume(coroutine.create(function ()
	console.log("main start")
	local savestate = BASE_SAVESTATE

	-- Run until the title screen (skipping over the BIOS animation).
	savestate = run_until_exec(savestate, nil, TITLE_SCREEN_ADDR)
	-- Find the earliest frame at which we can press Start to get past the
	-- title screen.
	savestate = earliest_effective_frame(savestate, function (s)
		return savestate_joypad_set(s, {Start = true})
	end, function (s)
		return run_until_exec(s, 32, MENU_ADDR)
	end)

	savestate = select_menu_item(savestate, 1) -- Heading Out?

	local SEGMENTS = {
		-- E42x30-H34x22-H22x10-H58x8-A37x12-A02x15-A04x15-A17x9
		{ skill = SKILL_EASY,    display = DISPLAY_BIRDSEYE, seed = 42, floors = { "32i", "35i", "55i", "46", "59", "45i", "49i", "58", "56", "33", "30i", "57", "48i", "31i", "47i", "51i", "37", "41", "38", "44i", "50i", "34i", "42i", "53", "43", "54i", "40i", "36", "52i", "39i" } },
		{ skill = SKILL_HARD,    display = DISPLAY_BIRDSEYE, seed = 34, floors = { "138i", "110i", "139", "113i", "146", "148i", "111i", "144i", "143", "140", "131", "142i", "136i", "149i", "112i", "147", "121", "129i", "127i", "141", "135i", "115i" } },
		{ skill = SKILL_HARD,    display = DISPLAY_BIRDSEYE, seed = 22, floors = { "128i", "123i", "145i", "117", "114i", "125", "119", "120", "133", "137" } },
		{ skill = SKILL_HARD,    display = DISPLAY_BIRDSEYE, seed = 58, floors = { "132", "122i", "126", "124i", "134i", "130", "118", "116" } },
		{ skill = SKILL_AVERAGE, display = DISPLAY_BIRDSEYE, seed = 37, floors = { "109i", "100", "106", "104i", "98i", "65i", "63i", "93", "90", "73i", "95i", "96" } },
		{ skill = SKILL_AVERAGE, display = DISPLAY_BIRDSEYE, seed =  2, floors = { "89i", "108", "71", "94", "87i", "102", "86", "92i", "80", "103i", "60i", "91", "97", "64", "62i" } },
		{ skill = SKILL_AVERAGE, display = DISPLAY_BIRDSEYE, seed =  4, floors = { "68", "82i", "83i", "74i", "99", "70i", "76", "84i", "101", "77", "67", "90", "69", "107", "105i" } },
		{ skill = SKILL_AVERAGE, display = DISPLAY_BIRDSEYE, seed = 17, floors = { "78", "75", "61i", "79", "88", "72", "85", "81i", "66i" } },
	}

	for i, segment in ipairs(SEGMENTS) do
		-- Use the menus to set the skill, number of floors, and display.
		if i == 1 then
			savestate = select_menu_item(savestate, segment.skill)
			savestate = select_num_floors(savestate, #segment.floors)
			savestate = select_menu_item(savestate, segment.display)
		else
			-- Run until we're just about at the "Way to go!"
			-- screen. (Saves some trials in post_run_reconfigure.)
			savestate = earliest_effective_frame(savestate, function (s) return s end, function (s)
				return run_until_exec(s, 4, FLOOR_END_ADDR)
			end)
			savestate = post_run_reconfigure(savestate, segment.skill, #segment.floors, segment.display)
		end
		-- At the Start/End menu, wait for the desired RNG seed.
		savestate = earliest_effective_frame(savestate, function (s)
			return savestate_joypad_set(s, {[menu_confirm_button(s)] = true}) -- Start
		end, function (s)
			return run_until_u8_becomes(s, 10, RNG_SEED_ADDR, segment.seed)
		end)

		console.log(string.format("start segment %s%02d%s %5d",
			({[SKILL_EASY] = "E", [SKILL_AVERAGE] = "A", [SKILL_HARD] = "H"})[segment.skill],
			segment.seed,
			({[DISPLAY_DIAGONAL] = "D", [DISPLAY_BIRDSEYE] = "B"})[segment.display],
			savestate_frame_count(savestate)
		))

		-- Play the floors in the segment.
		for i, floor in ipairs(segment.floors) do
			savestate, num_frames, num_steps = play_floor(savestate, SOLUTIONS[floor].moves)
			-- Empirically determined overheads for the predicted
			-- number of frames to solve each floor.
			assert(num_steps  == SOLUTIONS[floor].steps,  string.format("steps %d != %d",  num_steps,  SOLUTIONS[floor].steps))
			assert(num_frames == SOLUTIONS[floor].frames, string.format("frames %d != %d", num_frames, SOLUTIONS[floor].frames))
		end
	end
	console.log(string.format("end %5d", savestate_frame_count(savestate)))

	movie.stop()
	console.log("main end")
end)))