Submission #8079: davidtki's DOS Hero's Quest "maximum score" in 10:38.73

(Link to video)
DOS
maximum score
JPC-rr 11.9 alpha
38324
60
14855
PowerOn
SIERRA.EXE
Submitted by davidtki on 3/4/2023 4:25:47 PM
Submission Comments
Being dissatisfied with how little of Spielburg he actually saw last time he was here, our hero (who has changed name again to RED) returns back to Spielburg to actually experience all the sights. But little does he realize that he will soon run into his toughest foe: a 5-minute cutscene.

Game objectives

  • To become a hero!
  • Aims for fastest time
  • Gets the highest possible puzzle point total of 517
  • Emulator used: JPC-rr 11.9-alpha (I was able to play this back on JPC-rr r11.8 rc2)
  • Game Version 1.000

Comments

Class and Version choice

In version 1.0, the game does not check your class when giving points for playing and winning at Mage’s Maze. Fighters and Thieves can beat the game with 517 points by adding points to magic. Fighter has 4 points locked behind Saurus Rex, and Saurus Rex will not appear prior to 1000 experience points. Therefore Thief has been picked for this run.

RNG

The RNG seed is initialized the first time the game calls RNG. This happens immediately on game startup to initialize the thieves guild password. It calls an interrupt to get the system clock and saves register DX into the RNG seed before using (DH = seconds, DL = hundredths of seconds). There are 5999 possible starting seeds (0 is discarded and reinitialized). When SCI rolls RNG, you can calculate the result as follows:
Given the method (random min max):
  • a = 31821 * RNG seed
  • b = floor(a/256) % 65536
  • c = b * (max - min + 1)
  • d = floor(c/65536)
  • Result = d + min
The RNG seed is then updated with seed = seed * 31821 % 65536. This RNG seed update is imperfect and has a tendency to lead to variable lengths of loops of RNG, with some starting seeds having a loop length of 1024 or 512, and even one possible loop that is 2 seeds long. QFG1 has the fun ability to manipulate RNG by right-clicking on nothing - this calls (random 0 4) and if the result is 0 will display a random text box.
The first RNG roll of the game is (Random 0 6) for the thieves guild password. The ideal result is 3 to get the shortest password of Antwerp.

Skill checks

The bulk of the RNG that needs to be manipulated is around skill checks. Skill checks are either random difficulty or fixed difficulty. Random difficulty checks will first roll (Random 1 6) to determine stamina drain, then (Random 0 999) / 10 + 1 to determine difficulty. Following this point, all skill checks will roll (Random 1 200) for luck, and a result that is less than or equal to the hero’s Luck stat will then get a (Random 1 20) bonus to the skill check.
Some amount of experience will be given to each stat on skill checks. These are stored in a global experience accumulator variable. When the amount of experience for a skill is greater than or equal to your skill level, that amount of experience will be consumed and the skill will increase a random amount. (Random 1 3) on the skill level-up roll.
The route chosen has a DC 40 strength check and several DC 35 climb checks. Erasmus will not challenge you to mage’s maze unless you have 20+ magic. Therefore the starting skill allotment is to increase Magic to 20, Strength to 20, and Climbing to 15.
Intelligence gets experience for each puzzle point. Vitality gets experience for stamina or health drain.

Starting RNG seed

Several of the starting RNG seeds I looked at had a 1024-length loop, with the loop having three +20 luck bonuses spread throughout it. Of these I picked 2864 as the starting RNG seed:
  • Rolls 3 immediately on game startup for Antwerp password.
  • Has a +15 luck bonus roll 44 seeds in, ideal for climbing the flying falls.
  • Has an ideal Mage’s Maze layout after another 217 seeds.

Route changes from version 1

  • Zara is not visited at all on day 1. This means going through the game without the flame dart or fetch spells.
  • Town is left through the main gates rather than the alley wall to set the flag for Bruno to appear.
  • The trigger spell is used to open the kobold’s chest rather than a failed pick locks attempt (this allows me to leave the flying falls with a flying fall).
  • Without the fetch spell, rock throws are used to solve puzzles instead.
  • The meeps and dryad are visited on day 1.
  • The forest is wandered on night 2 to get the fairy dust, cheetaur claws and troll beard.
  • The healer is not visited until day 3. All the collectibles can be turned in in one visit and the undead unguent obtained then.
  • After activities are finished in town on night 3, climb over the town wall and head straight to the brigand fortress.
  • Mandrake root is obtained on the way to Baba Yaga’s. All 3 Baba Yaga visits are grouped at the end and the game ends at midnight of day 3.

Full-Second Frame Rules

Several scripts have multiple states. Some scripts will use a term (= seconds x) to tell the engine to advance to the next state after x seconds have passed. The assembly code that deals with this uses the system clock to determine when time has passed but will ignore the value in DL (hundredths of seconds) - this means any script that has this (= seconds x) term is full-second frame ruled.

Parser dumping

SCI is slow to type the commands needed - 10 characters per second. In version 1 of the TAS I discovered that SCI will continue to poll the keyboard while loading screens, so typing time can be saved by dumping some amount of input into the parser during screen transitions. Over multiple rooms it’s possible to build up the parser slowly by pressing F3 between screens and typing more.

Room-by-room specifics

Character Sheet

Because of the full-second frame rule at the Fox, there is enough time on the character sheet to give the hero a 3-letter name with no loss of time. I have chosen to name him RED for fun at Erasmus.

Town (Day 1)

The adventurer’s guild and alleyway have to be visited during the day at least once. We also need two flasks for our adventures. Doing guild on day 1 allows the “ask about name” input to be reused for all 3 of Wolfgang, Schultz and Hilde. RNG was manually advanced to the point of leaving the town gate on seed 2032.
Leaving through the town gate a second time sets the flag for Bruno to appear later.

Flying Falls

The flying falls were entered on seed 16112. This gives enough time to get the flying water before the attempt to climb the flying falls. Seed 54320 rolls 6 on the luck check and 20 on the luck bonus, to give a climb check of 35 against a difficulty of 30. Falling off the flying falls deals 10 damage, reducing health to 16. No longer enough to survive the exploding chest in the Kobold cave.

Mount Zauberberg

The fox and gargoyle are full-second frame ruled, allowing plenty of time to advance RNG. I was unable to manipulate out a goblin chasing me along the way and room 26 required navigating high on the screen to avoid locking up in room 27 from the goblin chase.
The gargoyle’s questions were manipulated to ask “favorite color” and “thieves guild password”. Any wrong answer will work for the password, and v1.0 allows you to use “red” as a favorite color. With this I used the same answer for all 3 questions.

Mage’s Maze

Mage’s Maze has random starting positions for the boulders, bridges and ladders. Without the fetch and open spells, these must spawn in positions where they don’t need to be moved. Spawning goes thus:
There are 4 boulders, 5 bridges and 2 ladders, and they are spawned in that order. There are 8 possible boulder locations, 14 possible bridge locations, and 5 possible ladder locations. Roll (Random 0, X) for each object. If there is already an object in the rolled location, roll again. (And yes, with the 2-seed RNG loop this will get trapped in an infinite loop)
Based on this I identified two possible routes through the maze:
Route 1:
  • No boulders at 0 or 7
  • Bridges at 3, 4 & 13
  • Ladder at 2
Route 2:
  • No boulders at 0, 4 & 6
  • Bridges at 3, 4, 12 & 13
  • Ladder at 1
44144 is the seed I was aiming for. It spawned this maze:
  • Boulders 5, 3, 2, 1
  • Bridges 3, 8, 4, 13, 12
  • Ladders 1, 4
This generated maze requires only 3 trigger casts, using 9 mana and leaving 9 mana left.Some well-timed right clicks directed the creature to randomly go in the direction I needed it to and beat the maze in record time. Note that Mage’s Maze rolls RNG *rapidly* and that it is difficult to predict where RNG will end up afterward. In order to be able to properly manipulate the rock throw at the spirea seed later, I slowed down the maze by one second on purpose with an additional trigger spell (Erasmus’ speech after is full-second frame ruled), finishing the maze on seed 21168. The maze used 12 mana in all, leaving 6 mana left for a dazzle and a trigger cast.

Kobold Cave

Rocks are picked up on the way to the cave. V1.0 has a bug with stealth where if you enter the kobold’s room sneaking already and use mouse clicks exclusively to move, you can grab the key without waking it up. Unfortunately I was not able to get into the chest without waking it up, and if the kobold wakes up then there are only 3 possible outcomes:
  • It hits you immediately with its first blast, killing you.
  • It misses you with the blast, but then due to a bug your parser immediately locks up and you can’t type anything until the kobold hits you again, killing you.
  • Dazzle the kobold first so it can’t blast.
So dazzle the kobold after grabbing the key then use the trigger spell to open the chest. This leaves mana at 0, but luckily no more spell casts will be needed this day.

Spitting Seed

The plant will spit after (Random 3 5) seconds. I’ve manipulated a 3 second spit. After this wait it will roll (Random 0 3) to determine which plant to spit to, with 1 being the ideal result.
There are 4 ways to get the seed:
  • Open spell (Flat requiring 30 skil, can’t luck bonus thisl)
  • Fetch spell (Random difficulty with a +25 bonus to the roll)
  • Climb (Difficulty 35)
  • Throwing (Random difficulty with a -10 penalty to the roll)
The open spell will not have the skill needed to succeed without grinding, leaving that out as an option. Fetching the seed has an (= seconds 3) wait after success. Climbing has an (= seconds 8) wait after success. Throwing has no wait but a starting skill of 5 and a penalty of 10 work against it.
I initially thought throwing wasn’t possible, but then I found two RNG seeds in the loop that make it possible: 65008, and 41456. 65008 was chosen due to proximity. This seed will:
  • Roll (Random 0 3) and pick plant 1.
  • Roll some number for stamina drain that does not matter.
  • Roll (Random 0 999) for difficulty and get 10, leaving a final difficulty of 2 for the check.
  • Roll (Random 1 200) for luck and get 5, allowing a luck bonus.
  • Roll (Random 1 20) for the bonus and get 7.
  • Throwing 5 + Bonus 7 - Penalty 10 = 2, matching the difficulty and succeeding.
  • As an additional bonus, Throwing levels up to 8 (+3 on the roll).

Meeps and Dryad

It’s possible to manipulate the number of throws (and whether or not you get a scroll) at the meeps, as well as the position of certain throws. The meeps are full-second frame ruled and this likely doesn’t matter, but the throws were manipulated anyways to have only 3 items out with the fur far to the right.
The stag rolls RNG several times but the only roll that matters is whether the stag is facing left or right. There’s an additional RNG roll for how long it goes through its animation before walking off that is relevant to any%, but isn’t relevant to 100% since 100% pathing puts us close enough to immediately make the stag bolt.

Monster encounter on the way to Mirror Lake

It’s time to talk about monster encounter code and RNG.
Each room in the forest has a value encChance assigned to it. This is modified based on certain circumstances. At night encChance is doubled, and if you have dispelled the curse on Elsa then encChance is doubled. If both conditions are true, chances are quadrupled. The same RNG roll that is used for random difficulty (0-999 and modified to be in the 1-100 range) is used to determine if you get a monster. Result less than or equal to modified encChance means encounter. At this point it then rolls to determine which monster. It will also at some point roll (Random 0 1000) to determine if the monster comes from the west or east.
There are two global variables 117 and 118. When you change screens while a monster is chasing you, these variables are updated with the difference between the hero’s position and the monster’s position. Room 81 just north of Mirror Lake has an EncChance of 20. RNG was advanced to 19056 to forcibly spawn a monster from the west on this screen. Entering Mirror Lake while this monster was spawned set Global117 to 195 and global118 to 49. Global117 being greater than 180 sets the conditions for a bug that will be used later.

Archery Range, Bruno and Castle

The archery range conversation can only happen at midday. Day 1 starting at midday makes it ideal for overhearing this conversation. Bruno and Swordy Lordy will also appear at midday. Sleeping at the castle barracks passes time instantly to night.

Night 2 fairies

The fairies have an agility 25 check for determining if you get the good dance. This doesn’t matter to a thief except that the luck roll gives 1 experience, leveling it up. A +3 levelup was manipulated (luck 13). Asking the fairies a question that bores them after getting the dust will stop them from following you and saying goodbye, saving a few seconds. The screen rolls RNG rapidly, but was slightly manipulated so that we would leave the screen on RNG seed 42928.

Troll

With less than 1000 experience, (Random 0 3) will be rolled on monster encounter to determine which monster appears. +2 is added to this result at night. In most forest screens a 6 or higher is required to see a troll, making spawns impossible. Rooms 85, 86 and 92 (east of the antwerp cave) give you a troll with a 5 or higher, brigand otherwise. Room 85 has an encChance of 30 (60 at night) and is the least out of the way for a random encounter. Seed 45040 is manipulated here to roll 426 on encounter chance and 3 on selected monster.
Since we haven’t seen any monsters since Mirror Lake on day 1, global117 is still 195.
The newRoom code for forest screens is bugged. At the beginning of the method it will check various conditions to determine if it should despawn the monster. Among those conditions, it checks if global117 is greater than 180 or global118 is greater than 80 (note: It’s not checking the absolute values, meaning this only works going east or south). The idea here is supposed to be that if you get far enough from the monster you escape. But global117 and global118 are updated *after* this check, and getting into close combat counts as a new room. As a result, global332 (chosen monster) and global333 (monster health) are set to 0 upon entering combat. This isn’t enough - the game will just set the monster’s health to full if global333 is 0. But if you hit the monster with a projectile at the same time as screen transition, global333 will be set to a negative value, killing the monster instantly. Throwing a rock deals 1 damage.
RNG was advanced a bit to manipulate the throwing levelup off the troll, bringing it up to 11.

Cheetaur

After the troll fight, global117 is no longer set to the value needed to do the one-hit KO. To get it again, we must spawn the Cheetaur in room 79 from the left and then run east one screen to set global117. Room 79 has an encChance of 20 (modified to 40). Entering on seed 42224 spawns a cheetaur from the left.
The rock throw was enough to level up luck (+3, up to 16) and throwing (+1, up to 12) again. After the fight we advance RNG to seed 38512 to get a +20 luck bonus climb into town. This climb allows for a +2 level up (to 17).

Town night 2

We need at least one item to fence before visiting the thieves guild. Little old lady’s house is easier to get into and has a lockpicking difficulty of 30 (the regular lockpick has a +10 bonus). Seed 7408 gives a +12 luck bonus, allowing us to get in first try. Pick Locks increased to 12 here (+2 levelup).
The inn closes at midnight. This means the inn and mandrake root cannot be on the same night.

Day 3

The main part here is the nest with the healer’s ring. Flame dart and fetch will always succeed, but wait a few seconds afterward before picking up the ring. Therefore the fastest way is to throw a rock at it.
V1.0 is bugged to lock up if the hero is running after throwing the rock. You need to WALK first, and then you need to hit a DC 25 throw. Unfortunately the only suitable luck bonus seed that was reachable was 50096, giving a +12 bonus on the throw. With throwing at 12, this meant needing to throw 2 rocks at the nest to get the experience for a level up before succeeding on the third throw.

Night 3 climb into town

Again RNG was a little less than ideal climbing into town. Seed 29552 gives a +16 luck bonus, requiring climbing two times to get a levelup. Strength and luck were also high enough to get levelups at this time. Strength was manipulated for a +2 levelup (to 24), and Luck and Climbing were manipulated for +3 levelups (19 and 20, respectively).
The thieves guild required only a minor manipulation to win at dag-nab-it. Crusher is full-second frame ruled; I delayed about half a second at him with no loss of frame rules to get better RNG after being thrown out to pass time to midnight.

Sheriff’s House and remaining town at midnight

The Sheriff’s door is difficulty 50. The toolkit gives a +35 bonus. Seed 47472 gives a +5 luck bonus to the pick and lets you in. Most of the time in the sheriff’s house is used to advance RNG to seed 12080 for the climb out of town. To hit this seed, seed 5168 was chosen for picking the safe. The safe is random difficulty and includes the toolkit bonus but also a -20 penalty. Seed 5168 will roll (Random 0 999) = 27 and give a difficulty of 3. It also places us on seed 12080 (12080 can’t be reached with right-clicks due to the previous seed displaying a text box).
Prior to rolling the climb check when climbing out of town, the game will roll a random number to see how many climb checks you get before it will stop you from trying more. 12080 will roll this random number; then 29040 gets you a +20 luck bonus climb.
The undead unguent was used shortly after the climb. This was the first available point at which I found time to parser dump the input necessary to use it without needing it on other commands.

Antwerp door

At this point strength is 24, requiring a +16 luck bonus to get into the Antwerp door. Seed 16368 gives a +17 bonus but can’t be reached with right-clicks, instead requiring a failed strength check to get to the seed. The time spent on this extra check gets consumed by the Elsa full-second frame rule.

Fortress door

There’s actually two ways through this door. The traditional TAS method is to cast open on the gate and then open it. The open spell gives you a grace period in which Toro will not notice you, allowing you to run right up to the door.
But if you instead “get rock” on entering the room, the fortress door will not be fully solid. You’ll be able to run right through it if you run up to it with high-speed hero off, then turn high-speed hero on. The downside to this method is that you have to wait for Toro to get far enough before you can do it.
Both methods entered the cafeteria at the same time. I picked “get rock” to showcase phasing through the door.

Fortress

The fortress is largely characterized by tricks discovered by the RTA community. Runners OhNoes and CaneCraft figured out how to set up the hero to run right over the trap rug and gap, and how to run straight to the final door in Yorick’s room. Through running the randomizer we also discovered that it’s possible to unlock the hero’s movement and grab items off of Elsa’s desk before she is dispelled, which I did to grab the mirror.

To Baba Yaga’s hut

RNG happened to line up just right so that there were no encounters to the Graveyard. But room 33 has an encChance of 35 - there is no way to get to Baba Yaga’s from the Graveyard without an encounter at this point. I tested two ways here - NWNWN spawned a goblin that followed closely and required swinging further north to avoid a lock up. NNWWN spawned the goblin off screen enough that it was never seen.

Baba Yaga

The game is looking for two substrings for the rhyme: “hut of brown” and “now sit down”. It’s possible to save two characters by writing “hut of brownow sit down”.
The fence is narrow enough on the right and left sides of the screen that it’s possible to run up to the fence with high-speed hero off and then turn it back on and phase through the fence. This is done the first two times to avoid the gate opening animation.
Normally Baba Yaga will skip straight to her final script if you have the mirror in your inventory. We get around this by dropping the mirror when we enter, then “toss rock” + direction to enter the screen and start her scripts. This unlocks the parser and allows us to type commands. The full “get hand mirror” input is necessary to pick the mirror back up to avoid losing it, and we can then type “hut y of brown now sit down”. “Y” as the second word is necessary to give Baba Yaga the affirmations she needs and leaves us with most of the rhyme typed out so the only thing we need to do after being teleported out is to delete the “y” to say it.
There’s a funny animation that can happen if you throw another rock while being held over the cauldron.
Finally the third time I enter, I make the deal with bonehead and dump the remaining input while he’s talking.

Possibilities for improvement?

I had to slow down Mage’s Maze by 1 second to get ideal spitting seed RNG. The two extra rock throws and climbs on day 3 I estimate lost me a second to Crusher’s frame rule. So maybe, maybe with better RNG planning it might be possible to save 2 seconds. Maybe. What the RTA community would really wants to see is not having to spend 5 minutes at Baba Yaga for 3 points.

Files:
HDD, 16 tracks, 63 sectors, 16 sides.
FilenameSizeMD5Timestamp
ADL.DRV8896b2b4afb47ab5bc94d5bdd63b38b6265919900101000000
CGA320BW.DRV201767237a44391ea20481cd85bf3dd3962019900101000000
CGA320C.DRV23765a8ab7f0b418cb2b4e37dfb0c1b5082e19900101000000
CMS.DRV58078d5ad9b51c0c6b82a7abbcf13fe1b0b419900101000000
CSM1.DRV2532da85de05261fd24096dc6b1e7ae9607919900101000000
EGA320.DRV1952d2f9e9ea730745558926518c930e737519900101000000
EXISTS.COM57461f35eec8998802abe2118b26e0c9cfa19900101000000
FB01.DRV2459fa73279cd317612f802a1609f6ea283c19900101000000
GODIR.COM50728d7efe8c850bfb52231fc58567e25cc19900101000000
HERCMONO.DRV2193f548ada27461194ed2671b8a236e666a19900101000000
HERO.BAT1004a413f79a5e15bcad59ae5d3982469419900101000000
IBMKBD.DRV446ab91b093a010aeb63866f71bc491110e19900101000000
IMF.DRV22901b495562c5d107a0577f39ad1dcc606619900101000000
INSTALL.EXE24064a5a151689bfc0d0c5ab3ee61ff2f90ba19900101000000
INSTALL.HLP96987a0925b0cf9d429c4d598ecc774e168519900101000000
INSTGAME.BAT83783cd6f1fd6df591e15a2df654da4b60119900101000000
JOYSTICK.DRV536865fd157f28915c8c831c17ac8b55ed119900101000000
JR.DRV29905195148b419497549252ca91d491ff6619900101000000
MCGA320.DRV16267ced0fc0a7cc5395a1e4321250fcd94319900101000000
MT32.CFG75c6b0d4b6e526a04134c74c82b8032c5719900101000000
MT32.DRV309907664f7d1fab316cf10c433c6b4e1fb719900101000000
MT540.DRV2528686177937d356346279f4cc582645eb619900101000000
PCJR320.DRV1660307e67dbdf6289bf7e0488030f4442af19900101000000
RESOURCE.00080334e4f2ab5f2e0cf5c78a3c072ef1d0dea319900101000000
RESOURCE.001462727308503aa041ff254197d561dba3b506319900101000000
RESOURCE.00264686967f08692e0457e84f81eed714309619019900101000000
RESOURCE.00364220314542d38474c3b36d0cedafbd8777caa19900101000000
RESOURCE.004641688ba7c4ac121c40a125f0b871f7cb8709a19900101000000
RESOURCE.CFG744e14592928598a4736830af6c59418b319900101000000
RESOURCE.MAP6474cb0ba17773dff6eab9628ceeed2e368619900101000000
SB.CFG744e14592928598a4736830af6c59418b319900101000000
SCIV.EXE750279c902990be12e95e82e5845d390cd98519900101000000
SIERRA.COM53848e6faeb6af8e540fed0523f17ec4c0d19900101000000
SPACE.COM500c645587408e33998e9fc6f51be4ee63919900101000000
STD.DRV2471b2f21c19f676932d09ffd8becd80533419900101000000
TANDY320.DRV16676881cad03cd7386836cab4d19b03bdf719900101000000
TANDYKBD.DRV473d51176ceeae179f95ae478933591a0f219900101000000
__INSTH.BAT1009af4fc6a3f46cda7216cdb0b98931434919900101000000

DrD2k9: Claiming for judging.
DrD2k9: Obviously a lot of work was put into planning, routing, and RNG Manipulation. Optimization looks solid.
Considering this run is over 12 minutes faster than the current record RTA run of the same category (also held by the author of this submission), it's obviously superhuman play.
Accepting as new branch for this game.
Side note: Edited the file list in submission notes to match actual filename used in the movie file. INSTH.BAT should have been __INSTH.BAT

EZGames69: Processing...
Last Edited by EZGames69 on 4/1/2023 8:38 PM
Page History Latest diff List referrers