supercat
-
Content Count
7,259 -
Joined
-
Last visited
Blog Comments posted by supercat
-
-
Rewriting the kernel, though often scary and mostly fraught with deadends (for me, anyway), can dramatically improve a game, IMO. With Reindeer Rescue, a bunch of rewrites, to accomodate some of Nathan's ideas, really took the game to the next level, both in gameplay and in how it looked.I'm working on a kernel for Wormy (another game I started in 1994 but abandoned). Didn't get as far on that one as on Col (Strat-O-Gems) but I've managed a proof-of-concept kernel demo that shows the basic idea is sound. The question is what to do with it.
Currently, the kernel supports a 32x16 playfield bitmap for the snake, plus up to 128 objects that can either be mines or goodies (shown using a striped playfield). I've got all this stuff working in a kernel that uses a missile to show the head of the snake; at present, the snake is limited to motion in playfield-sized steps but I've got oodles of cycles to work with. I could also use the ball to move the tail smoothly. My biggest problem is that memory is going to be extremely tight (try fitting a 32x16 bitmap plus 128 objects plus 64 'clicks' of snake tail all within 128 bytes of RAM!) so I don't want to use any more than I must in the kernel.
My guess is I'll probably do a few kernel rewrites before the thing is done, making different RAM/time/versatility tradeoffs. Still, even what it does now is sorta neat.
-
ror PF3Data-1,X clc lda #%00001000 and PF3Data-1,X beq PF2Rotate sec PF2Rotate
6+2+2+4+2+2 = 18 cycles (10 bytes)
How about
ror PF3Data-1,X lda #8 and PF3Data-1,X cmp #1; Carry set if NZ
6+2+4+2 = 14 cycles (8 bytes)
Another variation:
lda PF3Data-1,X ror sta PF3Data-1,X and #8 cmp #1
4+2+4+2+2 = 14 cycles (9 bytes) Not sure if there's any advantage to this version. If an "rol" wer needed instead of "ror", there'd be some 'undefined opcodes' that might be helpful, but nothing looks apropos here.
-
Would it be possible to use some sprites to draw thin vertical lines? The spacing ends up being somewhat awkward (every 12 pixels doesn't fit with any rate of sprite 'repetition') but if you set Player and Missile 0 both to two copies you might be able to alternate stores to them every 12 cycles. You'd have to skip a couple stores to be able to rewrite the PF1/PF2 registers at the right times, but you could use Player and Missile 1 to fill in a couple of those spots. Alternatively, you could use Venetian Blinds on your vertical lines, though that might not look so great if it interacted poorly with the color striping.
Another possibility would be to not use the color striping for the fill, but instead use a self-modifying sequence of STX COLUBK,X/STY COLUBK,X to control the pipe color (if X<>0, the address would have to be adjusted appropriately). The lack of clear boundaries might be a problem, but perhaps that issue could be resolved other ways (it only really matters on empty cells, so an indication of where the empty cells are should be sufficient).
-
I cannot use 27256/27C256 EPROMs without a 32K binary (in other words, I can't just drop-in a 27256 with the 16K PLD code). Having said that, I pretty much use CMOS parts for all 32K games (2600, CV, and 5200) without any problems. However, I'm not sure I've used any TI 27C256 parts. Joe Grand did look at the data sheets for the 27C128 and the ATMEL PAL we're using, and did not see any problems. It's possible there's a problem with the PLD code that only affects these 16K CMOS parts.I use 27C256's in 8K/16K carts with no problem. Indeed, I never use anything else.
From the DOS prompt:
COPY /b 16Kfile.bin + 16Kfile.bin 32Kfile.bin
or
COPY /b 8Kfile.bin + 8Kfile.bin + 8Kfile.bin + 8Kfile.bin 32Kfile.bin
This works just fine with the 8K and 16K PLD's. I don't know whether A14 is high or low, but since the same code is selected regardless it doesn't matter.
-
2. On the other hand, this method of scoring does directly reward good play*, which is cool. This could add a lot of replay value in finding the best route through a level. Assuming the levels are designed to allow multiple routes, of course.
*By this I mean it would reward players who beat the level in the fastest time without losing a life.
I'd rather have a timer that restarts when you lose a life, but a substantial reward for getting through a level without losing one. Perhaps you could start a player with three lives and award an extra life each level but give a player a minimum of three lives at the start of each level even if a player was down to his last life when he cleared the previous level. Award a substantial bonus for any lives remaining at the end of a level, and a very substantial bonus if a level is completed with a pegged maximum number of lives remaining.
3. On a related note, if the relative points (for things like gold) are balanced well with the level bonuses, then there would be no advantage to "gaming" the system; e.g., dying and repeating the same level multiple times to rack up big points. The method for achieving the highest score would match up with the method for playing a "perfect" game (i.e., finishing all levels as fast as possible without losing any lives).Alternative solution: if a level is not cleared, nothing in the failed attempt scores anything; the player's score reverts to what it was at the start of the level.
-
And remember, that's 17 minutes total to complete the level. That clock doesn't reset when you die. The level resets 100% and you get to try again, but that clock keeps going down. (The arcade game also has a timer which gets added to your score when you finish the level, but it gets reset when you lose a life.)I don't like this scoring idea, since it penalizes people who die after almost completing a level more than those who die earlier on.
-
I will send you a socketed 16K board with a TI EPROM with the original code, as well as a GI 27C128 (which works, but behaves differently than a 27128 part) as well as a non-CMOS part for comparison. I don't actually have enough non-CMOS parts for all the Reindeer Rescues I need to build, so I am trying to source more at the moment.
I'll get the board and EPROMs out to you on Monday, as I'd really like to know what's going on here.
Could you try using 27256 or 27C256's? I don't think I have any TI 27C128's but I might have some TI 27C256's in the office, so if those exhibit the same problem I could experiment with those and a scope (might be better than a logic analyzer).
-
*As an aside, I think M-4 may have the record for the most game variations on a single cartridge. Space Invaders has 112, but half of those are the same, just 2-player. Well, M-4 has 76 variations, all of which are 1-player. Plus you can select straight or steerable missiles with the L Difficulty switch, bringing us up to 152 variations. Now factor in that all those variations can be played 2-player as well, and we're up to 304! Ok, half of those variations are just shorter games (with a 30 second time limit instead of 90 seconds) but, still, if you disallow those you still end up with 152 variations! Well, I think it's cool, anyway.
Space Invaders has 416 variations (sixteen one player, times two difficulty settings, plus 96 two player times four combinations of difficulty settings).
-
Apparently it is not fully floating the bus - it must be weakly pulling data lines up or down, possibly depending on what data is located at the address on its address lines. I looked at the TIA schematics, and it only drives values low - high values are caused by pullup resistors.Always-on pullup resistors? Hmm... I wonder how my 4A50 cart works then, since it relies upon the data bus to be floating when the processor isn't doing anything to it.
It seems that loading from CXP0FB causes an incorrect value to be placed in the accumulator, but a correct value is placed in processor flags. Maybe a different mechanism is used in the 6507 for each - and the flaky value on D7 goes one way or the other.How does the "Supercat" solution work, then (LDA, followed by masking)?
-
If you're looking for weird and wacky ways to do that, how about:lda #128 cmp CXP0PF adc #127; Decimal mode must be clear
That's an approach I just thought of for turning a bit into a 00 or FF. Actually, it might be useful for a number of things.
I just realized, it should be:
lda #127 cmp CXP0PF adc #128
That way, if CXP0PF happens to yield exactly 128 this will still work. Don't think it matters for this, but if one's using the trick for other purposes it might.
-
If my wild conjecture is correct, then all of the above will work. Can anyone guess what all of the above have in common?They'll all put bit 7 of CXP0PF into bit 7 of SantaTemp. Not sure what else they have in common. If you're looking for weird and wacky ways to do that, how about:
lda #128 cmp CXP0PF adc #127 ; Decimal mode must be clear
That's an approach I just thought of for turning a bit into a 00 or FF. Actually, it might be useful for a number of things.
-
And no, I haven't yet modified a binary to write the contents of CXP0FB to the screen somehow. Maybe I'll work on that. The easiest way would be to convert the binary value to two bcd bytes and then write that to the score. Anybody have a nice, short routine that can do that?You're handling the score in BCD, right? Just write the byte value directly to the score. If a nybble is 0-9 it will show up as a digit. If it's A-F it'll show up as some garbage character. If that happens, look through the code to figure out what it is.
-
Why does the code require D0-D5 to hold certain values when I can't find anywhere that it does and only for a certain type of EPROM?I don't know. If your post was thorough in listing all of the places you use SantaTemp, I can't see why it would matter like it does. Nothing is different between an "AND #$C0" and an "AND #$FF" except the content of the accululator and Z flag afterward.
A few more things I'd be curious to see...
-1- Ensure some bits are set, but don't mask off any (maybe zero is a bad value?)
lda CXP0FB ; Or whatever it is and #$FF ora #$02 sta SantaTemp
-2- Ensure no excess bits are set, but don't add any
lda CXP0FB ; Or whatever and #$C2 ; Bit 1 would typically be set, but don't force it ora #$00
-3- Force all excess bits to '1' [may be worth trying in emulator, though I did with no apparent effect]
lda CXP0FB; Or whatever and #$C0 ora #$3F
-4- Force all excess bits to '1' [may be worth trying in emulator, though I did with no apparent effect]
lda CXP0FB; Or whatever and #$C0 ora #$00
-5- Make all excess bits random [try in emulator]
lda SomethingRandom and #$3F eor CXP0FB
For the last one, SomethingRandom should be some address that holds a changing value.
Two other things to consider:
-1- Do the difficulty switches do anything?
-2- Is it possible that an indexed load is looking at SantaTemp and gets confused if it's zero when it shoudn't be, or it has unexpected bits set?
-
lda CXP0FB jmp ImHere and #$C0 ora #2 ImHere
If this happens to re-break things, then try:
lda CXP0FB and #$FF ora #0 jmp ImHere ImHere
In this version, the "AND" and "ORA" don't affect the accumulator contents, but they do take time to execute. If this version is also re-broken, then something in the code depends upon D0-D5 holding certain values. If the code works with the second version but not the first, then there is something that requires the right amount of delay. And if both versions work, then there's something elsewhere in the code that's address-sensitive. In that case, you should try removing this extra code and inserting seven extra bytes at various other places (probably between routines). You may find a routine which will work if seven extra bytes are placed before it but not if they're placed after.
-
You're definitely not too late!EDIT: Ok, Al tested that version and it did not work, Fred.
But supercat had a theory of his own and his own fix to try, which ended up working.
His looked something like this:
Replace
lda CXP0FB sta SantaTemp
With this:
lda CXP0FB and #$C0 ora #2 jmp ImHere ImHere sta SantaTemp
I didn't quite understand his explanation of why that might work, so I'm waiting to hear back from him.
Well, my theory was that it might be an address-sensitivity problem elsewhere in the code, solved by expanding this bit of code by seven bytes. Using seven NOPs might not be an adequate substitute because they take too long to execute. Try changing the above code to
With this:
lda CXP0FB jmp ImHere and #$C0 ora #2 ImHere
Same size. Four cycles faster, but that shouldn't matter--the key thing is that it's not fourteen cycles slower. I must confess to still being puzzled.
Incidentally, the earlier code would force the accumulator to have the value it would normally have if D0-D5 float cleanly.
-
However, I understand that LDA, LDX, LDY and LAX operate a little differently than the others. I don't know if this has any effect, but I think (and I hope someone will correctly if I am wrong) that the other instructions actually perform the operation during the 4th cycle but simultaneously fetch the opcode for the next instruction, so it seems like 3 cycles.On the 6502, the processor has to know what it's going to do with the memory bus on any given cycle before the previous cycle completes, with two exceptions:
-1- The low-order part of the address may be taken from the previous cycle's data, or
-2- The high-order part of the address may be taken from the previous cycle's data.
If you perform any instruction that ends with a read operation, the processor will start processing the opcode fetch for the next instruction before it finishes processing the read data. This is fine, though, since the opcode fetch will be unaffected by the previous operation.
Regardless, my gut tells me that it's a problem with how the EPROM affects the data bus. If the data bus has open-collector outputs (I am not sure if it does or not) then it can easily be pulled low. In LDA CXP0FB, the data bus will contain $A5...$02...then the contents of $02. Maybe the $02 is staying on the bus too long. I wonder if you changed it to LDA $E000+CXP0FB, then the bus would contain $AD...$02...$E0 and I wonder if this would work, since the high byte would be 1 instead of 0.When performing a "LDA $02" instruction, there are about 420ns between the time A12 goes low (indicating the EPROM should shut up) and the time the TIA starts outputting its data, and another 420ns before the CPU actually reads the data. Any EPROM which can't shut up within 420ns is broken.
One source of incompatibility, though, is that the TIA doesn't bother to say anything on bits 0-5. They may float high, they may float low, or they may just sit at the last driven value. This would most likely be 2, but it could at least theoretically be something else (if the EPROM is faster than the GAL, the changing address could cause it to output a goofy data byte before the chip becomes deselected). Thus, bits 0-5 of any TIA read should be regarded as indeterminate.
Note that this only affects bits 0-5. Bits 6-7 will be actively driven by the TIA, and it should have no trouble whatsoever doing so.
-
Sorry but Companies throw out trash, they throw out defective product they don't throw out perfectly good products and then open warehouses 6 months later to store products or order a second run of the very same title.In some industries, it's very common for companies to throw out lots of unsold product and then later produce essentially the same thing. When a typical paperback publisher supplies books to a bookstore, they want to receive within a reasonable time cash for the books that sold, or the covers of books that didn't. They don't want the unsold books for anything; all they want is proof that they were unsold and destroyed. On the other hand, the fact that a book didn't sell out everywhere doesn't mean the publisher won't print more. It's just cheaper for a publisher to print a new copy of a book than it is to recover, inspect, and sell an unsold copy.
I don't know what the various costs of cartridge production and packaging were during E.T.'s heyday, but if Atari wanted to make any change whatsoever to the box or its contents, it may have not have been worth the expense to open up the old boxes, remove the contents, and repackage them.
-
I am using fractional positioning for the enemies, but I need to have the player move at a constant rate so I only have to check for player "on grid" every sixth frame (fifth for PAL). The player can also reverse direction at any time, which would play havoc with fractional positioning. (Can you say roundoff errors? Good, I knew you could.)Instead of using fractional positioning as such, you could use fractional rate adjustment. Every frame (whether the player is actually moving or not) you do something like:
sec lda movespeed adc movephase sta movephase bcc move_never_mind jmp handle_playermove move_end:
If movespeed is 255, the player will be allowed to move on every frame. If zero, player moves once every 256th frame. To move on five frames out of six, use a value of 204. If it's necessary to support higher speeds (more than one move per frame), then you can do something like:
lda movespeed asl bcs movehighspeed sec adc movephase sta movephase bcc move_end jmoveonce: jmp player_move_once movehighspeed: adc movephase bcc jmoveonce jmp player_move_twice move_end:
In this case, the value of movespeed is the number of moves per 128 frames, minus one. So a value of 192 would cause alternative frames to move once or twice.
-
Thomas said that Thrust+ and Jammed both use those numbers and nobody has complained yet.
Thrust+ certainly worked on my 1990s TV when I played it with a CC1.
Thrust+ Platinum clocks in at 262 according to Z26. Were earlier versions more?
-
1. Thomas J had a great suggestion for the PAL conversion of Reindeer Rescue - have the NTSC version run at 270 scanlines per frame and the PAL version run at 300 spf - then the speed difference between the two is closer to ~11% rather than ~20%I don't like that idea; my 1990's television set couldn't manage a stable display with Karate, and that was only 269 lines. Some new LCD sets are picky as well.
-
The lengthy delay in this version makes the game nearly unplayable for me. Personally, I found the previous version to be quite difficult already
I wonder if you would not be better just introducing some randomness into the system when there is no obvious move, rather than implementing detailed heuristics? As you probably know, random techniques often outperform detailed rules in such systems.
Both Connect-4 and Othello are somewhat difficult games to write good heuristics for, because the key to effective strategy lies not in the number of pieces you have or their position, but rather with moves that become available as a result of other moves. It is common on Connect-4 for a situation to arise in which the next person to play in a column will lose. The game from that point revolves around exhausting the supply of other moves in such a fashion that the opponent is the one who has to make the losing play. I would suggest finding and reading articles about the game; they'll probably provide some insight.
Heuristics can count for a lot, but multi-ply analysis is important as well. I have won quite handily at Othello at a game I should have lost, because the computer made a bad play 11 moves from the end. Multi-play analysis would have caught that (indeed, the bad play was the computer's last, since I made all 10 remaining moves without leaving the computer any other entry).
-
WOW. That's a 2600?

The game does seem exceptionally hard, and I can't figure out any way to resume after dying (is there any?) Also, someimes a wide pink bar appears over my character when digging (probably a sprite artifact).
Still, pretty darned impressive.
-
I think you are rather brave to go back to your old code like that - I recently had a look at the Hunchy source code (my first game) and it was not a pretty sight!The Strat-O-Gems kernel dates back to 1994. I added a couple of undefined opcodes to free up cycles to stick in some other stuff (probably not really needed) but I actually posted a copy awhile back.
When I did that kernel, I didn't understand how the divide-by modes of the RIOT worked; I'd figured I'd have to use cycle and scan-line counting to keep track of vertical position while doing all the game computations (which would have been an absolute nightmare!) so I dropped the project. I also deliberately made things off-center to get the timings right, which is odd since the timings work just fine with everything centered perfectly.
-
You could also blank the display and periodically change the background color while it is calculating. I'll bet you could speed up the calculation by close to an order of magnitude if you do this, since you could run everything in a tight loop without any need to render a screen.If your program knows its progress state, and you don't want to have any sort of kernel while it's thinking, I'd suggest doing something like the Supercharger "barn door" effect. It's quick and easy, and will give a nice progress indication, at least on older television sets (newer ones, unfortunately, may blank the screen if they don't get VSync).

Reindeer Rescue Final Recap
in Bob's Blog
A blog by vdub_bobby
Posted
There are times having things RORGed to the same address can be helpful, especially if some banks will share common content. What I did in Strat-O-Gems was to have the common areas RORG'ed at $Fxxx but all the distinct areas RORGed at $1xxx, $3xxx, $5xxx, and $9xxx. Interestingly, those other areas were also ORG'ed at those same addresses (using every other bank); I then used a simple utility I wrote to interleave the NTSC and PAL versions of the code.