Tuesday, July 24, 2007

Cleaning up

Now that the worst bugs seemed to be fixed, I took some time to fix most of the flickering when the game changed screens. I did that once already, but delaying sprite writes made the flickering come back. Now everything seems to be fine except going to game mode, where sprites are delayed that one frame. I'm not sure if I bother to fix that or not - it's hard to notice the delay without slowing the game down in emulator.

I've spent enough time doing boring tweaking, next day or two will be spent playing the game instead. There are loads of sanity checks to catch the bugs which should be gone now, so playing will be purely for quality control ;)

The mother of all bugs killed

It seems that I finally nailed it! Whenever the collision check had to swap sprite indexes (to avoid having separate routines for droid-laser and laser-droid collision, for example) it also swapped counters for outer/inner loops.

    lda $d015       ; active sprites
and #$7e ; mask out player and player_fire
sta mask
 
ldy #7
.outer_loop
asl mask
bcs .has_high_sprite
.back_o
dey
bne .outer_loop
rts
 
.has_high_sprite
beq .back_o ; no sprite to collide with
 
sty high_sprite ; remember sprite count
 
lda mask
.inner_loop
dey
asl
bcc .inner_loop
 
pha ; remember all remaining sprites
sty low_sprite
 
; do stuff
 
ldy low_sprite
pla
bne .inner_loop
 
ldy high_sprite
jmp .back_o


That one has no chance of working if low_sprite and high_sprite get swapped. Inner loop starts accessing too high sprites, and outer loops messes with lower sprites than intended, eventually accessing sprite 0 (player) and negative numbered sprites (completely random data).

This shows a situation where HLL compilers beat humans when generating code. Human with limited memory can't remember which routines read/write which variables, leading to local variables having way too long life span. Compilers know exactly which locations are needed and when, and that allows them to reuse local variable area much more efficiently.

BTW, that's my only admission to the mantra compiler writers repeat: "modern compilers write as good code as any human". I have seen some pretty impressive compilers for embedded systems, but I have seen them beaten, too ;)

Monday, July 23, 2007

Another Droid in the Wall - not any more!

As expected the game has some trouble running after the changes in droid allocation, but that did make me disable all collisions (which are the only remaining thing bugging because of allocation change) and enabling them one at a time. This pinpointed droid reversal bug into droid-droid collision.

Simplified droid movement logic is as follows


  • Move droid unless it's paused.

  • If pause count is not NULL, decrease it.

  • If pause count is still not NULL, exit movement phase.

  • If droid is on waypoint, pause it or give it new x/y speed.



The collision logic for droid-droid logic may also reverse droid direction. If that happens during the game frame when droid was given new direction from waypoint, the droid ends up in the wall unless it was lucky and ends onto another waypoint by sheer luck. To avoid that game checks if droid is on a waypoint and pauses it instead of reversing it in that case.

Waypoint check needs droid character position to compare against waypoint positions. Guess what? Droid char position isn't properly set at that point! Doh!

There are couple of ways to fix this

  1. Calculate char pos for the droid to be reversed.

  2. Set flag for each droid, telling if it was on a waypoint in a movement phase. That avoids the check altogether.

  3. Add two more attribute bytes to each droid, and store the char pos there.



I don't like option 1, as that may require multiple calculations per droid for one game frame. Option 2 is good, as it avoids waypoint check altogether. This saves one to three raster lines if droid needs reversing. Option 3 is good too, as it is guaranteed to save at least one 44-cycle subroutine call per droid. Calculating char pos for explosions is extra work, but explosions are much cheaper than any other object types so that evens it out.



Forgetting to set droid char pos wasn't the only stupid error... My collision check determines colliding object types by calculating index into offset table, then doing calculated jump like this:


lda jumpoffset,x
sta jump-1
bpl *
jump


jumpoffset
dc.b droid_droid-jump,droid_laser-jump,...

It would have worked much better if assembler told me that jump offsets got big enough to get negative... This means that the current beta version skips some collision checks.



With those bugs fixed I just need to find out what's wrong with collisions involving droid fire. At least one of them messes up my droid_to_sprite and sprite_to_droid links.

Saturday, July 21, 2007

It's working - time to break it!


After couple of changes the game now seems to run solid 25 Hz on PAL C64 (and should do 30 Hz on NTSC C128 but I have no means to verify that), but to make sure it stays that way I'm finally replacing the old droid table with a proper droid allocator à la Mike. That avoids copying data every time an object is removed.

Direct result of that is that I can freely add more data for each droid as I don't have to worry about removal time. I have some ideas how to use that to speed up the game and add new features, like droid behavior when its visibilty changes. Now droids actions between waypoints are limited to whether to fire or not, flee/pursuit decision is only done at waypoints. I want to make droids more intelligent, even if it's nothing more than pause and then reverse their direction when visibility changes. That way 123 entering a room when player controls 999 will get second thoughts about going where it was intending to, and retreats instead.

I hope that the current beta works enough to keep you happy, it may take a while before the game works again...

Friday, July 20, 2007

Speeding causes fatal crash in space!

"Did you really think you could run 1.3 MHz and not crash, sir?" the C128 cop asked me after the game went awry on my FrankenDiesel.

It's true: the game was too fast on C128! What happened was that at the beginning of a game frame the main loop told IRQ "Grab sprite register data next display frame and stuff them into VIC-II when I'm busy minding other business, please" and went on to do its other business. The screen redraw takes almost a complete display frame on PAL, more than one frame on NTSC, so everything was ok. But with C128 running 2 MHz in the lower/upper border screen redraw finishes a lot before the IRQ grabs the sprite data. In fact it finishes so early that the main loop can sometimes run the complete game loop before IRQ gets to do its task. An there the disaster strike: the main loop told the interrupt "do it next frame" before IRQ had done the previous task and so sprite registers never got updated.

I fixed this with a working frame limiter, forcing the game use at least two display frames for each game frame. There was a limiter before, but that broke down when I added VIC-II buffering. I bet it knew that it could cause havoc that way...



But wait - C128 problem isn't fixed yet! Because main loop still runs droids before IRQ grabs the sprite data, sprites may get the position meant for the next game fram, and they start to wobble around the screen again. And that's what I was trying to avoid in the first place when I added the buffered writes.

Solution was surpisingly easy: screen drawing finishes somewhere around raster line 0 on PAL C128, later on all other computers. So I can do the sprite register writes in the bottom-of-the-display-IRQ which is guaranteed to come before any droids get run. This has the added bonus that now the copy loop runs in 2 MHz, speeding up the game ;)

Another bonus is that now I can allow the game run every frame if I really want. If I ever write C128-specific version I can save ~4000 cycles in the screen draw alone by unrolling the loop - that takes "only" 8 KB or so. In the end C128 version might run at 50 Hz all the time - Paradroid Redux Competition Edition, anyone?



Update


Bugfix at the usual place. Title screen crashed randomly on C128 too, that should be fixed now.

Monday, July 16, 2007

Double the fun!

Rather quick new release, fixing some things:


  • Subgame didn't clear it's local variables before retrying after deadlock.

  • Background stars flickered on NTSC, now screen/star draw is done in two parts so stars get updated in time.

  • "Floating" player fire hopefully fixed by caching joystick position through the routine.

  • Sound routine tweaked for sharper sounds, unfortunately this has changed couple of sound effects.



If the game hangs it has most likely tripped on one of my sanity tests. Freeze the game and tell me the program counter so I can check where that happened.

? Deadlock error, please debug

I found the bug which broke the transfer game. Some time ago I got rid of routine which finds starting locations of tokenized strings and stored them into a temporary table for later use, and replaced it with realtime search. There was no speed difference anyway because of raster sync and that saved some memory. Not only in the printing routines, but I could remove now unnecessary temporary buffer clearing from multiple places.

Unfortunately one of those places was in the subgame, in the retry loop to be excact. Ok, it worked perfectly unless you got deadlock and there were auto pulsers active. In that case temporary buffer still had auto pulser data active and copied color from now gone pulser position to the central bar. Usually there was wire, resulting in black color. Fix was simple: put that buffer clear back.


I've been tweaking the sfx routine further. Now it takes about 8 lines at maximum and sound starts are clearer. That however intoduced nasty snapping sound to repeating sounds, especially transfer fx. I think I can prevent that by checking if the new sound is the same as the previous one, and skipping gate off in that case.

Sunday, July 15, 2007

Sound of optimization

I went through the sound effect routine and optimized it a bit. As a result it's slightly faster, but what's more important is that now I can move all sfx zero page variables to "normal" memory and that will only cost one or two raster lines. That means that I have over 30 ZP locations which I can use for other purposes. Too bad that VICE doesn't have memory access profiler, as with one it would be easy to find out which variables/tables are accessed the most. Counting label occurences in source files gives some hints, but that doesn't count loop iterations etc. Time to do some VICE hacking I guess. Unless I find something more interesting to do, of course. Running out of interesting things made Mike add weapons systems to XeO3, I'm facing the same problem here!

Well, I have the droid allocator to rewrite too. The problem with the current method is that there is no allocator at all, so every time droid/laser/explosion is removed, every object behind it in the list needs to get moved down in memory. Changing this will make object removal a breeze and solves some other problems at the same time, but changing it also means that everything is broken for at least a couple of days because of things I can't even imagine yet. So I've stayed away from it. Well, I have to face it one day anyway...

Saturday, July 14, 2007

It's playtime!

Grab the new beta and have some fun.

Changes since the last release:


  • Decks have separate start position, you no longer start new ship on one of the waypoints.

  • Several waypoints near lifts have been excluded from available droid positions, making it safer to enter some decks.

  • Deck sections are completely separate from each other.

    • Droids stay in the correct deck section between visits and if teleported away.

    • Power off condition is now done for each section separately.

    • Bonus is still awarded only when the whole deck is cleared.


  • Some twinky stars added to background. Needs more work, especially for NTSC.

  • Droids aren't completely predictable under red alert any more.

  • Player droid damage calculation modified slighly, now higher class droids can take couple more hits under player influence.

  • Fixed character animation, ended up rewriting it completely.

  • Improved Game Over TV static.

  • F7/F8 selects starting ship, assumed you've cleared one or more ship.




There may still be a problem with the subgame, but I completed the first ship without shooting a single shot (pacifistic takeover?) without seeing the bug so at least it isn't too frequent. It might have gone away when I added some more temporary buffer clearing.

Friday, July 13, 2007

It works again!


After some cursing, hair tearing and generic despair it's working again. Well, it awards deck bonus of 500 points when you clear deck section even when there are droids left in the other section, but that's a minor problem. The most important thing in that is that it now sees section as cleared and knows to turn off the lights,regardless of remaining droids. That means that droids stay put in correct half without teleporting between sections and droids in other sections aren't active at all.

Droids also seems to obey my new "don't start on this waypoint" system, which was evident when I tried entering bridge. I had excluded one waypoint too many, so there wasn't enought starting points for all droids. The game hung when trying to find free waypoint for the last droid. I like nothing more than clear signal when something is wrong :)

New waypoint checking system is a lot faster than the original one. The original check used max. 11 lines every time droid was centered on tile, no matter if there was a waypoint or not. And because of a bug it did it twice per tile! It scanned through Y-sorted list until it found same/higher Y coordinate. As there are only 14 valid Y positions for waypoints it meant that multiple X coordinates needed checking too.

Now every waypoint is marked with magic char, so droids only check for waypoint when they are over one. First check is the the usual droid_centered, and only if that's true game proceeds to find which waypoint data is needed. That test uses binary search to find the correct waypoint among 31 possible ones, so there are max. 5 comparisons.

To cut needed time down even more game calculates simple hash from x & y coordinates and uses that as primary search criteria. Only if that matches (and there are only eight hash collisions total so it doesn't happen often) it needs to test for another byte. And remember - this check will match in the end, it is never done unnecessarily.

Max. time taken is slightly over three raster lines, as seen in the attached picture.

Next beta will be out this weekend.

Thursday, July 12, 2007

All hail simplicity

After I wrote the code to create all droids on ship I had second thoughts about it. The code was horrible mess, parsing deck data to get correct number of certain level droids into correct sections. Nested loops, lots of comparisons, checking every section to get droid distribution right. In the end I made one 256 byte table to place every droid where it belongs. 256 bytes is quite a lot of memory compared to 30 bytes which the droids required when associated with decks and sections. However, the table ended up being only 48 bytes when packed, and the routine which goes through it (once!) was about 80 bytes shorter than my original code. In fact it was shorter than the one from original Paradroid, and that one didn't know anything about deck sections.

Now the ship contained all the necessary inhabitants, so it was time to check out the code which activates correct droids when player enters a deck. That was bound to be easy. However, when I depacked section waypoints I didn't store their Y coordinates at all. This confused droid placement, and it had every reason to do so. That meant that I had to put Y coordinates into a temporary buffer where InitDeckDroids() could find them. Yeah right, except that there was a routine between the two subroutines which cleared the coordinate table...

Wednesday, July 11, 2007

NewsFlash! Viral infection grinds PR development to a halt!

Not my computer, luckily. Most of the last two days has been used to salvage data from one of the worst infected computers I've seen. Booooooooring! My recommendation: buy removable HD. Copy data there. Format C: Reinstall.

I've managed to teach the game quite a bit more about ship layout, droid distribution and other small things. Now it knows which deck part lifts connect and really should be able keep droids in their respective deck parts. I bet tomorrow will be spent on debugging every possible problem caused by game not generating droids, droids jumping around whole ship, waypoints being completely garbled up and whatever the game can think of.

Tidbit of the day: left side of observation has two waypoints, but game never generates droids there.

Tuesday, July 10, 2007

My God, it's full of stars!


I'm not sure if I like stationary background stars (this time I nicked the idea from Uridium) as wall chars have couple of empty pixels at the edges. This means that stars will disappear before they reach the ship. This could be explained with diffraction in the micro-atmosphere around the ship, of course...

Adding stars would be quite cheap - I only need to check for underlying char being "outside_of_deck" and replace that char with animated star char. Actually I check the next one too, to be able to have two pixels wide stars. Animating the star chars is even easier:

.stx
ldx #0
lda #0
sta Font+chr_STAR*8,x
sta Font+chr_STAR*8+8,x
 
lda vScroll
eor #7
tax
stx .stx+1
ldy hScroll
lda bits,y
lsr
ora bits,y
sta Font+chr_STAR*8,x
lda #0
ror
sta Font+chr_STAR*8+8,x


First it clears previous star pixels, then calculates new Y position, stores it for the next round and plots two bits into chars.

Don't mind the regular position of stars, that's just test data. Stars are exactly 64 chars apart from each other. If this gets used then star positions get randomized every time you enter a deck.

Sunday, July 8, 2007

Bit by bit

After I had to come up with a name for the fastloader I decided to post this bog-standard routine I use in Paradroid Redux to depack waypoint and droid distribution data. The only reason it's worth of posting is that it doesn't use any other register than accumulator.



;   in: ax=ptr

InitGetBits
stx .get+1
sta .get+2
lda #$80 ; start with end bit
sta cbits
rts
 
 
GetBits
.1 asl cbits ; 5
bne .3 ; 8
 
pha ;( 3)
.get
lda $1234 ;( 7)
inc .get+1 ;(13)
bne .2 ;(16) assume not taken
inc .get+2
.2 rol ;(18) rotate end bit in
sta cbits ;(21)
pla ;(25)
 
.3 rol ; 10
bcc .1 ; 13 assume taken
rts



13 cycles per bit unless new byte is needed, 25 cycles extra every 8th bit. This adds up to 16.125 per bit on average, not accounting for jsr/rts overhead.

Calling it requires loading A with a bit mask telling it when to stop. Usually this is single bit to return only input data, for example %00010000 to read four bits. However, as I need to ORA the four-bit Y coordinate with #$20, I can do it for free by doing "LDA #%00010010; JSR GetBits". The loop ends when the highest bit is rotated into the carry, so A is "0010yyyy" at that point. Even better, after I've stored the ORed value I need to multiply A by four and then add two, and this time I can use the fact that carry is always set on exit so it's just "ROL; ASL". Try to do that with a high-level language :)

Edit: oh bugger, IE6 collapses multiple line feeds into one even inside PRE tag. I hope IE7 is more intelligent.

Loading, please wait...

I cleaned up XeO3 loader (yes, it's for Commodore 264 family) slightly and added couple of comments. You can find the loader + source code here, I hope it gets added to Plus/4 World soon so people can actually find it.

The loader works with 1541/157x and 1581. Every bit is clocked separately, so it's safe to use interrupts while loading. It beats many 2-bit loaders, and allows keeping the screen on.

Resident loader part is less than 256 bytes. Save works too, but maximum size is 252 bytes and file to overwrite must be on the disk already. It's only meant for high scores anyway ;)

Porting back to C64 is easy enough, just change couple of variable definitions and remove unnecessary code.



Update:



This is not a generic turbo you would use to load programs with. It was meant for game/demo use and so it has some limitations.


  • You can't LOAD "$",8.

  • Drive code is disabled (drive is reset) as soon as ATN line is active. This means that it's gone as soon as you access any device on IEC bus with kernal routines.

  • Drive code is installed only once, so once it's gone, it's really gone. That's the dark secret behind small resident code.

  • If you try to LOAD/SAVE without restoring kernal vectors once drive code is gone, computer will hang waiting for drive response.


Optimal interleave is 16 sectors, unless your interrupt takes lots of time. In that case try interleave 17 or 18 to see if that speeds up loading. Normal interleave used by 1541 is 10, which means that the loader must wait additional 10+ sectors to pass by the r/w head before reading next sector from disk surface.

Friday, July 6, 2007

629 - number of the crap

No, not really. 883 still holds that position, but 629 is a close runner-up. 615 is bit more usable after damage adjustment, but I'm still not going to change my playing style.

Lift exit bug seems to have gone away when I reverted back to the original code. I hope it stays there, too.

Aye, captain! Turbolift is up and running

One deck map is 16 KB of data, so it takes a while to build one from 64*16 byte layout. That's why lift movement between decks is as slow as it is - game builds the deck in advance to avoid delay when you exit the lift.

I modified the lift routine to check if it was allowed to move up/down from the new deck and to pass that info to the deck building subroutine. Deck builder checks halfway through the data if joystick is pushed to allowed direction, and if it is then the routine exits without finishing the deck. Lift routine then sees the return value and moves to next deck if needed. When joystick is finally released or there are no more decks above/below current one, the deck gets fully built.

The end result: lift appears to be twice as fast as before, without affecting the exit time much.



While testing it on real C64, I noticed that a new bug has crept in - sometimes the game pushes player droid inside the wall when it exits the lift. I haven't been able to repeat the bug inside VICE, which makes it "fun" to find. I suspect my new and optimized "keep old position on tile when exit" code. Maybe I will just center the droid on lift tile, as I bet there's "stay away from walls" sign in the lift anyway...



I've also modified player damage calculation slightly. Earlier player droid class didn't affect the amount of damage it received from enemy fire, now higher class droids take a little less damage, especially from single laser. The difference isn't big, but now it's a bit more tempting to use higher class (and faster decaying!) droids instead of 476.

Thursday, July 5, 2007

?No space in brain error


You all have noticed how 999 description text has extra space after the word "brain", haven't you? Twice, even. That's because the same string token is used for the first page of droid data, and space is needed to align the text. Three other strings are padded too, but they don't affect any other text. Instead of adding one more token costing five bytes I removed all trailing spaces and added code to do the aligning. Couple of changes later I had won one byte and nice warm feeling, knowing that now the text is perfect.


Speaking of space saving - I checked how much free memory I have, and I have 400+ more bytes than two weeks ago. If I continue like this the game doesn't use any memory at all after couple of years...

... no, not really. New TV static routine suggested by Mike is longer than the original. Oh, horror! However, it looks so much better than the original that I can accept the loss of six bytes.

I took the blue pill

While the Matrix effect with Paradroid charset doesn't look all that bad (in fact I like it more with 1x2 charset) it looks really out of place when it's used inside the game. Next one to try is to get "true" random static. That may require more than four different "random" chars, which means some extra data to be moved around. I already re-use console icons for waypoint markers, swapping them in and out as needed.

Wednesday, July 4, 2007

Too kitsch, or not too kitsch: that is the question.


Nothing much done today, except one completely unrelated routine inspired by some random web site. Funny thing, I have never thought about writing Matrix character drop effect for C64 before.





Now I'm wondering how it would look with Paradroid font. Core routine is about 200 bytes, which is longer than the original TV static effect. However, I've saved quite a lot of space reserved for data, so I could take some of it back.




I guess there's only one way to find out how it really looks. Code has to be changed slightly to cope with 1x2 chars, but other than that it should be simple case of inserting the new code (famous last words?)

Tuesday, July 3, 2007

Let There Be Light

On a sunny summer day in Finland it finally happened - I gave in and started a blog about my Paradroid hack. Unlike PC remakes this one is made for the Commodore 64. (Enough links already? Can I stop it now?)

The disassembly was completed over a year ago, and since then there have been many changes. Main goal was to give it a nice speed boost, but I couldn't resist changing some other things as well. You can read more about it here, but let's recap the list:


  • Support for second fire button.
    No more accidental transfers.
  • Number of droids remaining on deck/ship shown in console
    Nicked from Paradroid'90.
  • Statistics for previous game can be viewed during intro sequence.
    Nicked from the same source...
  • Start ship can be selected from any previously visited ships.
    One more from Paradroid'90. This info is saved to disk so you can carry on from your last game.
  • Scoring changes: alert scoring is altered to match the original instructions, ship clear now has both accuracy and alert bonus (p90 is good for inspiration!). I also replaced rather stupid "lowest scre of the day" with all time high score, which is saved to disk.
  • Droid AI has changed quite a lot: droids flee/pursuit depending on their level, energy and ship alert status. Radar equipped droids do that even if player is not visible.
  • Damage calculation has changes slightly as well. Droids getting hit by friendly fire sustain same damage than from player, and explosion does sligthly less damage so it's easier to survive one. Explosions don't restart any more when hit by laser/disruptor, which makes them less deadly as well.
  • Original game killed droids when there were no free sprites, I attempt to teleport them to some other part of deck.
  • Droids bumping into each other behave randomly.

You all knew that all already, so let's end this entry with something useful to know. When writing minimal saving routine, be sure to clear memory location $02a1 beforehand. Otherwise you may get endless loop when kernal waits RS232 routines to release IEC bus. Here is the minimal version of system restore before load/save:

RestoreSystem
pha

ldx #$80 ; store game vars
.1 lda 0,x
sta $bf00,x
inx
bne .1

stx $02a1 ; avoid IEC_idle hang
stx $9d ; no messages

inc $01 ; kernal in
; IRQ will now use $0314
pla
rts