Jump to content

NoLand

New Members
  • Content Count

    57
  • Joined

  • Last visited

Posts posted by NoLand


  1. Very nice!

    Minor idea: Have a slightly different sound for an attempt to move the cursor off the board. (This is just one of those tiny things the "brain" tends to like.)

    However, evaluating the game over state is probably of higher priority – and quite a challenge, given the limited amount of memory. :-)

    • Like 1

  2. Maybe worth mentioning: The TIA is all you have (apart from the RIOT chip for a few things like timers and reading input), it's all there is. It's your only friend. Don't rush things, understand them. The TIA is much like an operating system on modern computers, strobing and setting registers is like calling OS routines. However, the TIA is also a rather tacit friend, it won't talk back regarding any errors (unlike an OS). Instead, there's immediate visual feedback. Part of the art is also, guessing from the visual feedback what may have gone wrong.

     

    Some of this may seem tedious, but it's really a quite ingenious design, given the (cost) constraints. Part of these constraints is (a) the TIA "knows" nothing about vertical images, it just does scanlines, over and over again, as currently configured. Hence, we're counting scanlines, etc. (b) It uses linear feedback shift registers internally for counting, rather than counting by ordinary numbers. These shift registers progress by unique values, like normal counters, but non-sequentially, out of sequence. This is why we have to communicate horizontal sprite positions by timing. This is too coarse for an exact position at most of the pixels, but there are also HMOVE and the corresponding horizontal movement registers. These can be used either for moving an object by a certain offset to the left or right, or for fine positioning. This is, where the more arcane code, like the one in the Battlezone routine comes to help. Thankfully, this has been figured out long ago…

     

    Combining all those things in the tiny amount of time provided by a single scanline (often, we want most of this already be set in HBLANK, which is just 22 CPU cycles) in the right order is the high art. (This is, what caught me somewhat by surprise: reading it up, it seems just simple, there are that and that constraints and that and that recipes to do it, fair enough. But then you realize, you must be ready by the end of HBLANK, if you want to show a sprite at the left side of the screen. Maybe, you'll have to tweek your game design, maybe you'll have to become inventive with your code. Maybe, reordering code helps. Again, making copied code your own by thoroughly understanding it helps enormously.) Maybe start with looser constraints, i.e. a two-line kernel, which provides you double the time at the cost of vertical resolution.


  3. Andrew Davie's advice is probably the best one: start simple, understand each of the steps, and progress.

    Take a very simple kernel, e.g., the one attached here, understand what it is doing.

    The one suggested here uses timers for VBLANK and overscan, so you've only to worry about counting the visible kernel scanlines.

    Also, it can be configured for NTSC and PAL (already preconfigured), showing that there really isn't much difference (it's just about the extent of the three parts, VBLANK, kernel, overscan).

     

    Try inserting code, start – as already suggested – by editing the kernel so that a full sprite (where ever it will appear on the screen) is displayed. Maybe try different patterns. Maybe, you'll want to try to move that band incrementally using HMOVE (insert this before the kernel once per frame, don't forget to rest HMOVE values). Then try to make the sprite just on a single scanline, or on a certain vertical position for a given height. Try changing the pattern for each of the scanlines it's visible. Use patterns stored in a table for a real 2D sprite.
    Finally, implement some code for horizontal positioning. (This is the most tricky part, since it can be done only by timing and the CPU is slower than the beam/TIA. So there'll has to be compensation for fine adjustment.)
     
    There are well known routines to achieve this, e.g., the "Skipdraw" routine for vertical drawing, or the Battlezone routine for horizontal positioning (in various flavors).
    Have a look at various tutorials or at http://www.qotile.net/minidig/tricks.html. Copy just the code you actual need and integrate it into what you already have. Understand it and make it your own!
     
    If you're more the reading type, read it up (again, see tutorials, etc), and, again, understand it and make it your own.
     
    So, here's a simple kernel to start with:

    playfieldstarter.asm


  4. Mind that you'll have to (a) write the first value/pattern of PF0 on the start of each scanline for the left side, then wait for PF0 having been actually displayed and then set the new value – and this for each line. Start counting your cycles at WSYNC.

     

    E.g.,

     

    sta WSYNC ;strobe WSYNC to be woke up at start of next scanline
    
    lda #%00010000 ;[3 cycles]
    sta PF0        ;[3 cycles, now at 6]
    
    lda #%01110000 ;[3 cycles, now at 9]
    sta PF1        ;[3 cycles, now at 12]
    
    lda #%11111100 ;[3 cycles, now at 15]
    sta PF2        ;[3 cycles, now at 18]
    
    lda #00010000  ;[3 cycles, now at 21] load right-side PF0
    
    ;now wait until PF0 has been displayed
    ;scanline display starts at the end of cycle 23
    ;+ 4 fat pixels = 4 * 4 / 3 (pixels per CPU cycle) = 5.333
    ;so we'll have to wait at least until cycle #28 has passed.
    ;on the other hand, we should be done before cycle #49,
    ;when PF0 is used by the TIA again for the right side.
    
    nop ;[2 cycles, now at 23]
    nop ;[2 cycles, now at 25]
    nop ;[2 cycles, now at 27]
    
    ;mind that the value will be actually set only at the end of
    ;the last cycle of the STA instruction, so we're good to go
    
    sta PF0 ;[3 cycles, now at 30]
    
    ; now wait for PF1 (compare the chart)
    ; and for PF2 ...

     

    • Like 1

  5. 3 hours ago, Dong said:

    So, there are some place to store the shape data of the invaders. Here the shape data means the pixels which record how the invader should look like in the screen.

    The shape data would be normally stored in ROM, of which there's plenty, comparatively speaking.

     

    For a bit of understanding of what the TIA does or doesn't: The TIA just assembles a horizontal line of TV image on the fly. For this, there are background graphics (20 bits of "wide" pixels, 4 times the size of a normal pixel, repeated or mirrored for 40 wide pixels in total), two player sprites, a missile (a single dot) for each player, and a ball (another pixel). Sprites may be scaled and player sprites may be also repeated at an offset, up to three times. You may see where this goes with Space Invaders, already, as there are 6 Invaders, which may be accomplished by using the two sprites with 3 copies for each of the two sprites. We just have to change the pattern for the sprites, just at the right time, which is the tricky portion. So, where does the pattern come from? You read it from ROM and put it in the respective register. And you have to do it for each line of the screen. Mind that the TIA does horizontal lines only and has no notion/concept of a vertical extent. If we do nothing on the next line, the image will be repeated over and over, resulting in vertical stripes or bands. So we have to provide for this in the program, in a section usually called "kernel" that controls the visual image. We'll have to count the rows, as we're passing over them, and we'll have to figure out, if a certain sprite is on for the given row/line of the TV image. This is done by keeping track by counters, which are also used to read the sprite data with an increasing (or much more often, decreasing offset, as we're usually counting down to zero from a set number of screen lines). Fortunately, the 6502 processor provides some handy instructions for this.

     

    (Mind that all invader sprites are positioned at the same horizontal offsets, meaning, we haven't to change the horizontal positions of the sprites. They are either off or displaying a certain pattern. However, down the image, it becomes more tricky, as we'd have to reposition the sprites for the barriers and the player sprite. The TIA has a really as basic as possible approach to this, where you basically say "now" to the chip, when the beam is it at the right position. This is, because the TIA doesn't implement numeric counters for determining positions, but so called linear feedback shift registers, which count in unique, but non-sequential digits. Oops. But this lessens the transistor count and therefore costs dramatically. Also, the CPU runs slower than the TIA, therefore, us saying "now" is a bit too coarse in granularity and we'll have to figure out a correcting offset. Fun! The tell-tale sign of this procedure are the black "combs" seen frequently on the very left edge of the screen, which are artifacts of this fine positioning.)

     

    BTW, the TIA, being the little box of wonders that it is, does sound, as well.

     

    3 hours ago, Dong said:

    Here, what your mean is that the TIA will have some registers to record the ovelap event between bullets and the invader. The programmer can get that infomation and then decide what to do to react to this ovelapping thing. 

     

    Am I correct? 

     

     

    Yes, this is actually quite simple. While the TIA is assembling the signal, which will be eventually sent to the TV, it's rather easy to determine, if another signal source is already set high/active. If this is the case, this will be stored in the respective collision register, of which there are 8 in total to represent any of the possible combinations.

     

    To tap into the TIA's "stream of thought", this may be well like, "Now on to the next pixel, which is pixel 67 – or 123, as I call it internally, – let's see, background is off, nothing to do, get the player sprite #0, oh, its position is well out of scope, never mind, then sprite #1, oh, it's active and, as I'm shifting the bits along, as well, it's an active bit. — Let's look up the color, oh, it's a bright red, put this on the output. Then, let's move to the first missile, oh, it's active as well, let's put this one on the output. Oh, there's already an active signal from sprite 1, let's take note of this…" – As it happens, each of the "oh"s can be implemented by a simple logical gate.

     

    (These registers share addresses with those registers used to set the state of the TIA. If you write to these addresses, you set the state, if you read from them, you get a readout of the registers storing collisions or the state of the controllers. Again, this is for cost reduction, since, this way, we can save some cents on additional pins on the chip. Which is the logic behind most of the VCS/Atari 2600: Why spend money on each of the millions consoles shipped to users, if a few extra hours on the programmer's side may do the same? Still, development and production of the VCS was that expensive that Atari had to be sold to Time Warner in order to survive. It was pretty much on the verge of what was commercially viable at the time.)


  6. First, mind that this is a simplified example to illustrate the principle. In actuality, there are also explosions and maybe even explosion phases to take into account. So it will probably a byte for each invader to store an additional state and a counter.

     

    Regarding TIA collision registers, the TIA registers any of the possible combination of a set bit of any of the sprites overlapping and stores them in a few read-only registers. In theory, this would provide already all we need to determine a collision. (E.g., does the missile of the player overlap with the enemy sprite? Read the according register and check for the collision bit representing this exact condition.) However, here, we're repurposing sprites multiple times, so all the TIA could tell us is whether there was a collision at all and we would have to figure out on our own, which invader was hit, etc.

    • Like 1

  7. Mind that not every object on the screen has to have a representation of its own in the code!

    E.g., having a look at the Space Invaders screen in your example, there are 6 rows of 6 invaders each. Mind that the rows move in concordance as a single block. So, you actually only need the (x/y) offset of the block and a byte a row with bits set for an active invader. The rest can be done by simple math, using a few shifts and addition for comparing objects. (However, the TIA collision registers will do some of this for us already.) Of course, we'd need some additional code to handle an entire edge (to the left of right) of the invader block becoming free, meaning, the block wouldn't reverse when reaching x=0 zero on the left side, but at x=16, where the second invader is located, etc. So we'd need another byte for an offset to add to this on the left and another one to be subtracted on the right in order to handle the desired reversal of direction properly. Considering it all, we'd need 6 bytes to store 6 bit-vectors for each of the rows of invaders, 2 bytes for the block coordinates and another two bytes for the left and right offsets. Putting this a step further, we could also store the latter in a single byte using a bit vector indicating offset shifts to be applied to the block coordinates, which would give us a storage requirement of just 9 bytes to handle 36 objects.

    • Like 1
    • Thanks 1

  8. 1 hour ago, Prince Manic said:

    My dad does own an Atari 130 XL/XE and I know that can run 2600 games without issue

    Please do not confuse "Atari 8-bit games", generally meaning Atari 400/800 and later siblings of that family, and Atari 2600 (VCS). These are not the same platform, they are actually incompatible. However, a controller for the Atari 8-bit family should work with the Atari 2600.

    Instead of buying a dedicated USB joystick, you could also get one of the various Atari joystick port (DB-9) to USB adapters and use an original or compatible one. You could even build your own using the "zero delay" USB interface used by many arcade gamers (you can find these tiny boards quite cheap at ebay, Amazon, etc.) I believe, there are several tutorial videos on how to do this on YouTube, as this has gained some popularity with the New C64.


  9. So ... we now support byte orders either per row (as before) or by labeled arrays per playfield register, both for import and for export.

     

    (Reload) https://www.masswerk.at/vcs-tools/TinyPlayfieldEditor/

     

    Not so sure about color. Setting color codes for up to 228 rows each (PAL standard), appears to be pretty exhaustive.

    I think, this may be best left to manual editing.

    (Even, if we changed the colors for any consecutive rows below as well, there's no reasonable relation of effort and achievement, both for the user and on the programming side of things.)


  10. 39 minutes ago, splendidnut said:

    was thinking there can be controls on the side of the import box with drop down selections.  When the user pastes into the import box, the labels are detected and the user has to select the mappings.  Something like:

     

    Map to PF0

      ( dropdown with labels )    <-- list of labels includes (none)

     

    Map to PF1

      ( dropdown with labels )

     

    ...

    This seems to be a viable path to go. (Alps or not. ;-) ) We may also try to preselect those semi-intelligently. (E.g., try to identify color, which is probably either the first or the last stream, assume any others are in sequence, only try to identify those, if one is missing.)

    Something I'd rather want to avoid is an import Wizard, like those found in Excel and similar applications. Those are generally a pain to work with and do rarely what's expected. (So you need to add a preview for the user to check the results and to evaluate consequences of any adjustments, etc, which is somewhat radically opposed to the idea of keeping things simple.)

     

    I think, regarding export, it's easy enough to edit the labels when pasting the code into the source and we may rather go with generic ones (like "DataPF1"). [Edit: If there was a previous import, we may, of course, reuse those labels.]

    Regarding output ordered by rows (as-is), we could go with options to put the color either first or last (always to be exported as a hex number).

     

    For a bit of background, this really started as a bigger version of the "Tiny Sprite Editor", which I found personally quite useful. Color didn't matter for sprites that much, it's probably in a separate stream and easy enough to edit manually. Most of the effort for the playfield editor went into handling the various playfield modes and doing the required conversions on the fly. (And also, to do it in a way, which allows to switch formats on the fly without losing data and still keeping things internally somewhat simple – read, reasonably responsive.) Color however was still assumed to be handled best separately. But I can see that it does matter for playfields.

     

    Regarding Batari Basic, is there a comprehensive documentation on code formats?

    • Like 1

  11. In honor of your mountain theme and to quote Kant, "where others may see flat footpaths, I do see Alps" (or whatever is the common English translation). ;-)

     

    How may we generalize on this? (E.g., there are just 3 streams, but one label includes "color" – easy enough to recognize by a regular expression –, so one stream is missing. Assume it's all zeros? Assume it's probably PF0, or do further semantic analysis on the labels?)

     

    Edit: Is it a robust assumption that every label for playfield data will include either "PF0", "PF1", or "PF2"? Can we make this a requirement? Or, at least, a viable fallback? (E.g., what about multiple screens and there's a label like "PF2_1"?)

    Again, suggestions, opinions, etc, are welcome…

     

    P.S.: I've no experience with Batari BASIC and I'm not necessarily inclined to install it. What are the formats used?

    • Like 1

  12. 3 hours ago, splendidnut said:

    Add option to output code into separate playfield arrays.  i.e.  Values for PF0, then values for PF1... etc.

    This may be interesting!

    However, it comes with a few complications, since as-is the editor takes just any stream of bytes in whatever notation as an input. With alternative sequences things are not that clear and we've got to come up with an arbitrary convention.

    Any suggestions? (E.g., just any label, or any including the string "PF", as a prefix to a stream of bytes?)

     

    Edit: The same is also true for color.

    E.g., inside the editor, we could set row colors by presenting a color picker on a right click.

    But, are there any conventional code formats for export and import? (Prefix the color as the first item? Have a separate label and stream for colors? Accept any label starting with "C" as a stream of color codes?)

     

    Any suggestions are welcome!

     

    The principal idea is to facilitate working directly with assembler code. (Which may be of use for interpreting any existing playfield definitions, e.g., for disassemblies, mods, etc, as well.) So what are common formats?


  13. Small update: The principal settings, like symmetry, repeat and line-height are now annotated in an additional comment in the assembler code generated by the editor, which is also used for adjusting these settings on data import.

     

    (These settings may appear in any order in a comment starting with "mode", using any kind of separator.)

    • Like 1

  14. 3 hours ago, Keatah said:

    What about 6502C "Sally"?

    The 6502C Sally used in the Atari 8-bits has an additional HALT signal (active LOW) on pin 35, which facilitates waiting for the next scanline by strobing WSYNC, just as on the VCS. Moreover, there's a duplicate R/W signal on pin 36. (Both pins, 35 and 36, are not connected on the stock 6502 and reserved for "future expansions".)

     

    6502C-Sally.png

    • Thanks 1

  15. 1 hour ago, MLdB said:

    this is just a regular button.

    To illustrate this, I once made a tiny sound application and was in need for additional controls for the volume – and used the select and reset button for this. (After all, these are nice, non-latching buttons, side by side, facing the user.) There is really nothing special about the console buttons, they have no immediate effect on the hardware, other than that the state of the button is stored in register SWCHB of the RIOT chip, where a program can read them. The meaning of these buttons is also just by convention, as indicated by the imprint. (However, there's nothing stopping you from abusing them for other purposes.)

     

    Usually, a program checks the state of the reset button once per frame and takes a jump to the initialization routine (the cheapest option), if the button is found pressed. However, the program may also choose to continue at any other point, e.g., a bit further "down" in the code, where the counter for the music has already been set up, meaning, the music will continue to play seamlessly.

    • Thanks 1

  16. Maybe a helpful link: http://www.6502.org/tutorials/compare_instructions.html

     

    Especially:

     

    Compare Result       N  Z  C
    A, X, or Y < Memory  *  0  0
    A, X, or Y = Memory  0  1  1
    A, X, or Y > Memory  *  0  1
    
    * The N flag will be bit 7 of A, X, or Y - Memory

    Meaning, you can do greater or equal than (>=) and less or equal than (<=) by checking two flags. Sometimes, it's even easier than that:

    Provided that a comparison is much like a subtraction but without putting a result into that register, you can do min/max by two consecutive comparisons, since, if a > 2  and a < 4, a - 2 will be positive (carry set, zero flag clear), but a - 4 will be negative (carry and zero flag clear/unset).

     

    cmp #min
    bcc skipToLess      ; a < min (carry is only unset, if register is less than the reference value)
    cmp #max
    bcs skipToGreater   ; a >= max (if greater or equal, carry is set)
    ; #min <= a < #max, if we arrive here

     

    • Like 1

  17. 27 minutes ago, Karl G said:

    Playing with it for a bit, the only thing I was wishing I had was some kind of line marker or shading to show where the PF regions begin/end.

    Actually, there are, but they are rather subtle. You probably have to know that they are there, in order to notice them. They also adjust to the kind of playfield repeat on the right side. (I found it hard to decide, if they were more distracting than useful, if more accentuated.)

     

    Edit: It's a bit like with the mash lines on the Trinitron monitors, once you've seen them, you can't unsee the PF-register dividers.


  18. I've devised a tiny playfield editor, which should support most needs. It supports symmetric and asymmetric playfields, repeated and mirrored mode (for both types) and generates assembler code in realtime. Correspondingly, the editor takes assembler code or simple lists of byte values as an input. Moreover, you may adjust the total number of scanlines for the kernel and the vertical resolution. The Interface is intentionally kept simple (or, at least, meant to be so). Mind that there's no support for color codes or row-repeat counts, confidently assuming you may be able to add them on your own. (Also, there isn't really a standard format for this.)

     

    https://www.masswerk.at/vcs-tools/TinyPlayfieldEditor/

     

    I also collected a few other tools for player graphics / sprites and sound under a common landing page: https://www.masswerk.at/vcs-tools/

     

    Have fun!

     

     

    Update: (Apr. 27, 2020)

    The editor now supports code ordered by rows (PF0, PF1, PF2 alternating) as well ass code ordered by PF-registers (one array per PF register), both for import and the realtime generated code output.

     

    On import, any consecutive stream of bytes (either bare values or assembler constructs like ".byte") will be interpreted as ordered by rows (alternating values for each of the PF-registers, as in "PF0, PF1, PF2, PF0, PF1,..."). If there are, however, separating labels found (anything beginning with a letter character or underscore and continuing with letters, numbers, or underscores at the start of a line) the imported code will be considered to be ordered by PF-register. In this case, an additional prompt will ask you to map the labels to the respective registers (defaulting to the order found in the imported code — no attempt is made to interpret the semantics of labels). The pop-up menus used for this also include an option to override the data and set all values to zero (which may be useful, if there are less than three arrays of PF-data or one of them is meant for color codes). Moreover, the line-height will be automatically adjusted to the length of the imported data stream(s).

     

    Automatic code generation defaults to ordered by rows, but may be switched anytime. (Mind that automatic generated comments with picture strings and row numbers are only available for the ordered-by-rows mode, while any output ordered by PF registers uses a more compact format of 8 bytes per line.) The output format also adjusts automatically to the last import format used, reusing any labels found in the imported code. Labels will be reset to generic ones, if you clear the playfield editor.

    As before, the byte order of the generated code may be switched any time. (You may also chose to import any code either in the existing order, or to import data bottom-up. This state will be honored as a default in the generated code output.)

     

    By this, the editor may be also a viable tool for interpreting playfield data or even for converting code formats.

     

    While this may sound a bit complex or even complicated, it really just describes a behavior, which should effortless adjust to your needs. :-)

    • Like 4

  19. 22 hours ago, SpiceWare said:

    Most likely Javatari is not emulating mid-scanline changes to VBLANK correctly.

    I don't want to diverge too much from the original topic, but does a mid-scanline start mean that the program is displaying even fields on a TV set (instead of the usual odd-only ones)? As I understand it, a mid-scanline start is what triggers the scanline drop for an even field.

     

    4 hours ago, Yoruk said:

    I know that it's better to start my look with X = 192 and decrease the register. But as you can see I have a very heavy loop with too much comparisons, this is what I need to optimize.

    Mind that because of how indirect addressing with pointers works on the 6502, it's generally preferable to use the Y-register for counting lines. Decrementing the index register is also mostly preferable (you save at least one comparison instruction in your kernel, and time is precious). On the other hand, all your graphics are to be stored in reverse order (upside-down) im memory for the purpose, when using a decrementing index.

     

    I once made a tiny sprite editor (online), which allows you to import and export assembler code and to reverse the byte order, if required:

    https://www.masswerk.at/rc2018/04/TinySpriteEditor/

     

    (If there's interest, I can do a similar playfield editor.)


  20. 3 hours ago, SpiceWare said:

    The loop is basically:

     

    Scanline 1: update playfield, update player

    Scanline 2: update player, calculate values for playfield - but save results for next scanline

    How could I miss this? (Tunnel vision regarding the playfield?) Two WSYNCs and two occurrences of "lda (SpritePtr),y" for everyone to see in plain daylight…

     

    Of course, resetting or preloading the playfield before resetting VBLANK is best practice.

     

    So, the answer to the scanline / playfield question is:

    a) segment count x 2 as the kernel iterates over pairs of scanlines

    b) 2 extra scanlines at the top (repeated as-is, i.e. from the bottom), due to the SLEEP and the time spent until the next WSYNC

    c) 3 extra scanlines at the bottom (the kernel actually stops, where the background color is set to black, however, since the playfield registers remain unchanged, the pattern is repeated until the timer kicks in and the code sets VBLANK).

     

    Interestingly, Javatari (and by this 8bitworkshop) doesn't show the first scanline starting mid-screen:

     

    playfield.png


  21. 1 hour ago, SpiceWare said:

    I don't see why the total of the left column doesn't give me 196 or 196/2, but if I try to modify these values it does affect the pattern height.

    Here is what the actual effect of this is on the output:

    PlayfieldData
    	.byte  4,#%00000000,#%11111110,#%00110000
    	.byte  8,#%11000000,#%00000001,#%01001000
    	.byte 15,#%00100000,#%01111110,#%10000100
    	.byte 20,#%00010000,#%10000000,#%00010000
    	.byte 20,#%00010000,#%01100011,#%10011000
    	.byte 15,#%00100000,#%00001100,#%01000100
    	.byte  8,#%11000000,#%00110000,#%00110010
    	.byte  4,#%00000000,#%11000000,#%00001100
    	.byte 0
    
    ....XXXXXXX.....XX..   8 lines
    ..XX.......X...X..X.  16 lines
    .X...XXXXXX...X....X  30 lines
    X...X...........X...  40 lines
    X....XX...XX...XX..X  40 lines
    .X......XX....X...X.  30 lines
    ..XX..XX.....X..XX..  16 lines
    ....XX........XX....  11 lines + 2 lines at top
    
    sum: 193 lines

    However, I can't see, why the pattern is repeated over two scanlines, or, why there's this overspill to the top.

    (Mind that the sprite is rendered at scanline resolution.)

    ; Set up VBLANK timer
            lda #0
            sta PFIndex	; reset playfield offset
    
    NewPFSegment
    ; Load a new playfield segment.
    ; Defined by length and then the 3 PF registers.
    ; Length = 0 means stop
            ldy PFIndex	; load index into PF array
            lda (PFPtr),y	; load length of next segment
            beq NoMoreSegs	; == 0, we're done
            sta PFCount	; save for later
    ; Preload the PF0/PF1/PF2 registers for after WSYNC
            iny
            lda (PFPtr),y	; load PF0
            tax		; PF0 -> X
            iny
            lda (PFPtr),y	; load PF1
            sta Temp	; PF1 -> Temp
            iny
            lda (PFPtr),y	; load PF2
            iny
            sty PFIndex	; save index of next segment
            tay		; PF2 -> Y
    ; WSYNC, then store playfield registers
    ; and also the player 0 bitmap for line 2
    ...
    ; Load playfield length, we'll keep this in X for the loop
            ldx PFCount
    
    KernelLoop
    ...
            dex
            beq NewPFSegment	; end of this playfield segment?
    ; WSYNC and store values for second line
            sta WSYNC
    ...
            jmp KernelLoop

     

    Edit: The overspill is quite remarkable, since the code at label "NextFrame" continues immediately at "NewPFSegment" (meant to start the playfield pattern at the very first entry), after resetting VBLANK and  a "SLEEP 10", which shouldn't extend over two full scanlines.


  22. 2 hours ago, Yoruk said:

    To actually use the pointer, I must use this syntax : "lda (pointer),y"

    This is correct.

     

    A bit of background on this: While the 6502 has just 3 registers of use for a program, the accumulator (A), X, and Y, the zeropage features as some kind of extended range of registers, especially for instructions `LDA ($LL), Y` and `LDA ($LL, X)`, which have a bit of "magic", as in indirect lookups using an index, added to them. Best think of the parentheses "(…)" as "effectively the address contained in that expression" (or simply, as "the contents of"). The behavior is somewhat different for X and Y in this context

     

    `LDA ($LL, X)` adds (ignoring any carry) the offset immediately to the address $LL and will do the actual lookup at the address contained in ($LL + X, $LL + X + 1). So, if $LL = 4 and X = 2, the value loaded will be the address contained in memory locations $06 and $07. If those contained, say, $10 and $F0, this would effectively load the value from memory location $F010. While this allows to iterate over lists of vectors starting at given base address in the zero page (you could probably built a nice state machine based on this), it is rarely used.

     

    Generally, `LDA ($LL), Y`is what you want for a lookup table. As the parenthesis suggests, this will do the effective memory lookup at the address in ($LL, $LL + 1) and the offset in Y added to this (with carry). If $LL = 4 and Y = 2 and memory location $04 contains $10 and memory location $05 contains $F0, the lookup would be done at $F010 + 2 = $F012. As we increment Y, we smoothly iterate over $F013, $F014, and so on, exactly what we'd want to do, when loading values from a table. It's generally much like a hard coded `LDA $F010, Y`, but additionally allows us to set the base address to our liking.

×
×
  • Create New...