6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Wed Nov 13, 2024 6:32 am

All times are UTC




Post new topic Reply to topic  [ 98 posts ]  Go to page Previous  1 ... 3, 4, 5, 6, 7  Next
Author Message
PostPosted: Fri Mar 22, 2019 7:44 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Interesting to see LDA/PHA/LDA/PHA/RTS used here, at $51C4. A more compact alternative might be:
Code:
JSR readNextChar   ; just to increment the pointer to an actual instruction
JMP ($0022)   ; indirect through the pointer
…which takes 6 bytes instead of 7, not a big difference, and the readNextChar routine isn't very fast. But it does save a bit of explicit stack manipulation.


Top
 Profile  
Reply with quote  
PostPosted: Fri Mar 22, 2019 5:10 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
BigEd wrote:
A milestone - well done!

Thank you, BigEd, your encouragements are very motivating!


Top
 Profile  
Reply with quote  
PostPosted: Fri Mar 22, 2019 5:15 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
Quote:
As a port, hopefully it'll not be super tied into any hardware specifics, though it doesn't look like the Apple II has too many hardware specifics to begin with. ;)

Thanks for the feedback, White Flame. Yes, the Apple II is really a clean platform, perfect for learning 6502. The one thing which I haven't cared about at all yet is the speaker. That's really so basic that it hurts :)


Top
 Profile  
Reply with quote  
PostPosted: Fri Mar 22, 2019 5:16 pm 
Offline

Joined: Sat Dec 13, 2003 3:37 pm
Posts: 1004
fschuhi wrote:
The one thing which I haven't cared about at all yet is the speaker. That's really so basic that it Hz :)


Fixed that for ya!
:)


Top
 Profile  
Reply with quote  
PostPosted: Fri Mar 22, 2019 5:18 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
Chromatix wrote:
Interesting to see LDA/PHA/LDA/PHA/RTS used here, at $51C4. A more compact alternative might be:
Code:
JSR readNextChar   ; just to increment the pointer to an actual instruction
JMP ($0022)   ; indirect through the pointer
…which takes 6 bytes instead of 7, not a big difference, and the readNextChar routine isn't very fast. But it does save a bit of explicit stack manipulation.

There is not a single indirect jump in the whole Robotron code. I wonder why, seeing how elegant it can be used. Do you use indirect JMP regularly?


Top
 Profile  
Reply with quote  
PostPosted: Fri Mar 22, 2019 5:20 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
whartung wrote:
fschuhi wrote:
The one thing which I haven't cared about at all yet is the speaker. That's really so basic that it Hz :)


Fixed that for ya!
:)

:lol: :lol: :lol:


Top
 Profile  
Reply with quote  
PostPosted: Sat Mar 23, 2019 4:55 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
It is one of the more obscure addressing modes (absolute indirect), as on the NMOS 6502 it's *only* available for JMP. But it's a central part of the Acorn MOS, as each entry point (eg. JSR $FFEE) is often just a JMP ($02xx) through the patch table. It just struck me as the natural way to implement a return from an inline-data subroutine.


Top
 Profile  
Reply with quote  
PostPosted: Mon Apr 01, 2019 3:20 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
Robotron has a great gameplay. It's getting crowded pretty quickly - that's a strength, but it has a steep learning curve. There are distinct types of levels (called "waves" in Robotron), regarding the type of robot enemies you have to battle. The waves get increasingly difficult, fast. In the "Tank Wave" (first occuring as #7) there are Quarks pulsing around and spanning multiple tanks, each shooting bouncing shells https://youtu.be/t7_hvzIaqxk?t=134, that's a good player, still struggling through #7.

Problem: It takes time to get to this level - - no savepoints, no way to train a particular wave. I therefore inserted a screen to the configuration menu where you can select the wave you want to start with:
Attachment:
2.jpg
2.jpg [ 29.09 KiB | Viewed 1735 times ]


The game continues to the controls menu:
Attachment:
3.jpg
3.jpg [ 48.52 KiB | Viewed 1735 times ]


The game then starts with the Tank Wave, score is 0:
Attachment:
4.jpg
4.jpg [ 49.57 KiB | Viewed 1735 times ]


Robotron cycles through the types while throwing in some additional robot types and generally ramping up the total number of enemies. Here is Tank Wave #27, with two additional Spheriods lurking in the corners:
Attachment:
6.jpg
6.jpg [ 58.56 KiB | Viewed 1735 times ]


Top
 Profile  
Reply with quote  
PostPosted: Mon Apr 01, 2019 3:39 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
I wanted to enter two-digit wave numbers, with backspace, return and escape, handle special cases like "00". It's implemented as a statemachine using an RTS jump table as described in https://wiki.nesdev.com/w/index.php/RTS_Trick. Apart from the special characters, only numbers are allowed. The tutorial http://6502.org/tutorials/compare_instructions.html was very helpful to get the constraint right (I hope).

Code:
next_char:                      ; main loop
      JSR show_digits
      JSR   waitKbdControls

      CMP ch_escape           ; <Escape> breaks
      BEQ break

      CMP ch_return           ; <Return> is a valid char, concludes input (if possible)
      BEQ .send_event

      CMP ch_left             ; <Left> removes last digit
      BEQ .send_event

      check_is_number         ; only chars <0> to <9> allowed
      BCC next_char           ; carry cleared if not in <0> to <9>

   .send_event:
      STA event               ; dispatch event
      JSR dispatch_event

      LDA state               ; check if we should exit, i.e. user has entered a number
      CMP s_exit
      BEQ exit

      JMP next_char

dispatch_event:
      LDA state               ; state index into RTS table
      ASL                     ; table has 16bit addresses
      TAX
      LDA rts_table+1,X       ; push hibyte first
      PHA
      LDA rts_table,X         ; push lobyte second, will be popped first by RTS
      PHA
      LDA event               ; state subroutines get the event in A
      RTS

state_hasNone:
      ...
      RTS

state_hasOne:
      ...
      RTS
      
state_hasTwo:
      ...
      RTS
      
state_exit:
      ...
      RTS
      
rts_table:
      .word   state_hasNone-1
      .word   state_hasOne-1
      .word   state_hasTwo-1
      .word   state_exit-1


Attachments:
hook.asm [5.37 KiB]
Downloaded 68 times
Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 02, 2019 3:03 am 
Offline
User avatar

Joined: Sun Jun 30, 2013 10:26 pm
Posts: 1949
Location: Sacramento, CA, USA
I can see the 1 MHz Apple ][ start to "run out of breath" on some of the busier screens, but it still impresses me that games like this can remain playable and entertaining on hardware with "unassisted" graphics, one-bit sound, '555 A to D, and no interrupts. The "centipede levels" in Bandits were especially marvelous to me. Benny Ngo was (is?) quite the 6502 virtuoso.

_________________
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 02, 2019 7:02 am 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
Yes, both Robotron and Bandits have aged very well, IMHO. Apart from the fun gameplay, Bandits is also a formidable piece of software, given what Benny Ngo was able to cram into 6kb.


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 02, 2019 9:48 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
I have started to get a closer look at what's happening in terms of reads from and writes to memory. The problem is of course not collecting the info but rather condensing it sensibly. I first tried to group the access in pages, for the different subroutines as suggested by sark02 earlier in this thread. The info is of course helpful but only on a per-subroutine basis. Given the overwhelming number of instructions (and even subroutines) in Robotron, I decided to try to see the bigger picture first, on a per-byte basis.

It's work in progress so it's a bit premature to share it here, but maybe you guys have feedback to offer on how to progress from here. That's the reads from a couple of seconds of the intro (up to $CFFF, i.e. including the softswitches but excluding the higher parts of the ROM):
Attachment:
reads small.jpg
reads small.jpg [ 19.2 KiB | Viewed 1669 times ]

That's really the big picture - - a heatmap with all reads, each line of blocks a page. I can zoom in:
Attachment:
reads top.jpg
reads top.jpg [ 32.84 KiB | Viewed 1669 times ]

Address and access count of the selected block is shown in the status bar:
Attachment:
reads bottom.jpg
reads bottom.jpg [ 11.33 KiB | Viewed 1669 times ]

Beginning $2000 is the memory which is mapped to the screen. I had first assumed that I should not see any reads here, only writes, but you can see the effect of the code updating "sprites" in memory. The algorithm obviously needs to know what's in a particular area of the screen to remove and display pixels correctly. Related to displaying graphics are the pages $1200 and $1300. Access to the pages have the same color (i.e. exactly the same counts in both pages for each byte). These are the lobyte/hibyte lookup tables to translate logical pixel line into the where those lines physically reside in memory.

Here the writes which clearly show the hires memory:
Attachment:
writes small.jpg
writes small.jpg [ 15.01 KiB | Viewed 1669 times ]

There are also some lone hot spots below the hires memory. That's where the code resides, so those bytes are modified by and in the code. The two consecutive ones on the left are part of the printChar subroutine explained in one of my earlier posts.

At last the counts for particular program counters for read and write instructions (hotter colors = instruction was executed more often):
Attachment:
PCs small.jpg
PCs small.jpg [ 11 KiB | Viewed 1669 times ]

The workbench is reasonably fast enough to allow "slicing" the total dataset into smaller pictures = containing less cycles. I should thus be able to drill down to seeing a particular sprite drawn on the screen (write) and relate the bitmap to it's origin (read).


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 02, 2019 10:25 pm 
Offline

Joined: Sat Dec 13, 2003 3:37 pm
Posts: 1004
The heat map of memory access is a pretty neat feature.


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 30, 2019 3:05 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
After some time away I've been able to spend some more hours on the reverse engineering project.

The Apple II is severly constrained in terms of memory. Consequently, Robotron uses for the "cut scenes" the actual game code to display elements like the player character or enemies. From a reverse engineering point of view this is fortunate, because I can use the uncluttered (and even scripted) movements of elements on the start screens to understand how the game moves "sprites" around the screen. For an example see the movements of the player character on the screen explaining the controls.

I've extended the read/write tracking of the workbench from my last post to project what's happening in memory onto the simulated screen. In the following pic you can see the "echo" of the beginning of the movement of the player character on the controls screen (i.e. what you see in the Youtube video, the character going from left to right first.)
Attachment:
control_move.jpg
control_move.jpg [ 19.69 KiB | Viewed 1542 times ]

Following the strategy suggested in my last post....
Quote:
The workbench is reasonably fast enough to allow "slicing" the total dataset into smaller pictures = containing less cycles. I should thus be able to drill down to seeing a particular sprite drawn on the screen (write) and relate the bitmap to it's origin (read).

... I can visually do a trial-and-error to constrain the shown range of cycle counts (which I use as global clock) until I only see one particular "frame" of the movement:
Attachment:
control_single.jpg
control_single.jpg [ 3.78 KiB | Viewed 1542 times ]

The workbench can now show me the corresponding reads from memory:
Attachment:
control_reads.jpg
control_reads.jpg [ 5.17 KiB | Viewed 1542 times ]

The area between the horizontal lines is the video memory. The colors on the zero page (first line) show that there is some heavy indirect reading involved.
Looking at which code was executed between the shown cycle counts is of course necessary to understand what e.g. the memory related to the two green lines on the lower left contain: It's the actual sprite data, both for removing the first shown player character ("facing the camera")...
Attachment:
man1.jpg
man1.jpg [ 9.57 KiB | Viewed 1542 times ]

... as well as one frame of the player character in movement to the right:
Attachment:
man2.jpg
man2.jpg [ 9.39 KiB | Viewed 1542 times ]

From inspecting the code and what's in memory I've gleaned some first information about how the sprites are stored in memory (resulting in the crude X. displays above), in particular how multiple sprites are stored in order to make it easy to horizontally move the sprite pixel-wise through the bits in screen memory.

This part of Robotron is quite central to the overall gameplay. The code takes generalised sprites (i.e. different heights, widths and "facings") and projects them onto the screen, inside bytes and scattered across the video memory (due to Apple II ideosyncrasies). In addition, the code needs to track the state of the sprite, to cycle through e.g. the left+right movements of the legs (or pulsing shapes etc.), which also needs to take into account the direction of movement. The algorithm is much more involved than I had originally thought it would be. A nice challenge! :)


Top
 Profile  
Reply with quote  
PostPosted: Tue Apr 30, 2019 3:50 pm 
Offline
User avatar

Joined: Sat Feb 23, 2019 9:11 am
Posts: 38
Having a good idea about how something works is still far from really knowing the details of how this is accomplished (oh yeah, Captain Obvious, tell us more ;) ). The "sprite projector" hinted at in the post above can serve as a good training ground to learn to tear into asm code.

Sadly (or interestingly, actually), my usual debugging skills can not so skillfully employed in the 6502 / Apple II arena. For the Apple II there are of course debuggers with single-stepping and breakpoints, but I think at this point of the learning curve I need more of an "explorer" than a "debugger".

My workbench has the ability to track all memory reads and writes, both absolute and indirect. That's e.g. what's behind the heat maps and the "screen echos" shown in my last two posts. For each read/write I track the cycle count of that operation, together with its PC. For the writes I store both the old value before the operation and the new value stored by the write. Although I do not yet log AXY (and SP only indirectly via the activity on page $01), some good understanding can be derived from relating changes in memory with the flow of instructions.
Attachment:
memlog.jpg
memlog.jpg [ 59.15 KiB | Viewed 1536 times ]

The pic above shows a part of the "sprite projector" and the memory log, positioned at a particular cycle and PC. I can move upwards and downwards in the MemLog window. This automatically synchronises with the asm code in the main window. I can also search forward and backward to the same PC, to compare changes in memory between different runs through the same stretches of code. I can also go in the main asm window to any PC and synchronise the MemLog to the first (or nth, forward and backward) occurance.

It's a bit like time travel debugging. For fully fledged time travel I would also have to store all changes to the registers, but that hasn't been necessary so far. Given the importance of the registers for, well, everything, that might come as a surprise. But because registers have to be loaded from somewhere and because this somewhere is usually not far from the point of interest, it's actually quite easy to trace the flow of data both through the memory and also through the registers. I'll see how far I get with that approach.


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 98 posts ]  Go to page Previous  1 ... 3, 4, 5, 6, 7  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: