Editor, Player (67)
Joined: 6/22/2005
Posts: 1041
The recent discoveries by QoNe (see here) motivated me to take a look at this game again. I found the enemy spawn data for the castle, town, and dungeons. A list of enemy types starts at ROM addresses 0x20BED, 0x20CF8, and 0x20E0F, respectively. The actual spawn locations immediately precede those lists and start at 0x20B84, 0x20CA4, and 0x20DB8, respectively. The format is three bytes per enemy. The first byte has the lowest 8 bits of the X-coordinate. The third byte has the lowest 8 bits of the Y-coordinate. The second byte has the highest 4 bits of each, with format yyyyxxxx. What this shows is that the green cloud head protecting the majority of the dungeons spawns at coordinate (0x05EB, 0x00CB). Glancing through the spawn addresses shows that the closest enemy spawn is probably at (0x0502, 0x00FE). I think that the camera X-coordinate has to be within 0x40 of the enemy's X-coordinate to trigger the spawn. This is based on tests with the beehive that gets bypassed in the town in the TAS. It spawns at (0x04FA, 0x02F4). The jump that's used to bypass it places the camera Y-coordinate at 0x02F5, but it only works if the camera X-coordinate is 0x053B or higher. If you jump too high and bring the camera Y-coordinate to 0x02F4 or less, then the beehive spawns. Camera X-coordinate is 2 bytes starting at RAM 0x0079, and camera Y-coordinate is 2 bytes starting at RAM 0x007B. I started an address set with some useful addresses here. The addresses for the thief bosses refer to the bosses you fight at the end of each trial. The attack state indicates if the boss is attacking, resting, or lunging backwards to dodge your attack. Value 0x71 is the backwards lunge for all three. Preliminary trace logging indicates that the code leading to that state is different depending on whether you're attacking from a standing position or ducking position. The counters indicate how much time the boss will spend in the current state; they decrease by 1 every 2 frames. There's plenty of work yet to be done, including:
  • Further clarification of criteria for enemies to spawn
  • Determine what causes the thief bosses to lunge backwards
EDIT: Further investigation shows the following code as pertinent for determining the lunging state of the thief 1 boss:
$BA5F:A5 16     LDA $0016   ; $0016 was set to X-distance between Kuros and boss earlier
$BA61:C9 2D     CMP #$2D
$BA63:B0 32     BCS $BA97   ; Distance must be less than 0x2D (45)
$BA65:AD D8 03  LDA $03D8   ; Kuros status
$BA68:C9 12     CMP #$12    ; is 0x12 if B button pressed (attacking)
$BA6A:D0 1A     BNE $BA86
$BA6C:AD 26 04  LDA $0426   ; Another Kuros status address that depends on what other buttons are pressed
$BA6F:29 7F     AND #$7F    ; 1 if none, 2 if up, 3 if up + forward
$BA71:C9 05     CMP #$05    ; 4 if forward, 5 if down + forward, 6 if down
$BA73:90 04     BCC $BA79   ; must be < 5
$BA79:C9 03     CMP #$03
$BA7B:90 11     BCC $BA8E   ; and >= 3
$BA7D:A9 71     LDA #$71    ; This is the value we want for the boss to lunge backwards
$BA7F:9D 40 04  STA $0440,X ; Set the boss attack state
So the boss will lunge backwards if Kuros is within an X-distance of 45 and attacking with the up + foward or the forward button(s) pressed. Forward is right in this case. Further testing shows that this does not speed up the fight, probably because of the long invincibility time of the boss. The current implementation of the thief 1 boss fight may be as optimized as possible. This is part of the code that determines when the thief 2 and 3 bosses lunge backwards:
$8D80:A5 B6     LDA $00B6    ; Check room number
$8D82:C9 62     CMP #$62
$8D84:F0 36     BEQ $8DBC    ; Must not be 0x62 (thief 1 trial)
$8D86:B9 40 04  LDA $0440,Y  ; Check boss attack state
$8D89:C9 97     CMP #$97
$8D8B:D0 0F     BNE $8D9C    ; Must be 0x97 (resting)
$8D8D:86 25     STX $0025
$8D8F:BE 26 04  LDX $0426,Y  ; Retrieve value of X from $043C
$8D92:A9 28     LDA #$28
$8D94:9D 70 03  STA $0370,X  ; Set counter for attack state
$8D97:A9 71     LDA #$71
$8D99:9D 40 04  STA $0440,X  ; Set attack state
I have not figured out the position criteria yet. The important part of the above code is that the boss has to be in its resting state, which occurs infrequently. Unfortunately, the boss continues its current attack pattern after a backwards lunge and does not go back into a resting state, so it does not seem possible to lock it into the backwards lunge state and avoid damage. It does go into a resting state briefly while attacking on the left side of the screen, which allows you to interrupt part of its attack pattern. This movie demonstrates such an interruption. And this movie uses the trick to improve the currently published thief 2 boss fight by 34 frames. Here is a Lua script to display hitboxes for Kuros's weapons. Another Lua script that shows the relevant hitboxes for the thief trials. Also includes the criteria necessary to trigger the backwards lunge. Note that the important hitbox is the one for the boss's weapon, not for the boss itself.
Current Projects: TAS: Wizards & Warriors III.
Post subject: RNG and clouds
Editor, Player (67)
Joined: 6/22/2005
Posts: 1041
This is what appears to be the code for the game's RNG at RAM addresses $000D through $000F:
$CF5E:A5 0D     LDA $000D
$CF60:29 48     AND #$48
$CF62:69 38     ADC #$38
$CF64:0A        ASL
$CF65:0A        ASL
$CF66:26 0F     ROL $000F
$CF68:26 0E     ROL $000E
$CF6A:E6 0D     INC $000D
$CF6C:A5 0D     LDA $000D
$CF6E:65 04     ADC $0004
$CF70:65 05     ADC $0005
$CF72:65 10     ADC $0010
$CF74:65 11     ADC $0011
$CF76:65 12     ADC $0012
$CF78:65 14     ADC $0014
$CF7A:69 0D     ADC #$0D
$CF7C:85 0D     STA $000D
Using FCEUX 2.2.3, I set write breakpoints on $000D through $000F with the condition P<#CF66 || P>#CF7C and played the latest published movie. The only other time those addresses were written was when they were loaded with 0 upon console reset, so I'm pretty confident that we have all the variables necessary to manipulate the RNG. $0004 stores the currently pressed buttons as bits for as long as they are pressed, while $0005 does the same thing but only for the first two frames when a button is pressed. The bit order--most to least significant--is A, B, Select, Start, Up, Down, Left, Right. $0010 through $0012 form a three-byte counter that increases by one each frame, with $0010 the least significant byte and $0012 the most significant byte. The counter seems to restart from zero each time you enter a door. $0014 is another counter that loops upward from 0 to 7 and then resets. It increases by one each frame but pauses when the screen is black, such as during transitions between areas after entering a door. When the camera X-coordinate gets within 0x40 of the previously mentioned (0x0502, 0x00FE) spawn coordinate, $04B9 starts counting downward from 0. It doesn't seem like you can get outside the acceptable range of camera Y-coordinates in the small space by the first wizard guild, thankfully. $04B9 pauses if camera X > 0x0542, however, and restarts from 0 every time camera X <= 0x0542. $04B9 decreases by one every two frames. When it reaches 0 during the countdown, an enemy cloud may spawn. Each cloud has another timer that starts at 0 and counts up by one every two frames. So far, I've seen these timers stored in $04BB and $04BC. Other addresses may be possible if you can get three or more clouds active at once. This leads to:
$8372:FE A8 04  INC $04A8,X  ; This is the cloud's timer
$8375:BD A8 04  LDA $04A8,X
$8378:C9 80     CMP #$80
$837A:D0 3C     BNE $83B8    ; Value must be 0x80
$837C:A5 0D     LDA $000D    ; One of the RNG values
$837E:4A        LSR
$837F:90 37     BCC $83B8    ; Branch if bit 0 was not set
$8381:A5 85     LDA $0085    ; Current costume type
$8383:C9 03     CMP #$03     ; Check if wizard
$8385:F0 38     BEQ $83BF    ; Branch if wizard, attack otherwise
So the cloud only attacks when:
  • Its timer is 0x80,
  • The least significant bit of $000D is set, and
  • Current costume is not wizard.
This means that it will take about 768 frames (2 * 256 for $04B9 + 2 * 128 for $04BB or $04BC) to spawn a cloud and get it into an attacking state. The good thing is that the way those counters work gives us time to manipulate the RNG into favorable states. It takes at least 3179 frames to get the knight costume, 433 frames to get the statue and 2746 to complete the trial (estimated using frame numbers for the first completely black screens when entering/leaving doors into the appropriate rooms), so we can save around 2400 frames by skipping it. We still have to get the crown jewel from the princess in the Palace, however, so we can't skip that area altogether. The location at which the cloud spawns is determined by some combination of RNG address $000F (primarily), RNG address $000E (in some cases), the camera X- and Y-coordinates, and Kuros's X- and Y-coordinates. The entire code is long, so I'll just post the results of my analysis. If you want to verify it, set an execution breakpoint at $D7BE. The subroutine starts there and goes through the RTS at $D857. Some helpful info:
  • $02EE,Y and $0308,Y are the low and high bytes of the cloud's X-coordinate, respectively.
  • $033C,Y and $0356,Y are the low and high bytes of the cloud's Y-coordinate, respectively.
  • $00B4 and $00B5 seem to always be 0x07 in the part of the UnderWorld where we're doing the glitch.
Bits 1 and 2 of $000F are tested to determine different X and Y spawn locations.
Bit 1 set, Bit 2 set:
 Cloud X = Camera X - 0x20
 Cloud Y = ($000E AND 0x7F) - 0x40 + Kuros Y

Bit 1 set, Bit 2 clear:
 Cloud X = Camera X + 0x120
 Cloud Y = ($000E AND 0x7F) - 0x40 + Kuros Y

Bit 1 clear, Bit 2 set:
 Cloud X = ($000F AND 0x3F) - 0x20 + Kuros X
 Cloud Y = Camera Y - 0x30

Bit 1 clear, Bit 2 clear:
 Cloud X = ($000F AND 0x3F) - 0x20 + Kuros X
 Cloud Y = Camera Y + 0xD0
It is important to note that the code actually discards any overflow after calculating the low byte of Cloud Y when bit 1 is set; the high byte is set directly to the high byte of Kuros's Y-coordinate. The Bit 1 clear, Bit 2 set case is useless for our purposes. When leaving the wizard guild, the camera coordinates are set to (0x052A, 0x0000). I have not found a way to get the Y-coordinate greater than 0x30 without scrolling the X-coordinate beyond 0x0542, which will reset the $04B9 counter. So the cloud gets spawned at some Y-coordinate with high byte 0xFF and immediately gets despawned. I think that one of the bit 2 clear cases should be our target. We can spawn the cloud to the right of and below Kuros, which will make it attack in a diagonal direction that makes Kuros clip up and left through the wall.
Current Projects: TAS: Wizards & Warriors III.
Editor, Player (67)
Joined: 6/22/2005
Posts: 1041
Here's a WIP that improves the fight with the guardian of the silver thief statue by 200 frames. A simple change in strategy based on runs from speedrun.com that attacks from behind when the guardian drops rather than making it jump to the opposite side of the room right away. There may be room for additional improvement, if anyone wants to give it a try. This is a Lua script I used to help. Another WIP that gets to the second thief guild boss fight. Required a lot of work to fix Kuros's X-subpixel position and maintain all of the frame savings. I also modified some of the movie's metadata to fix the ROM name and authors. WIP that completes the second thief guild. The game now has a resources page. FatRatKnight asked some questions on IRC that led to the discovery of a new trick: You can get to maximum speed more quickly by jumping from a crouching movement. Here's a WIP that saves 9 frames up to the thief 1 guild by implementing the trick; it's not used everywhere because it leads to unfavorable sub-pixel values sometimes. (In case you're wondering, WIP 04 changed some sub-pixel values to sync the previous improvements up to the thief 3 trial). Saved another 9 frames up to the point before fighting the silver thief statue guardian. The movements near the beginning of the thief 1 trial (frames 4300-4400) were done to sync the sub-pixel values and allow pasting of input from the published movie. Since the trial scrolls automatically, anything until the actual boss fight can be replaced without breaking sync. The corridor immediately before the boss fight should allow space to re-sync sub-pixels. Improved the silver thief statue building by 87 frames compared to the previous attempt in WIP 01. Mostly due to a better guardian fight, but there were also some improvements related to the crouch-jump trick. Get the file here. A new glitch was discovered by speedrunner theLimitBreak that despawns the thief guild guardian. An input file that replicates it can be found here. MAJOR UPDATE: Speedrunner xJackieBx discovered a faster Knight 1 skip that's also RTA viable. Video explanation here.
Current Projects: TAS: Wizards & Warriors III.
Joined: 2/25/2006
Posts: 407
I was reading through https://www.speedrun.com/wizards_and_warriors_iii_kuros_visons_of_power/thread/lkkiw and was kinda surprised to find https://tasvideos.org/GameResources/NES/WizardsAndWarriors3 was so barebones on tricks and glitches... I think someone trimmed the decimals off of Movement Speed for each costume too. Edit: I guess all the information from that topic is here, in this Tasvideos.org topic and the topic has so little activity there isn't much reason to port it all to a Resources page. I suppose I can impart some interesting info that's useless for TAS'ing the game. Losing a life results in an extended invincibility period, allowing you to jump over the beehive that blocks the way to Upper Piedup (This is useless since there is now a way to despawn the beehive which is significantly faster). You can also exploit this to get in to the Underworld depths without an appropriate costume to facilitate escaping the area as the glowing obstacle beneath the first Wizards test has no collision box, only a hurt box (so you can walk right through it after a death).
Ryzen 3700X, ASUS Crosshair VIII Hero (WiFi) Motherboard, 32GB 3600MHz RAM, MSI Geforce 1070Ti 8GB, Windows 10 Pro x64 http://tasvideos.org/Nach/FranpaAlert.html
Editor, Player (67)
Joined: 6/22/2005
Posts: 1041
franpa wrote:
I think someone trimmed the decimals off of Movement Speed for each costume too.
Do you mean on the Resources page? The sub-speed values are the fractional/decimal part.
Current Projects: TAS: Wizards & Warriors III.
Joined: 2/25/2006
Posts: 407
Dacicus wrote:
franpa wrote:
I think someone trimmed the decimals off of Movement Speed for each costume too.
Do you mean on the Resources page? The sub-speed values are the fractional/decimal part.
Ah, I see, thanks.
Ryzen 3700X, ASUS Crosshair VIII Hero (WiFi) Motherboard, 32GB 3600MHz RAM, MSI Geforce 1070Ti 8GB, Windows 10 Pro x64 http://tasvideos.org/Nach/FranpaAlert.html