Tuesday, January 29, 2019

Stellar 7 text font at 0x7900


I found the text font at 0x7900. It goes from character 32 (space) to 127. 126 and 127 are tiny flag symbols.

and we can write some lua to write some text to the screen:




Monday, January 28, 2019

More COS routine multiply analysis.

Binary math really melts my brain. I'm not really designed to function in binary so I've been trying to figure out how the multiply routine ends up with a costable value * multiplicand *2.


I think it's useful to build a model to see exactly what's happening with the bits.




And if I run this in mame's lua console I can better see where all those bits are going:

7 bits of multiplier
mem00=7f
mem01=ff
LDX $01=ff
DEX
STX $01=fe
LDA $00=7f
LSR A=3f
STA $02=3f
LDA #$00 a=00
BEFORE ADC $01=fe  a=00 00000000
ADD    ADC (c=1+$01)=ff 11111111
AFTER  ADC $01=fe  a=ff 11111111 c=0
BEFORE ROL: 0_11111111_00111111
ROL8 BEFORE= ff  carry= 0  11111111
ROL8 AFTER = 7f  carry= 1  01111111
ROL8 BEFORE= 3f  carry= 1  00111111
ROL8 AFTER = 9f  carry= 1  10011111
AFTER ROL:  1_01111111_10011111
1 7f 01111111 9f 10011111
BEFORE ADC $01=fe  a=7f 01111111
ADD    ADC (c=1+$01)=ff 11111111
AFTER  ADC $01=fe  a=7e 01111110 c=1
BEFORE ROL: 1_01111110_10011111
ROL8 BEFORE= 7e  carry= 1  01111110
ROL8 AFTER = bf  carry= 0  10111111
ROL8 BEFORE= 9f  carry= 0  10011111
ROL8 AFTER = 4f  carry= 1  01001111
AFTER ROL:  1_10111111_01001111
1 bf 10111111 4f 01001111
BEFORE ADC $01=fe  a=bf 10111111
ADD    ADC (c=1+$01)=ff 11111111
AFTER  ADC $01=fe  a=be 10111110 c=1
BEFORE ROL: 1_10111110_01001111
ROL8 BEFORE= be  carry= 1  10111110
ROL8 AFTER = df  carry= 0  11011111
ROL8 BEFORE= 4f  carry= 0  01001111
ROL8 AFTER = 27  carry= 1  00100111
AFTER ROL:  1_11011111_00100111
1 df 11011111 27 00100111
BEFORE ADC $01=fe  a=df 11011111
ADD    ADC (c=1+$01)=ff 11111111
AFTER  ADC $01=fe  a=de 11011110 c=1
BEFORE ROL: 1_11011110_00100111
ROL8 BEFORE= de  carry= 1  11011110
ROL8 AFTER = ef  carry= 0  11101111
ROL8 BEFORE= 27  carry= 0  00100111
ROL8 AFTER = 13  carry= 1  00010011
AFTER ROL:  1_11101111_00010011
1 ef 11101111 13 00010011
BEFORE ADC $01=fe  a=ef 11101111
ADD    ADC (c=1+$01)=ff 11111111
AFTER  ADC $01=fe  a=ee 11101110 c=1
BEFORE ROL: 1_11101110_00010011
ROL8 BEFORE= ee  carry= 1  11101110
ROL8 AFTER = f7  carry= 0  11110111
ROL8 BEFORE= 13  carry= 0  00010011
ROL8 AFTER = 09  carry= 1  00001001
AFTER ROL:  1_11110111_00001001
1 f7 11110111 09 00001001
BEFORE ADC $01=fe  a=f7 11110111
ADD    ADC (c=1+$01)=ff 11111111
AFTER  ADC $01=fe  a=f6 11110110 c=1
BEFORE ROL: 1_11110110_00001001
ROL8 BEFORE= f6  carry= 1  11110110
ROL8 AFTER = fb  carry= 0  11111011
ROL8 BEFORE= 09  carry= 0  00001001
ROL8 AFTER = 04  carry= 1  00000100
AFTER ROL:  1_11111011_00000100
1 fb 11111011 04 00000100
BEFORE ADC $01=fe  a=fb 11111011
ADD    ADC (c=1+$01)=ff 11111111
AFTER  ADC $01=fe  a=fa 11111010 c=1
BEFORE ROL: 1_11111010_00000100
ROL8 BEFORE= fa  carry= 1  11111010
ROL8 AFTER = fd  carry= 0  11111101
ROL8 BEFORE= 04  carry= 0  00000100
ROL8 AFTER = 02  carry= 0  00000010
AFTER ROL:  0_11111101_00000010
0 fd 11111101 02 00000010
[MAME]> 
[MAME]> print(hex(0xff * 0x7f * 2))
fd02
[MAME]> 

Friday, January 25, 2019

Cos and Sin multiplication routine at 0xD00.


So let's disassemble Stellar 7's cos and sin multiplication routine and write it to a file with the dasm command.

dasm d00_multiplication_dasm.txt,d00,f6

This will dissassemble from d00-df5.


As input, it takes the number to be multiplied in $00, the angle in A.

As output, the COS(A) * $00 goes in $22-23 and the SIN A goes in $24-25. The high byte of the output will always be zero. The multiplication generates 16 bits, we're only interested in the high byte, the remainder goes in $02 but it is just ignored.

Another thing to note is that the cos table values are 7 bits with the high bit used as a sign bit.
The cos table represent fractions from 0 to 80.

0x0 represents 0.0 and 0x80 represents 1.0.

But we only have 7 bits, from 0 to 0x7F, we can't actually get 1.0. That's why we've got the special case handling of the angles at 0,90,180 and 270 degrees.

0x7f divided by 128 gets pretty close to 1.0, about 99.2%.

print (0x7f/0x80)
0.9921875

The multiplication routine multiplies by the 7 bits of the lookup table value and then multiplies that by 2.

Therefore our multiplication routine can only get up to multiplying by 0x7f * 2 = 0xfe or 254.

===============================

Example: multiplying by 0x7f or (.992)

So for example, let's take a number, say 0x19 and multiply it by 0x7F and then multiply it by 2. We get

print (string.format("%x",0x7f*0x19*2))
18ce

We take the result of 0x18CE, throw away the low byte of the result 0xCE and we get 0x18 which is 99% of 0x19.


Example: multiplying by 0x40 or (0.5)

So for another example, let's take our same number, say 0x19 and multiply it by 0x40 and then multiply it by 2. We get

print (string.format("%x",0x40*0x19*2))
c80

We take the result of 0xC80, throw away the low byte of the result 0x80 and we get 0xC which is 0.5 * 0x19 or 0xC. 0x19=25 0xC=12.

Basically what we're doing is multiplying by our cos value fraction (0 to 255) and then shifting that result by 8 bits (dividing by 256 by throwing away the low byte).

result = (multiplicand * ((cosvalue AND 0x7f) * 2) / 256

The sign bit is handled specially, we just negate the result by EOR #$FF and ADC #$00 (with the sign bit set, effectively adds by 1).

==================================

Special cases:

First thing we do is to look and see if the number is zero. If so, then we can just put #$00 in $22-23 and $24-25 and rts.

At 0d0d, there's a TAY make a copy of the angle and store it in Y.

We and it with #$3f to check to see if it's any of the special angle cases, 0,0x40,0x80,0xc0 or 0,90,128,192 degrees. If A is zero after anding with #$3f, then we know it's a special case and we jmp to $0db3.

The special cases are pretty straightforward, if the angle is 0, cos 0 = 1, sin 0 = 0. Therefore at $0db6 we copy $00 to $22, put #$00 in $23,$24 and $25.

If the angle is 90, cos 90=0, sin 90=1.

If the angle is 180, cos 180=-1, sin 180=0. So return negative $00 in $22-23, #$00 in $24-25.

If the angle is 270, cos 90=0, sin 90=-1, at $0de6 we put #$00 in $22-23 and we do a subtraction of $00 from #$00 and put that in $24 (ldx #$00,txa,sec,sbc $00,sta $25) and put #$FF in $25 (ldx #$00,dex and stx $25).




0D00: A6 00    ldx $00
0D02: D0 09    bne $0d0d
0D04: 86 22    stx $22
0D06: 86 23    stx $23
0D08: 86 24    stx $24
0D0A: 86 25    stx $25
0D0C: 60       rts
0D0D: A8       tay                  copy the angle into y
0D0E: 29 3F    and #$3f
0D10: D0 03    bne $0d15
0D12: 4C B3 0D jmp $0db3            jmp to 0db3, one of our special cases 00,40,80,c0.
0D15: B9 00 0F lda $0f00, y         get our lookup table value
0D18: CA       dex                  decrement x (because when we do the adds, the carry bit will be set)
0D19: 86 01    stx $01              store x in $01  (multiplicand minus 1 goes in $01) 
0D1B: 4A       lsr a                shift a right
0D1C: 85 02    sta $02              store a in $02 (will shift bits in and out of $02 with ror, when done $02=low byte of result)
0D1E: A9 00    lda #$00
0D20: 90 02    bcc $0d24
0D22: 65 01    adc $01
0D24: 6A       ror a
0D25: 66 02    ror $02
0D27: 90 02    bcc $0d2b
0D29: 65 01    adc $01
0D2B: 6A       ror a
0D2C: 66 02    ror $02
0D2E: 90 02    bcc $0d32
0D30: 65 01    adc $01
0D32: 6A       ror a
0D33: 66 02    ror $02
0D35: 90 02    bcc $0d39
0D37: 65 01    adc $01
0D39: 6A       ror a
0D3A: 66 02    ror $02
0D3C: 90 02    bcc $0d40
0D3E: 65 01    adc $01
0D40: 6A       ror a
0D41: 66 02    ror $02
0D43: 90 02    bcc $0d47
0D45: 65 01    adc $01
0D47: 6A       ror a
0D48: 66 02    ror $02
0D4A: 90 02    bcc $0d4e
0D4C: 65 01    adc $01
0D4E: 6A       ror a
0D4F: 66 02    ror $02
0D51: A2 00    ldx #$00
0D53: 90 07    bcc $0d5c              no sign bit, so let's branch to d5c
0D55: 49 FF    eor #$ff               sign bit is set, so negate our result A by EOR #$FF, and ADC #$00 (carry is set)
0D57: 69 00    adc #$00
0D59: F0 01    beq $0d5c              need this branch here, because if A was 0, we don't want -0 to come back as FF00 (-256).
0D5B: CA       dex                    decrement x so x will be #$FF
0D5C: 85 22    sta $22
0D5E: 86 23    stx $23
0D60: A6 00    ldx $00
0D62: 98       tya                    Multiply by SIN value, first get the angle from the Y register
0D63: 18       clc
0D64: 69 C0    adc #$c0               add #$c0 (add 192) since sin A is cos(A-0x40)=(A+0x100-0x40), addition wraps around 0-255
0D66: A8       tay                    put our new angle (+192) back into Y
0D67: B9 00 0F lda $0f00, y           get the value from the cos table
0D6A: CA       dex
0D6B: 86 01    stx $01
0D6D: 4A       lsr a
0D6E: 85 02    sta $02
0D70: A9 00    lda #$00
0D72: 90 02    bcc $0d76
0D74: 65 01    adc $01
0D76: 6A       ror a
0D77: 66 02    ror $02
0D79: 90 02    bcc $0d7d
0D7B: 65 01    adc $01
0D7D: 6A       ror a
0D7E: 66 02    ror $02
0D80: 90 02    bcc $0d84
0D82: 65 01    adc $01
0D84: 6A       ror a
0D85: 66 02    ror $02
0D87: 90 02    bcc $0d8b
0D89: 65 01    adc $01
0D8B: 6A       ror a
0D8C: 66 02    ror $02
0D8E: 90 02    bcc $0d92
0D90: 65 01    adc $01
0D92: 6A       ror a
0D93: 66 02    ror $02
0D95: 90 02    bcc $0d99
0D97: 65 01    adc $01
0D99: 6A       ror a
0D9A: 66 02    ror $02
0D9C: 90 02    bcc $0da0
0D9E: 65 01    adc $01
0DA0: 6A       ror a
0DA1: 66 02    ror $02
0DA3: A2 00    ldx #$00
0DA5: 90 07    bcc $0dae
0DA7: 49 FF    eor #$ff
0DA9: 69 00    adc #$00
0DAB: F0 01    beq $0dae
0DAD: CA       dex
0DAE: 85 24    sta $24
0DB0: 86 25    stx $25
0DB2: 60       rts
0DB3: 98       tya             we copied the angle to the Y register before, so copy angle back to A
0DB4: D0 0D    bne $0dc3
0DB6: A2 00    ldx #$00        angle = 0 or #$00, cos = 1, sin = 0
0DB8: 86 24    stx $24
0DBA: 86 25    stx $25
0DBC: A5 00    lda $00
0DBE: 85 22    sta $22
0DC0: 86 23    stx $23
0DC2: 60       rts
0DC3: 30 0D    bmi $0dd2
0DC5: A2 00    ldx #$00        angle = 90 or #$40, cos = 0, sin = 1
0DC7: 86 22    stx $22
0DC9: 86 23    stx $23
0DCB: A5 00    lda $00
0DCD: 85 24    sta $24
0DCF: 86 25    stx $25
0DD1: 60       rts
0DD2: 29 40    and #$40
0DD4: D0 10    bne $0de6
0DD6: A2 00    ldx #$00        angle = 180 or #$80, cos = -1, sin = 0
0DD8: 86 24    stx $24
0DDA: 86 25    stx $25
0DDC: 38       sec
0DDD: 8A       txa
0DDE: E5 00    sbc $00
0DE0: 85 22    sta $22
0DE2: CA       dex
0DE3: 86 23    stx $23
0DE5: 60       rts
0DE6: A2 00    ldx #$00         angle = 270 or #$C0, cos = 0, sin = -1
0DE8: 86 22    stx $22
0DEA: 86 23    stx $23
0DEC: 38       sec
0DED: 8A       txa
0DEE: E5 00    sbc $00
0DF0: 85 24    sta $24
0DF2: CA       dex
0DF3: 86 25    stx $25
0DF5: 60       rts

Thursday, January 24, 2019

Comparing the cosine table to math.cos

Let's see how closely Stellar 7's cosine table compares to math.cos().






There's some interesting patterns in the difference between the cosine table and math.cos. It's never more than 1/128 or about +/- 1% difference. The horizontal lines are the range -(1/128) to +(1/128).

I wonder how this compares to Ulugh Beg's cos table.

Wednesday, January 23, 2019

Finding Stellar 7's cos table

I really love the mame debugger. I've got a new project: to figure out how Stellar 7's code works. It's such a genius game, I want to know how it was done.

So how does it do the math?

It stores the cos table in 0xF00 to 0xFFF, where the angle goes from 0 to 255. The high bit of the entry is the sign bit and the cos value is the low 7 bits. A value of 127 represents a value of 1.0, and a value of 0 represents 0.0. 128 represents the value of -0. 255 represents a value of -1.0.

In the next installment I'll show how it does the multiplication using the table.








To display the cos table, let's do a little bit of lua to render it to the apple's screen memory. If you've got the debugger open, just single step to get the screen to update.



Saturday, January 12, 2019

Stellar 7 cheats



Apple 2 version

81fc = shield

b@81fc=92 max shield (don't go above 92 or will overwrite the boundary)

81fe = fuel

b@81fe = 92 max fuel (don't go above 92 or will overwrite the boundary)


Playing Stellar7 with mame at double speed:

./mame64 apple2p stellar7 -speed 2.0 -frameskip 6

Thursday, January 10, 2019

Sirius Joyport and mame's apple2 driver

So I was reading up on the Sirius Joyport which was a box to convert Atari Joysticks to the Apple 2 Gameport and thought that it wouldn't be hard to add to mame's apple2 driver. All it really does is put an atari joystick on Pushbuttons 0,1 and 2 and selecting the button to read with annunciator #1.

https://www.atarimagazines.com/cva/v1n1/joysticks.php

or if you want the original source of the article:

https://archive.org/details/Video_Arcade_Games_Vol_1_No_1_1983-09_Creative_Computing_US/page/n101



It's a bit of a hack, but it's more of a proof of concept than anything else. You can switch the gameport from normal to sirius joyport on the fly which is useful.







Boulderdash with the Startup Screen where you can select the Joyport by pressing J.



Stellar 7 controls screen, you can select the Joyport with CTRL+A.




The games that I've found to work with the Sirius Joyport are:

Berzap!
Borg
Wavy Navy
Seadragon
Boulder Dash 1, Boulder Dash 2
Dino Eggs
Bouncing Kamungas
Lunar Leeper
Roundabout
Pest Patrol
Jawbreaker II
Vindicators (CTRL+M)
Miner 2049er, Miner II
Stellar 7
Outpost
Fly Wars
Lemmings
Plasmania


Stellar 7 really gets fast and furious with ramping the speed up to 1.5 or 2.0. What a superb game.

./mame64 apple2p stellar7 -speed 2.0

I can only finish the game with save states! Shift+F7 and F7 are your friend! I think you have to hit Gir Draxon 6 times, and he's got triple shots, super speed and invisibility.






I actually prefer boulderdash with the speed a little less at 0.8 or 0.9 making Rockford easier to control.

Monday, January 7, 2019

Miner 2049er II for apple 2


I got inspired to play miner 2 after watching some playthrough videos on youtube. I think I understand why I never was able to finish miner 2049er back in the day, it was just too tedious and unforgiving. It's a heck of a lot easier with savestates. I consider finishing one level an achivement.

Once you complete the 10 screens, it becomes near impossible with the stupid bouncy ball showing up unexpectedly.



Here's some cheats in the mame debugger:


set your score (you only see 7 digits)

d@080c=87654321

set the amount of time remaining, here it will be 99 minutes 60 seconds

w@0808=9960

If you set it too high, when you finish the level it can take a long time to count down, so just do

w@0808=0100

number of blocks remaining to cover, set it to zero to finish the level

b@080a=00

number of lives, don't set above 21 (0x15) or the hats will cover up the screen

b@081c=15




./mame64 apple2e -flop1 "~/Downloads/Miner 2049er II (4am crack).zip/Miner 2049er II (4am crack)/Miner 2049er II (4am crack).dsk" -window -confirm_quit -debug


There's a couple of interesting things I discovered while using the mame debugger.

I tried to disable a watchpoint numbered 0xA with wpdisable A but it didn't work.

What was happening is that it was interpreting A to mean the current value of the accumulator.

To workaround this, just do wpdisable 0xA.


If you want to flip to the hi res page 2 manually, do

b!c055=1

And to flip back to hi res page 1, do

b!c054=1

and here's a lua function to calculate the beginning address of each line of the hi-res screen: It's useful when you want to set a watchpoint at a specific point on screen.

function calc(line) return line % 8 * 1024 + math.floor(line / 64) * 40 + math.floor((line%64) / 8) * 128 +8192 end

for i = 0,191 do print(i,string.format("%x",calc(i))) end

and here's an example of writing directly to the apple's memory from lua:

cpu = manager:machine().devices[":maincpu"];mem = cpu.spaces["program"]
for i = 0,191 do mem:write_u8(calc(i),i) end