Discussion in 'Engineering & Reverse Engineering' started by Tweaker, May 29, 2008.

1. ### rocketsailor

Member
Oh...that makes sense. Now I feel dumb for not realizing it was a problem with the shield. Thank you Kilo.

Last edited: May 10, 2024
2. ### RetroKoH

Member
1,681
37
28
Hi guys! I've got two questions but I'm gonna wait to ask the second one until after the first one is resolved, as it's a little messy to explain.

So, tl;dr, how do I accurately generate a random number from 1-xxx in Sonic 1 that ISN'T a multiple of 2? I know how to call RandomNumber, and I know to use v_framecount if I wanna base "random" purely on timing, but IDK how to manipulate the end result for other ranges. Details below:

I implemented the random monitor into Sonic 1 as an optional toggle, and I want it to randomly give out one of 6 items (indexed 0-5). I couldn't ANDI #7, so I took a long convoluted route to get there that is likely more complicated (and less balanced) than it needs to be:

Code (Text):
1. ; give 'random' item (Fix randomizer to include S3K Monitors)
2.         jsr        (RandomNumber).w    ; call for random number
3.         andi.w    #\$11,d0                ; random items in two pools (upper nybble used later)
4.         move.w    (v_framecount).w,d1    ; use the timer to determine which item
5.         andi.w    #1,d1                ; get 0 or 1 (then decide whether to increment)
6.         addq.w    #1,d1                ; add 1 to prevent getting the static monitor
7.         btst    #4,d0                ; should we increment d1 a second time?
8.         beq.s    .noInc                ; if not, branch
9.         addq.w    #1,d1                ; increment again
10.
11. ;  d1 = (1, 2, or 3)
12.     .noInc:
13.         btst    #0,d0                        ; which item group?
14.         bne.s    .group0
16.     .group0:
17.         move.b    d1,obAnim(a0)        ; get subtype
So it generates a random number into d0, but only retains bytes 0 and 4. bit 0 denotes which pool to pull from (Pool 1 or Pool 2), while bit 4 denotes whether or not we will increment the next value by 1 or not.
It then loads the current frame count into d1, only retaining the least significant bit. This value determines which of the three items in the chosen pool we will receive, but note that we only have two resulting values for d1. To get a third, we use bit 4 from d0 to determine whether or not to increment d1. This allows d1 to be either 0, 1 or 2. d1 is then loaded into the icon's animation OST.

My question is, is there a better way to do this, where all items have closer to equal weight in randomness?

Last edited: Aug 23, 2024
3. ### Devon

DROWN, DROWN, DROWN MYSELF! Tech Member
1,361
1,612
93
An easy method is to mask out the high word of the random number (AND with 0xFFFF) and use modulus to fit it within a range. Let's say you want to generate a number within the range [1, 4] (inclusive). After getting the random number and applying the mask (this is important, because division is a 32-bit by 16-bit function, and the result must fit in 16-bits, because the top word is overwritten with the remainder of the division), you divide it by the max number you want - the min number you want + 1. In this case, you'd be dividing by (4 - 1) + 1... which is 4. Then, you perform a swap to access the remainder, with will be in range [0, 3], so then you add the min number. to get the range you want.
Code (ASM):
1.         jsr     RandomNumber                    ; Get random number
2.         ; Add further randomness modification here before continuing
3.         andi.l  #\$FFFF,d0                       ; Make sure division will always work
4.         divu.w  #(MAX_NUM-MIN_NUM)+1,d0         ; Divide by adjusted maximum number
5.         swap    d0                              ; Get remainder of division

A hacked up method that doesn't involve division is to AND mask the random number with the smallest possible mask you can, and then check if the result fits in the range, and if not, do something else to get a number that does (get another random value, modify the value, use a default value, etc.).
Code (ASM):
1. .GetRandom
2.         jsr     RandomNumber                    ; Get random number
3.         ; Add further randomness modification here before continuing
5.         cmpi.w  #MAX_NUM-MIN_NUM,d0             ; Is it in range?
6.         bhi.s   .GetRandom                      ; If not, branch

or
Code (ASM):
1. .GetRandom
2.         jsr     RandomNumber                    ; Get random number
3.         ; Add further randomness modification here before continuing
5.         cmpi.w  #MAX_NUM-MIN_NUM,d0             ; Is it in range?
6.         bls.s   .GotRandom                      ; If so, branch
7.         ; Modify number here
8.
9. .GotRandom:

4. ### RetroKoH

Member
1,681
37
28
Ok. Onto the second question... I don't know how to fully explain this without including a ROM, so here is the latest build from S1Fixed: https://github.com/RetroKoH/S1Fixed/blob/main/S1Fixed.bin

So, long story short, some of my art doesn't load into VRAM properly in Labyrinth Zone. The most apparent example is the Ring Count on the HUD. It seems that the code is being run, but the proper art doesn't appear, at least not initially. Oddly, this only happens when the Centiseconds mod is enabled. Meanwhile, the ? monitor icon NEVER loads when PLCs are loaded if the Randomizer mod is active.

Art loading bugs like these only occur in Labyrinth Zone (and by extension, SBZ3). Does anyone have any insight as to why this might be happening? Source code is available on the Github. Everything is public. TIA

5. ### Bellie

Member
so, I've got no idea on how to use `PlaneMapToVRAM` (or `copyTilemap` in Sonic 1).
apparently the tilemap shows up correctly in Blastem's plane map debugger, but nothing actually appears on screen. what am I doing wrong here?
my code: (sidenote: I don't know how to make a code-block appear, so I've changed the font to a monospaced one to approximate it.)

move.b #bgm_Stop,d0
bsr.w PlaySound_Special ; stop music

clearRAM v_objspace
clearRAM v_misc_variables
clearRAM v_levelvariables
clearRAM v_timingandscreenvariables

disable_ints
move.w (v_vdp_buffer1).w,d0
andi.b #\$BF,d0
move.w d0,(vdp_control_port).l

lea (vdp_control_port).l,a6
move.w #\$8B03,(a6) ; line scroll mode
move.w #\$8200+(vram_fg>>10),(a6) ; set foreground nametable address
move.w #\$8400+(vram_bg>>13),(a6) ; set background nametable address

bsr.w ClearScreen
lea (ArtNem_DemoEndingScreen).l, a0
locVRAM 0 ; ArtTile_Credits_Font*tile_size
bsr.w NemDec
lea (v_256x256&\$FFFFFF).l,a1
lea (Eni_DemoEndingScreen).l,a0 ; load title screen mappings
move.w #0,d0
bsr.w EniDec

disable_ints

copyTilemap v_256x256&\$FFFFFF,vram_bg,34,22

moveq #palid_Title, d0

-
nop ; temporary infinite loop
bra.s -

6. ### Kilo

Deathly afraid of the YM2612 Tech Member
715
711
93
Code (Text):
1. move.w (v_vdp_buffer1).w,d0
2. andi.b #\$BF,d0
3. move.w d0,(vdp_control_port).l
This is masking out the bit to enable the VDP to display anything to the screen.
v_vdp_buffer1 stores a copy of VDP register mode 1. It's a bitfield laid out like this
|0|E|V|D|H|1|0|0
E = Enable display
V = Enable V-Blank
D = Enable DMA
H = Screen height (Only usable in PAL)
\$BF in binary is 10111111, and the AND instruction means that only the bits that are 1 will be left alone, while the bits that are 0 will be turned to 0, and that clearly is being applied to the enable display flag.

So to fix this, after
Code (Text):
1. copyTilemap v_256x256&\$FFFFFF,vram_bg,34,22
Place
Code (Text):
1.         move.w    #\$8100|%01110100,(vdp_control_port).l
I'd also probably recommend moving that 2nd disable_ints to after the branch to PaletteFadeIn, not sure if that effects anything, but base Sonic 1 usually does that after a palette fade call rather than after decompressing mapping data.

7. ### Bellie

Member
hah, I knew it was something like this! thank you~
also for your suggestion for disable_ints, that was a failed safety measure in case that was what I was missing.

8. ### penBorefield

Living in my best life Member
188
29
28
Basement
Patching things up
Quick question. What's the difference between two disassembly types (AS vs. ASM68K)?

9. ### Kilo

Deathly afraid of the YM2612 Tech Member
715
711
93
Those aren't disassembly types, those are assemblers that build the code.
ASM68k assembles only 68k code. It's fast and reliable, but old and closed source.
AS assembles for a multitude of CPUs, which includes the 68k and Z80. However it's slow, taking up to a minute to assemble games from the early 90's, seemingly unstable, often crapping itself for seemingly no reason and then reassembling just fine, but open source.
We've been trying to look for a solution to the problem of ASM68k only being limited to the 68k, and AS being garbage for like 5 years now. Some suggest ASMX, some VASM. Everything has pros and cons. If you don't intend to mess around with the sound driver though, you'd probably be fine with ASM68k.

Oh also ASM68k's local label prefix by default is @ while AS' is . and I don't care what you say @ is a far better prefix, it's clear and unique.

Last edited: Sep 2, 2024 at 12:26 PM
10. ### MainMemory

Kate the Wolf Tech Member
4,781
362
63
SonLVL
Technically AS' local label prefix is \$\$, the . is a separate feature called composed temporary symbols, that allow you to refer to them from outside their scope by including the name of the previous full label before the dot.

• Informative x 2
• Like x 1
• Agree x 1
• List
11. ### penBorefield

Living in my best life Member
188
29
28
Basement
Patching things up
I got a fatal error from BlastEm. It says "Unable to create SDL window. Window is too large". I set the video width to 320 then reverted to 640. Now it's stuck. What should I do?

12. ### Kilo

Deathly afraid of the YM2612 Tech Member
715
711
93