I could sworn I had released one, but I can't seem to find it. The script is below; I have no recollection on how good or bad it is (it is that old). The major issue with animal manipulation in S2 is that you have very few opportunities to do so compared to S1: you can only manipulate them by adding or deleting boss explosions (which is annoying because you need to use something like TASMovieEditor or have to redo everything from the explosion to the capsule hit) or by deleting or adding additional animals.
Both animals and boss explosions can be deleted by pausing a game so that the frame when the byte at FFFFFE0F has the low 3 bits clear (in RAM watch, make it a hex value, the rightmost digit must be 0 or 8). So pause right before, unpause right after.
To add animals or explosions, you want to pause in such a way that these frames happen more often when the game is unpaused; for example, pausing right after them, and unpausing right before them.
Anyway, the script:
Language: lua
local object_size  = 0x40
local Object_RAM = 0xFFFFB000
local Dynamic_Object_RAM = Object_RAM + 16 * object_size
-- Object SST constants.
local render_flags        = 0x1
local x_pos               = 0x8
local y_pos               = 0xC
local x_vel               = 0x10
local y_vel               = 0x12
local mapping_frame       = 0x1A
local y_radius            = 0x16
local x_radius            = 0x17
local anim_frame_duration = 0x1E
local routine             = 0x24
local routine_secondary   = 0x25
local animal_ground_routine_base = 0x30
local animal_ground_x_vel = 0x32
local animal_ground_y_vel = 0x34
-- A few important RAM locations.
local RNG_seed       = 0xFFFFF636
local Current_Zone   = 0xFFFFFE10
local Vint_runcount  = 0xFFFFFE0C
local Collision_addr = 0xFFFFF796
local Level_Layout   = 0xFFFF8000
local ColCurveMap    = 0x00042D50
local ColArray       = 0x00042E50
local Camera_X_pos   = 0xFFFFEE00
local Camera_Y_pos   = 0xFFFFEE04
-- Function to find the main capsule object
local function find_capsule()
	for addr=Dynamic_Object_RAM,Object_RAM + 0x2000,object_size do
		if memory.readbyte(addr) == 0x3e and memory.readbyte(addr+routine) == 2 then
			return addr
		end
	end
	return nil
end
-- Function to enumerate all animals that matter for the capsule
local function enum_animals()
	local animals = {}
	for addr=Dynamic_Object_RAM,Object_RAM + 0x2000,object_size do
		if memory.readbyte(addr)==0x28 and memory.readbyte(addr+routine) > 0 then
			table.insert(animals, addr)
		end
	end
	return animals
end
local function speed2color(speed, min, max)
	local green = math.floor(((speed - min) * 255) / (max - min))
	local red   = math.floor(((max - speed) * 255) / (max - min))
	local blue, alpha = 0, 255
	return {red, green, blue, alpha}
end
local minspd, maxspd = 0x140, 0x300
-- Names for animals.
local animals = { [0]={"Rabbit"  , speed2color(0x200, minspd, maxspd), -0x200, -0x400},
                  [1]={"Chicken" , speed2color(0x200, minspd, maxspd), -0x200, -0x300},
                  [2]={"Penguin" , speed2color(0x180, minspd, maxspd), -0x180, -0x300},
                  [3]={"Seal"    , speed2color(0x140, minspd, maxspd), -0x140, -0x180},
                  [4]={"Pig"     , speed2color(0x1C0, minspd, maxspd), -0x1C0, -0x300},
                  [5]={"Flicky"  , speed2color(0x300, minspd, maxspd), -0x300, -0x400},
                  [6]={"Squirrel", speed2color(0x280, minspd, maxspd), -0x280, -0x380},
                  [7]={"Eagle"   , speed2color(0x280, minspd, maxspd), -0x280, -0x300},
                  [8]={"Mouse"   , speed2color(0x200, minspd, maxspd), -0x200, -0x380},
                  [9]={"Beaver"  , speed2color(0x2C0, minspd, maxspd), -0x2C0, -0x300},
                 [10]={"Turtle"  , speed2color(0x140, minspd, maxspd), -0x140, -0x200},
                 [11]={"Bear"    , speed2color(0x200, minspd, maxspd), -0x200, -0x300}}
-- Map between zone ID and animal names.
local typelist = { [0]={[0]= 6, [1]= 5},	-- EHZ
                   [1]={[0]= 6, [1]= 5},	-- Zone 1
                   [2]={[0]= 6, [1]= 5},	-- WZ
                   [3]={[0]= 6, [1]= 5},	-- Zone 3
                   [4]={[0]= 9, [1]= 7},	-- MTZ
                   [5]={[0]= 9, [1]= 7},	-- MTZ
                   [6]={[0]= 9, [1]= 7},	-- WFZ
                   [7]={[0]= 9, [1]= 7},	-- HTZ
                   [8]={[0]= 8, [1]= 3},	-- HPZ
                   [9]={[0]= 8, [1]= 3},	-- Zone 9
                  [10]={[0]= 2, [1]= 3},	-- OOZ
                  [11]={[0]= 8, [1]= 1},	-- MCZ
                  [12]={[0]=11, [1]= 5},	-- CNZ
                  [13]={[0]= 0, [1]= 7},	-- CPZ
                  [14]={[0]= 4, [1]= 1},	-- DEZ
                  [15]={[0]= 2, [1]= 5},	-- ARZ
                  [16]={[0]=10, [1]= 1}}	-- SCZ
-- Adddress of capsule switch in RAM.
local main_capsule,capsule_body,capsule_switch = nil
-- Internal variables.
local seed = 0
local last_seed = -1
local last_vbla = -1
-- Lua version of the Sonic 2 random number generator.
local function RandomNumber()
	local d1 = seed or 0x2A6D365A
	local d0 = d1																	-- move.l	d1,d0
	d1 = SHIFT(d1, -2)																-- asl.l	#2,d1
	d1 = d1 + d0																	-- add.l	d0,d1
	d1 = SHIFT(d1, -3)																-- asl.l	#3,d1
	d1 = d1 + d0																	-- add.l	d0,d1
	d0 = OR(AND(d1, 0x0000FFFF), AND(d0, 0xFFFF0000))								-- move.w	d1,d0
	d1 = OR(SHIFT(AND(d1, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16))		-- swap	d1
	d0 = OR(AND(d1 + d0, 0x0000FFFF), AND(d0, 0xFFFF0000))							-- add.w	d1,d0
	d1 = OR(SHIFT(AND(d0, 0x0000FFFF), -16), SHIFT(AND(d1, 0xFFFF0000), 16))		-- move.w	d0,d1 \n swap	d1
	seed = d1																		-- move.l	d1,(RNG_seed).w
	return d0
end
-- Lua version of the Sonic 2 function to find the nearest tile.
local function Floor_ChkTile(objRender, objX, objY)
	-- move.w	d2,d0	; y_pos
	-- add.w	d0,d0
	-- andi.w	#$F00,d0	; rounded 2*y_pos
	-- move.w	d3,d1	; x_pos
	-- lsr.w	#3,d1
	-- move.w	d1,d4
	-- lsr.w	#4,d1	; x_pos/128 = x_of_chunk
	-- andi.w	#$7F,d1
	-- add.w	d1,d0	; d0 is relevant chunk ID now
	local d4 = SHIFT(objX, 3)
	local index = AND(AND(SHIFT(objY, -1), 0xF00) + AND(SHIFT(d4, 4), 0x7F),0xFFFF)
	-- moveq	#-1,d1
	-- clr.w	d1
	-- lea	(Level_Layout).w,a1
	-- move.b	(a1,d0.w),d1	; move 128*128 chunk ID to d1
	local chunknum = memory.readbyte(Level_Layout + index)
	-- add.w	d1,d1
	-- move.w	word_1E5D0(pc,d1.w),d1
	-- move.w	d2,d0	; y_pos
	-- andi.w	#$70,d0
	-- add.w	d0,d1
	-- andi.w	#$E,d4	; x_pos/8
	-- add.w	d4,d1
	local chunkaddr = 0xFFFF0000 + AND(memory.readword(0x1E5D0 + 2*chunknum) + AND(objY,0x70) + AND(d4, 0xE), 0xFFFF)
	-- movea.l	d1,a1	; address of block ID
	-- rts
	return chunkaddr
end
-- Lua version of the Sonic 2 secondary function to find the floor.
local function FindFloor2(objRender, objX, objY, solidbit, delta, initangle, flipmask)
	local angle = initangle
	local tileaddr = Floor_ChkTile(objRender, objX, objY)		-- bsr.s	Floor_ChkTile
	local floordist = 0
	local tile = memory.readword(tileaddr)		-- move.w	(a1),d0	\n	move.w	d0,d4
		-- andi.w	#$3FF,d0	\n	beq.s	loc_1E88A	\n	btst	d5,d4	\n bne.s	loc_1E898
	local tileid = AND(tile, 0x3FF)
	if tileid ~= 0 and AND(tile, solidbit) ~= 0 then
		-- loc_1E898:
		local colptr = memory.readlong(Collision_addr)		-- movea.l	(Collision_addr).w,a2
		local block = memory.readbyte(colptr + tileid)		-- move.b	(a2,d0.w),d0
		if block == 0 then		-- beq.s	loc_1E88A
			-- loc_1E88A
			return 0xF - AND(objY, 0xF), tileaddr, angle
		end
		
		angle = memory.readbytesigned(ColCurveMap + block)		-- lea	(ColCurveMap).l,a2	\n	move.b	(a2,d0.w),(a4)
		block = SHIFT(block, -4)		-- lsl.w	#4,d0
		local xcopy = objX		-- move.w	d3,d1
		if AND(tile, BIT(0xA)) ~= 0 then		-- btst	#$A,d4	\n	beq.s	+
			xcopy = -objX-1		-- not.w	d1
			angle = -angle		-- neg.b	(a4)
		end
		-- +
		if AND(tile, BIT(0xB)) ~= 0 then		--- btst	#$B,d4	\n	beq.s	+
			angle = -0x80 - angle		-- addi.b	#$40,(a4)	\n	neg.b	(a4)	\n	subi.b	#$40,(a4)
		end
		-- +
		xcopy = AND(objX, 0xF) + block		-- andi.w	#$F,d1	\n	add.w	d0,d1
		local colhgt = memory.readbytesigned(ColArray + xcopy)		-- lea	(ColArray).l,a2	\n	move.b	(a2,d1.w),d0	\n	ext.w	d0
		tile = XOR(tile, flipmask)		-- eor.w	d6,d4
		
		if AND(tile, BIT(0xB)) ~= 0 then		-- btst	#$B,d4	\n	beq.s	+
			colhgt = -colhgt		-- neg.w	d0
		end
		-- +
		if colhgt == 0 then		-- tst.w	d0	\n	beq.s	loc_1E88A
			-- loc_1E88A
			return 0xF - AND(objY, 0xF), tileaddr, angle
		elseif colhgt < 0 then		-- bmi.s	loc_1E900
			-- loc_1E900:
			local dist = AND(objY, 0xF)		-- move.w	d2,d1	\n	andi.w	#$F,d1
			if dist + colhgt < 0 then		-- add.w	d1,d0	\n	bpl.w	loc_1E88A
				return -dist-1, tileaddr, angle		-- not.w	d1	\n	rts
			end
		else
			local dist = AND(objY, 0xF)		-- move.w	d2,d1	\n	andi.w	#$F,d1
			return 0xF - (dist + colhgt), tileaddr, angle		-- add.w	d1,d0	\n	move.w	#$F,d1	\n	sub.w	d0,d1	\n	rts
		end
	end
	-- loc_1E88A:
	--	move.w	#$F,d1
	--	move.w	d2,d0
	--	andi.w	#$F,d0
	--	sub.w	d0,d1
	--	rts
	return 0xF - AND(objY, 0xF), tileaddr, angle
end
-- Lua version of the Sonic 2 function to find the floor.
local function FindFloor(objRender, objX, objY, solidbit, delta, initangle, flipmask)
	local angle = initangle
	local tileaddr = Floor_ChkTile(objRender, objX, objY)		-- bsr.s	Floor_ChkTile
	local floordist = 0
	local tile = memory.readword(tileaddr)		-- move.w	(a1),d0	\n	move.w	d0,d4
		-- andi.w	#$3FF,d0	\n	beq.s	loc_1E7E2	\n	btst	d5,d4	\n bne.s	loc_1E7F0
	local tileid = AND(tile, 0x3FF)
	if tileid ~= 0 and AND(tile, solidbit) ~= 0 then
		-- loc_1E7F0:
		local colptr = memory.readlong(Collision_addr)		-- movea.l	(Collision_addr).w,a2
		local block = memory.readbyte(colptr + tileid)		-- move.b	(a2,d0.w),d0	\n	andi.w	#$FF,d0
		if block == 0 then		-- beq.s	loc_1E7E2
			-- loc_1E7E2
			floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
			return floordist + 0x10, tileaddr, angle
		end
		
		angle = memory.readbytesigned(ColCurveMap + block)		-- lea	(ColCurveMap).l,a2	\n	move.b	(a2,d0.w),(a4)
		block = SHIFT(block, -4)		-- lsl.w	#4,d0
		local xcopy = objX		-- move.w	d3,d1
		if AND(tile, BIT(0xA)) ~= 0 then		-- btst	#$A,d4	\n	beq.s	+
			xcopy = -objX-1		-- not.w	d1
			angle = -angle		-- neg.b	(a4)
		end
		-- +
		if AND(tile, BIT(0xB)) ~= 0 then		--- btst	#$B,d4	\n	beq.s	+
			angle = -0x80 - angle		-- addi.b	#$40,(a4)	\n	neg.b	(a4)	\n	subi.b	#$40,(a4)
		end
		-- +
		xcopy = AND(objX, 0xF) + block		-- andi.w	#$F,d1	\n	add.w	d0,d1
		local colhgt = memory.readbytesigned(ColArray + xcopy)		-- lea	(ColArray).l,a2	\n	move.b	(a2,d1.w),d0	\n	ext.w	d0
		tile = XOR(tile, flipmask)		-- eor.w	d6,d4
		
		if AND(tile, BIT(0xB)) ~= 0 then		-- btst	#$B,d4	\n	beq.s	+
			colhgt = -colhgt		-- neg.w	d0
		end
		-- +
		if colhgt == 0 then		-- tst.w	d0	\n	beq.s	loc_1E7E2
			-- loc_1E7E2
			floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
			return floordist + 0x10, tileaddr, angle
		elseif colhgt < 0 then		-- bmi.s	loc_1E85E
			-- loc_1E85E:
			local dist = AND(objY, 0xF)		-- move.w	d2,d1	\n	andi.w	#$F,d1
			if dist + colhgt < 0 then		-- add.w	d1,d0	\n	bpl.w	loc_1E7E2
				-- loc_1E86A:
				floordist, tileaddr, angle = FindFloor2(objRender, objX, objY - delta, solidbit, delta, initangle, flipmask)
				return floordist - 0x10, tileaddr, angle
			end
		elseif colhgt == 0x10 then		-- cmpi.b	#$10,d0	\n	beq.s	loc_1E86A
			-- loc_1E86A:
			--	sub.w	a3,d2
			--	bsr.w	FindFloor2
			--	add.w	a3,d2
			--	subi.w	#$10,d1
			--	rts	
			floordist, tileaddr, angle = FindFloor2(objRender, objX, objY - delta, solidbit, delta, initangle, flipmask)
			return floordist - 0x10, tileaddr, angle
		else
			--	move.w	d2,d1
			--	andi.w	#$F,d1
			--	add.w	d1,d0
			--	move.w	#$F,d1
			--	sub.w	d0,d1
			--	rts	
			return 0xF - (AND(objY, 0xF) + colhgt), tileaddr, angle
		end
	end
	-- loc_1E7E2:
	--	add.w	a3,d2
	--	bsr.w	FindFloor2	; try tile below the nearest
	--	sub.w	a3,d2
	--	addi.w	#$10,d1		; return distance to floor
	--	rts	
	floordist, tileaddr, angle = FindFloor2(objRender, objX, objY + delta, solidbit, delta, initangle, flipmask)
	return floordist + 0x10, tileaddr, angle
end
-- Lua version of the Sonic 2 function to find distance to the floor.
local function ObjCheckFloorDist(objX, objY, objH, objR)
	--local objX = memory.readword(obj+x_pos)		-- move.w	x_pos(a0),d3
	--local objY = memory.readword(obj+y_pos)		-- move.w	y_pos(a0),d2
	--local objH = memory.readbytesigned(obj+y_radius)		-- moveq	#0,d0	\n	move.b	y_radius(a0),d0 \n	ext.w	d0
	--local objR = memory.readbyte(obj+render_flags)
	--objY = objY + objH		-- add.w	d0,d2
	-- lea	(v_anglebuffer).w,a4
	-- move.b	#0,(a4)
	-- movea.w	#$10,a3
	-- move.w	#0,d6
	-- moveq	#$D,d5
	local floordist, tileaddr, angle = FindFloor(objR, objX, objY + objH, BIT(0xC), 0x10, 0, 0)		-- bsr.w	FindFloor
	if AND(angle, 1) ~= 0 then
		angle = 0		-- move.b	(v_anglebuffer).w,d3	\n	btst	#0,d3 \n beq.s	locret_14E4E \n	move.b	#0,d3
	end
	return floordist, tileaddr, angle		-- locret_14E4E:	rts	
end
-- Lua version of the Sonic 2 function to make an object "fall".
local function ObjectMoveAndFall(objX, objY, objVX, objVY)
	--local memory.readlong(obj+x_pos)		-- move.l	x_pos(a0),d2
	--local memory.readlong(obj+y_pos)		-- move.l	y_pos(a0),d3
	--local memory.readwordsigned(obj+x_vel)		-- move.w	x_vel(a0),d0	\n	ext.l	d0
	objX = AND(objX + SHIFT(objVX, -8), 0xFFFFFFFF)		-- asl.l	#8,d0	\n	add.l	d0,d2
	--local memory.readwordsigned(obj+y_vel)		-- move.w	y_vel(a0),d0	\n	ext.l	d0
	objY = AND(objY + SHIFT(objVY, -8), 0xFFFFFFFF)		-- asl.l	#8,d0	\n	add.l	d0,d3
	objVY = objVY + 0x38		-- addi.w	#$38,y_vel(a0)
	return objX, objY, objVX, objVY		-- move.l	d2,x_pos(a0)	\n	move.l	d3,y_pos(a0)	\n	rts
end
-- Lua version of the Sonic 2 function loc_11ADE.
local function animal_fall(objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla)
	--	tst.b	render_flags(a0)
	--	bpl.w	DeleteObject
	objX, objY, objVX, objVY = ObjectMoveAndFall(objX, objY, objVX, objVY)		-- bsr.w	ObjectMoveAndFall
	if objVY >= 0 then		-- tst.w	y_vel(a0)	\n	bmi.s	+
		local floordist = ObjCheckFloorDist(SHIFT(objX, 16), SHIFT(objY, 16), objH, objR)		-- jsr	(ObjCheckFloorDist).l
		
		if floordist < 0 then		-- tst.w	d1	\n	bpl.s	+
			objY = objY + floordist		-- add.w	d1,y_pos(a0)
			objVX = objNVX		-- move.w	animal_ground_x_vel(a0),x_vel(a0)
			objVY = objNVY		-- move.w	animal_ground_y_vel(a0),y_vel(a0)
			--	move.b	#1,mapping_frame(a0)
			--	move.b	animal_ground_routine_base(a0),d0
			--	add.b	d0,d0
			--	addq.b	#4,d0
			--	move.b	d0,routine(a0)
			--	tst.b	objoff_38(a0)
			--	beq.s	+
			if AND(vbla, BIT(4)) ~= 0 then		-- btst	#4,(Vint_runcount+3).w	\n	beq.s	+
				objVX = -objVX		-- neg.w	x_vel(a0)
				objR = XOR(objR, BIT(0))		-- bchg	#0,render_flags(a0)
			end
		end
	end
	-- +
	--	bra.w	DisplaySprite
	return objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla
end
local function simulate_animal_delay_fall_escape(obj, objX, objY, objVX, objVY, objNVX, objNVY, objWait, vbla, capX)
	local objH = 0xC
	local objR = 0x85
	local objW = 8
	local initvbla = vbla
	-- If we have a real object:
	if obj ~= nil then
		objX    = memory.readlong(obj+x_pos)
		objY    = memory.readlong(obj+y_pos)
		objVX   = memory.readwordsigned(obj+x_vel)
		objVY   = memory.readwordsigned(obj+y_vel)
		objNVX  = memory.readwordsigned(obj+animal_ground_x_vel)
		objNVY  = memory.readwordsigned(obj+animal_ground_y_vel)
		objWait = memory.readword(obj+0x36) - 1
	end
	
	-- Compute time to get offscreen
	local le = SHIFT(Camera_X_pos       - objW, -8)
	local re = SHIFT(Camera_X_pos + 320 + objW, -8)
	
	if objX <= le or objX >= re then
		return 0, vbla
	end
	-- Simulate wait if needed
	if objWait > 0 then
		vbla = vbla + objWait + 1
	end
	
	-- Simulate falling
	while objVX == 0 do
		vbla = vbla + 1
		objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla =
		      animal_fall(objX, objY, objVX, objVY, objH, objR, objNVX, objNVY, vbla)
	end
	
	-- Lets use relative position here.
	objX = SHIFT(objX, 8)
	local lastvbla = vbla
	if objVX > 0 then
		vbla = vbla + math.abs(math.floor((re - objX + objVX - 1) / objVX)) + 1
	else
		vbla = vbla + math.abs(math.floor((objX - le - objVX - 1) / -objVX)) + 1
	end
	
	return objVX, vbla
end
function print_animal_objects(vbla)
	local animal_list = enum_animals()
	if #animal_list > 0 then
		row = 4
		gui.drawtext(4, row, "Spawned animals:")
		row = row + 8
		gui.drawtext(12, row, "Animal   Wait D Exit at")
		row = row + 8
		local capX = memory.readword(capsule_body+x_pos)
		local list1 = {}
		local list2 = {}
		for i,m in pairs(animal_list) do
			local ty    = memory.readbyte(m+0x30)
			local delay = memory.readword(m+0x36)
			local vx, endvbla = simulate_animal_delay_fall_escape(m, nil, nil, nil, nil, nil,
			                                                      nil, nil, vbla, capX)
			local dir = (vx > 0 and ">") or "<"
			local endpt = movie.framecount() + endvbla - vbla + 1
			if m < capsule_switch then
				endpt = endpt - 1
			end
			table.insert((delay == 0 and list2) or list1, {endpt, function(rw)
					gui.drawtext(12, rw,
							     string.format("%-8s %4d %s %7d", animals[ty][1], delay, dir,
							                   endpt), animals[ty][2])
				end})
		end
		
		table.sort(list1, function(item1, item2)
				return item1[1] < item2[1]
			end)
		table.sort(list2, function(item1, item2)
				return item1[1] < item2[1]
			end)
		
		for i,m in pairs(list1) do
			m[2](row)
			row = row + 8
		end
		
		gui.drawtext(12, row, "========= Free =========")
		row = row + 8
		
		for i,m in pairs(list2) do
			m[2](row)
			row = row + 8
		end
	end
end
function predict_animals()
	-- Try to find a capsule
	main_capsule   = find_capsule()
	if main_capsule == nil then
		-- If it was not found, so update variables and leave
		last_seed = memory.readlong(RNG_seed)
		last_vbla = memory.readbyte(Vint_runcount+3)
		return
	end
	capsule_body   = 0xFFFF0000 + memory.readword(main_capsule+0x3C)
	capsule_switch = 0xFFFF0000 + memory.readword(main_capsule+0x38)
	
	-- Update random seed to RAM value
	seed = memory.readlong(RNG_seed)
	
	-- Capsule switch's routine counter
	local rout = memory.readbyte(main_capsule+routine_secondary)
	
	-- V-Int counter, used for pseudo-random numbers
	local vbla  = memory.readbyte(Vint_runcount+3)
	
	-- Sometimes, the V-Int counter does not update; doing this "manual"
	-- update corrects the issue, which causes misprediction.
	if last_vbla == vbla then
		vbla = vbla + 1
	end
	local savevbla = vbla
	
	-- Special case:
	if rout == 0 and memory.readword(capsule_switch+0x32) ~= 1 then
		-- Switch not pressed
		gui.drawtext(4, 4, "Capsule not broken yet.")
		last_seed = seed
		last_vbla = savevbla
		return
	end
	local row = 0
	-- Draw container box.	
	gui.drawbox(0, 0, 223, 28*8-1, {  0,   0,   0, 128}, {255, 255, 255, 255})
	if memory.readbyte(capsule_body+routine) == 0xA then
		-- All animals spawned and moving to leave screen
		gui.drawtext(128, 12, "Waiting for animals\nto leave screen.", {255, 255,   0, 255})
		print_animal_objects(vbla)
		last_seed = seed
		last_vbla = savevbla
		return
	end
	
	-- Get animal list for current zone.
	local zone  = memory.readbyte(Current_Zone)
	local types = typelist[zone]
	local le = Camera_X_pos       - 8
	local re = Camera_X_pos + 320 + 8
	
	if rout == 0 then
		-- Waiting for initial animals to be created.
		row = 20
		gui.drawtext(4, row, "Initial animals:")
		row = row + 8
		gui.drawtext(12, row, "Animal   Wait D Exit at")
		row = row + 8
		
		local capX = memory.readword(main_capsule+x_pos)
		local capY = memory.readword(main_capsule+y_pos)
		row = row + 56
		-- Initial animals
		for i = 1, 8 do
			-- Random number determines kind of animal.
			local rand = RandomNumber()
			local ty = types[AND(rand, 1)]
			local objX = SHIFT(capX - 0x1C + 7 * i, -16)
			local objY = SHIFT(capY, -16)
			local delay = 0x9A + 8 - 8 * i
			if le <= objX and objX <= re then
				local vx, endvbla = simulate_animal_delay_fall_escape(nil, objX, objY, 0, -0x400,
					                                                  animals[ty][3], animals[ty][4],
					                                                  delay, vbla, capX)
				local dir = (vx > 0 and ">") or "<"
				gui.drawtext(12, row,
					         string.format("%-8s %4d %s %7d", animals[ty][1], delay, dir,
					                       movie.framecount() + endvbla - vbla),
					         animals[ty][2])
			end
			row = row - 8
		end
	end
	
	print_animal_objects(vbla)
	if rout >= 2 then
		-- Single animals are being generated.
		-- Pseudo-random animal generation
		row = 4
		local saverow = row
		row = row + 8
		gui.drawtext(124, row, "Animal  Spawn D Exit at")
		row = row + 8
		local animals_left = 0
		local dt = 7 - AND(vbla, 7)
		local timer = (memory.readbyte(capsule_body+routine_secondary) == 2 and memory.readword(capsule_body+anim_frame_duration)) or 0xB4
		
		local capX = memory.readword(capsule_body+x_pos)
		local capY = memory.readword(capsule_body+y_pos)
		for i = 1, timer + 1 do
			if AND(vbla + i - 1, 7) == 0 then
				-- First random number is used to determine the position
				-- offset from the switch.
				local rand = AND(RandomNumber(), 0x1F) - 6
				local pos = (seed >= 0 and rand) or -rand
				-- Second random number determines animal type.
				rand = RandomNumber()
				local ty = types[AND(rand, 1)]
				if i - 2 > 0 then
					local objX = SHIFT(capX + pos, -16)
					local objY = SHIFT(capY, -16)
					local delay = i + 12
					local vx, endvbla = simulate_animal_delay_fall_escape(nil, objX, objY, 0, -0x400,
							                                              animals[ty][3], animals[ty][4],
							                                              delay, vbla, capX)
					local dir = (vx > 0 and ">") or "<"
					if le <= objX and objX <= re then
						gui.drawtext(124, row,
									 string.format("%-8s %4d %s %7d", animals[ty][1], delay-14, dir,
									               movie.framecount() + endvbla - vbla + (rout < 0xC and 1 or 0)),
									 animals[ty][2])
						row = row + 8
					end
					animals_left = animals_left + 1
				end
			end
		end
		-- If we have animals still to be generated, and we are not in the
		-- process of making explosions, we have an opportunity to change
		-- if the next animal will be loaded or not.
		if animals_left > 0 then
			gui.drawtext(112, 180,
			             string.format("Next oportunity to prevent\nanimal spawn: %d frame%s",
			                           dt, (dt == 1 and "") or "s"), {255, 255,   0, 255})
			gui.drawtext(116, saverow, string.format("Animal spawns: %d left", animals_left))
		end
	end
	
	last_seed = memory.readlong(RNG_seed)
	last_vbla = savevbla
	return
end
gens.registerafter(predict_animals)
savestate.registerload(predict_animals)