Sprite crunching

From: Ettore Perazzoli (ettore_at_comm2000.it)
Date: 1998-08-30 22:41:18

Hi!

I think I have found a bug in Christian Bauer's explanation of sprites
and $D017.  I have found it quite a long time ago, and I hoped I could
investigate further to find what the correct explanation is, but there
has not been enough time so far.

To make it short, try the following program by Andreas Boose:

begin 644 sp.prg
M`,!XJ7^-#=RM#=RI4XT4`ZG`C14#J1N-$="I8XT2T*D!C1K0K1G0C1G0HC^I
M59W``\H0^JD/C?@'J8B-`-"I9HT!T*D`C1#0C1S0J0&-%]"-%=!88,X9T.X1
GT,X1T"3_HC'*T/VI`(T7T*D!C1?0H@G*T/VI`(T7T*D!C1?03#'J
`
end

Here is the assembly listing:

    C000  SEI
    C001  LDA #$7F
    C003  STA $DC0D
    C006  LDA $DC0D
    C009  LDA #$53
    C00B  STA $0314
    C00E  LDA #$C0
    C010  STA $0315
    C013  LDA #$1B
    C015  STA $D011
    C018  LDA #$63
    C01A  STA $D012
    C01D  LDA #$01
    C01F  STA $D01A
    C022  LDA $D019
    C025  STA $D019
    C028  LDX #$3F
    C02A  LDA #$55
    C02C  STA $03C0,X
    C02F  DEX
    C030  BPL $C02C
    C032  LDA #$0F
    C034  STA $07F8
    C037  LDA #$88
    C039  STA $D000
    C03C  LDA #$66
    C03E  STA $D001
    C041  LDA #$00
    C043  STA $D010
    C046  STA $D01C
    C049  LDA #$01
    C04B  STA $D017
    C04E  STA $D015
    C051  CLI
    C052  RTS
    C053  DEC $D019
    C056  INC $D011
    C059  DEC $D011
    C05C  BIT $FF
    C05E  LDX #$31
    C060  DEX
    C061  BNE $C060
    C063  LDA #$00
    C065  STA $D017             ; first $D017 write
    C068  LDA #$01
    C06A  STA $D017             ; second $D017 write
    C06D  LDX #$09
    C06F  DEX
    C070  BNE $C06F
    C072  LDA #$00
    C074  STA $D017             ; third $D017 write
    C077  LDA #$01
    C079  STA $D017             ; fourth $D017 write
    C07C  JMP $EA31

Basically, this code activates an y-expanded sprite starting at line
$67; then, using an IRQ, it writes $01 to $D017 at cycle 15 and $00 at
cycle 21, once at line $69 and once at line $6a.

According to the "VIC article",

  1. The expansion flip flip is set as long as the bit in MxYE in
  register $d017 corresponding to the sprite is cleared.

(so the flip flop is always 1 if the sprite is not y-expanded)

  3. In the first phases of cycle 55 and 56, the VIC checks for every
  sprite if the corresponding MxE bit in register $d015 is set and the
  Y coordinate of the sprite (odd registers $d001-$d00f) match the
  lower 8 bits of RASTER. If this is the case and the DMA for the
  sprite is still off, the DMA is switched on, MCBASE is cleared, and
  if the MxYE bit is set the expansion flip flip is reset.

(so the flip flop is always 0 on the first line of an y-expanded
sprite)

I have checked "sp.prg" on my 64C at home.  I have figured out which
memory locations are displayed in which pixels, and it looks like
there is really something wrong with this description.

	+-----+-----+-----+
	|  0  |  1  |  2  |
	+-----+-----+-----+
	|  0  |  1  |  2  |
	+-----+-----+-----+
	|  3  |  4  |  5  |	<-  First $D017 write
	+-----+-----+-----+
	|  7  |  8  |  9  |	<-  Third $D017 write
	+-----+-----+-----+
	|  7  |  8  |  9  |
	+-----+-----+-----+
	|  7  |  8  |  9  |
	+-----+-----+-----+
	| 10  | 11  | 12  |
	+-----+-----+-----+
	| 10  | 11  | 12  |
	+-----+-----+-----+
	:     :     :     :

Each rectangle represents 8 pixels, and the number represents the
offset of the memory location that contains the bitmap for those 8
pixels, relative to the start of the sprite (in this case, $03C0).

The $D017 writes are the ones "sp.prg" makes at cycle 15 and 21, after
the `DEX' loop.  The flip flop before the first write is 0 (because
the sprite starts expanded), and writing 0 to the register should
(according to the article) set the flip flop to 1.  As the write
happens at cycle 15, the memory pointer should be incremented by 1
instead of 3 (as explained in 3.14.7).  As "sp.prg" also writes 0 into
$D017 afterwards, at cycle 54 the flip flop would be inverted (3.8.1,
rule 2), so its value would be zero before the third $D017 write,
which thus should cause another 1-byte shift in the memory pointer.
So the beginning of the map would be like this:

	+-----+-----+-----+
	|  0  |  1  |  2  |
	+-----+-----+-----+
	|  0  |  1  |  2  |
	+-----+-----+-----+
	|  3  |  4  |  5  |	<-  First $D017 write
	+-----+-----+-----+
	|  4  |  5  |  6  |	<-  Third $D017 write
	+-----+-----+-----+
	|  5  |  6  |  7  |
	+-----+-----+-----+
	|  5  |  6  |  7  |
	+-----+-----+-----+
	|  8  |  9  | 10  |
	+-----+-----+-----+
	| 11  | 12  | 13  |
	+-----+-----+-----+
	:     :     :     :

That's what FrodoSC does, indeed, but it is clearly not correct.

VICE does this right instead.  This happens because the "Xayne's
Birthday Party" disk by Crest contains a routine showing this effect,
and even a table of the values that should be added to the sprite
memory pointer when this effect is used; I have just copied it and
implemented things accordingly.  The table looks like the following
(grabbed from `src/c64/sprcrunch.h'):

static const int sprite_crunch_table[64] = {
    1,   4,   3, /* 0 */
    4,   1,   0, /* 3 */
   -1,   0,   1, /* 6 */
    4,   3,   4, /* 9 */
    1,   8,   7, /* 12 */
    8,   1,   4, /* 15 */
    3,   4,   1, /* 18 */
    0,  -1,   0, /* 21 */
    1,   4,   3, /* 24 */
    4,   1,  -8, /* 27 */
   -9,  -8,   1, /* 30 */
    4,   3,   4, /* 33 */
    1,   0,  -1, /* 36 */
    0,   1,   4, /* 39 */
    3,   4,   1, /* 42 */
    8,   7,   8, /* 45 */
    1,   4,   3, /* 48 */
    4,   1,   0, /* 51 */
   -1,   0,   1, /* 54 */
    4,   3,   4, /* 57 */
    1, -40, -41, /* 60 */
    0
};

Using this table is quite straightforward: whenever $D017 is written
to I do the following (I just include the C source here, as I think
it's clearer than words):

    sprites[i].y_expanded = value & (1 << i) ? 1 : 0;
    
    if (!sprites[i].y_expanded && !sprites[i].exp_flag) {
        /* Sprite crunch!  */
        if (cycle == 15)
            sprites[i].memptr_inc = sprite_crunch_table[sprites[i].memptr];
        else if (cycle < 15)
            sprites[i].memptr_inc = 3;
        sprites[i].exp_flag = 1;
    }

`value' is the value being written to $D017, `i' is the number of the
sprite, `exp_flag' is the status of the expansion flip flop, and
`memptr_inc' is the value that is being added to the sprite memory
pointer on this line, after the DMA is done.  Of course this is done
for every sprite.

Now, this has no theoretical foundations; I have no explanation for
those values, that actually look like black magic.  Is there anybody
who has tried to explain this effect?

Another problem is that I am pretty sure there is still something
wrong with this.  I have at least two demos that do not behave
correctly yet.  Maybe trying to explain the internals could help, but
so far we have not had the time to investigate this further.  Is there 
anybody out there who could help?

Btw, CCS64 does that right.  Too bad it does not come with source
code.  :-)


Bye
Ettore.
-
This message was sent through the cbm-hackers mailing list.
To unsubscribe: echo unsubscribe | mail cbm-hackers-request@dot.tcm.hut.fi.

Archive generated by hypermail 2.1.1.