Jump to content
IGNORED

ARRGGGGHHH! VIDEO CORRUPTION


digress

Recommended Posts

WP 20160806 003

 

I've been running into this video corruption from time to time. The entire screen at once.

 

I've been moving all vdp access inside the nmi routine. Still hasn't improved though.

 

I'm wondering does any of these statements actually access VRAM which are not inside the nmi_routine. In other words do I need to move them inside the nmi routine.

sprites[1].x=100;

sprites[1].y=100;

sprites[1].colour=4;

sprites[1].pattern=(25*4);

 

or does only updatesprites(0,16); access vram which I already have inside the nmi routine.

 

My understanding is they are just change data in an array which is only written to the vram once updatesprites happens so it shouldn;t matter.

 

Thanks for any replies.

 

Link to comment
Share on other sites

I don't think the Sprites[] instruction touch the VRAM , it is only when you do the updatesprites that the data are transfered.

 

Are you sure you don't overrun the Nmi routine?

 

I mean ,are you sure of what you do in the routine takes less than 1/60 of seconds ?

 

You can check that by for instance changing the border color at start of the nmi and at end. Like that you will see how much time your routine consume on screen .

Link to comment
Share on other sites

Yeah that is my understanding that sprites[1].x or anything shouldn't cause video corruption as it's not changing anything only updatesprites does.

 

Ok, i'll try the colour method. But I was getting the same video corruption before I had anything in the nmi_routine.

 

The only solution that has worked is to put in delay(1) several times but it slows down the game too much as I needed 3- delay(1) in the main loop to make it stable.

 

I'd say that video corruption is the biggest problem in programming colecovision. I've solved it in the first 2 games I made but it was a similar hassle to get a balanced program that was stable.

 

 

 

I don't think the Sprites[] instruction touch the VRAM , it is only when you do the updatesprites that the data are transfered.

 

Are you sure you don't overrun the Nmi routine?

 

I mean ,are you sure of what you do in the routine takes less than 1/60 of seconds ?

 

You can check that by for instance changing the border color at start of the nmi and at end. Like that you will see how much time your routine consume on screen .

Link to comment
Share on other sites

I believe the main problem your main bank is overfilling, so I would check with an Hexediter to see how many FF is in a bank.

 

EDIT: Or stack overflow. I just remembered from Knightmare. It is caused by doing too many things at once or program GOTO out of the sub routine. Having everything in nmi{} will overflow the stack if the tasks isn't going to be finish once the CPU run out of cycle.

 

I don't have anything in nmi{} unless there's something that need to run while in pause(); like Star Ocean sprite animation.

 

To prevent nmi corruption.

 

while(gameloop==1){

delay(1);

updatesprites(0,64);//(so it doesn't tear midpoint of the screen)

video stuff like doing small video write to tiles.

 

then sprite table stuff and game logic that can take CPU time without bugging the video chip.

 

I did see the scene with there's a chest in one of your video, and there was part of the chest printed else where due to video corruption. It tells me that the chest been drawn every frame instead of on queue. During the scene drawing routine, you can put_frame(chestclose); or put_frame(chestopen); on that scene, and the turn on screen. Once the player open the chest, change chest from 0 to 1, call chest drawing routine to change the chest from close to open. Something like

 

void DrawChest(void){

if (chest==0){delay(1);put_frame(chestclose,24,20,4,4);

if (chest==1){delay(1);put_frame(chestopen,24,20,4,4);

}

 

delay(1) will makes sure that you're on the fresh frame and corruption shouldn't happen.

Edited by Kiwi
Link to comment
Share on other sites

Kiwi's got the best suggestions there... but to control video corruption you just need to understand the NMI. That may require you to dig below the libraries and look at your CRT0 and the code it calls.

 

The trick is to remember that at the end of a video frame, that NMI code will execute, no matter what else you were doing. In the middle of a memory copy? Too bad! Changing a video register? Not anymore! So it's important to be aware of what the possible race conditions in your code actually are.

 

I took a different approach in libti99Coleco, since I was coming from a different mindset. I refuse to let the NMI own my code. It took a few iterations to get the race conditions out, but it does what I intended now... I can trivially enable and disable the interrupts in my code to ensure that anything I'm doing that might touch the VDP can't be interrupted. Note that I don't disable interrupts in the VDP itself -- that's also a racey operation since it takes two writes, but instead my NMI function doesn't touch the VDP unless it's allowed to by the flags set in the system.

 

A normal NMI, besides the processing that you request, also clears the interrupt from the VDP by reading the VDP status register. Reading the VDP status register has the side effect of changing the internal VDP address. This is why, even if you don't do anything else, the NMI can corrupt your VDP access. I do away with that except when the program explicitly permits it. The net effect of this similar to changing the NMI to a maskable interrupt (by design, that was my goal). If the status register is NOT read (because the program forbid the NMI to do it), then the interrupt will not retrigger until it is. This means my program can delay handling the NMI during critical sections. (Of course, if it delays for longer than 1/60th of a second, this is still safe, but the program will slow down).

 

To see what I did, have a look here. The concept is portable to other CRT0s.

 

https://github.com/tursilion/libti99coleco/blob/master/crt0.s -- look at nmi:

https://github.com/tursilion/libti99coleco/blob/master/vdp.h -- scroll down and look at VDP_INT_ENABLE and VDP_INT_DISABLE -- ENABLE is particularly important because it needs to check for missed interrupt. :)

 

For stack overflow, you just have to watch what you're doing. In a debugger you can tell how roughly much stack you're using by checking the RAM that seems changed.

Link to comment
Share on other sites

Hmmm. a little over my head.

 

If I replace part of my crt0.s with your nmi code and get it working I would call the nmi or disable the nmi by:

 

VDP_INT_ENABLE; VDP_INT_DISABLE;

 

similar to: SWITCH_IN_BANK7;

 

I would:

 

VDP_INT_DISABLE;

put_char(x,y,c);

VDP_INT_ENABLE; <<--this doesn't actually call the nmi but would allow it to be called like normal.

 

and for good measure here is my current crt0.s

 

; crt0.s for Colecovision cart
;; external routines
;;.globl set_snd_table
;; global from this code
.globl _buffer32
;;.globl snd_areas
.globl _no_nmi
.globl _vdp_status
.globl _nmi_flag
.globl _joypad_1
.globl _keypad_1
.globl _joypad_2
.globl _keypad_2
.globl spinner_enabled
.globl _spinner_1
.globl _spinner_2
;; global from C code
.globl _main
.globl _nmi
.globl l__INITIALIZER
.globl s__INITIALIZER
.globl s__INITIALIZED
;; Ordering of segments for the linker - copied from sdcc crt0.s
.area _HOME
.area _CODE
.ascii "LinkTag:Fixed\0" ; also to ensure there is data BEFORE the banking LinkTags
.area _main ; to work around a bug that drops the first AREA: tag in the ihx
.area _data_arrays_rom ; the data arrays for room data in tanks area
.area _sound2 ;sound fx
.area _INITIALIZER
.area _GSINIT
.area _GSFINAL
;; banking (must be located before the RAM sections)
.area _bank1
.ascii "LinkTag:Bank1\0"
.area _main2 ;more tanks routines
.area _bank2
.ascii "LinkTag:Bank2\0"
.area _main_init_bank2 ;inits for tanks
.area _GRAPHICS_TABLES ;tanks tables
.area _room_graphics ;room designs tanks / four_GOALS_004 tank castle picture
.area _bank3
.ascii "LinkTag:Bank3\0"
.area _cake ;four_GOALS_002 - escape prison picture
;four_GOALS_003 - the plane picture
.area _titlescreen_bank3
.area _data_messages_rom
.area _bank4
.ascii "LinkTag:Bank4\0"
.area _storming_building_arrays
.area _storming_building
.area _bank5
.ascii "LinkTag:Bank5\0"
.area _storming_water
.area _storming_water_arrays
.area _scroll_bg_array
.area _bank6
.ascii "LinkTag:Bank6\0"
.area _storming_init_bank6 ;storming inits & level drawing
.area _storming_building_graphics ;storming graphics tables
.area _bank7
.ascii "LinkTag:Bank7\0"
.area _sound7 ;pMusic_7 pMusic_2 = 4 songs & dog fx
.area _player ;the music player
;; end of list - needed for makemegacart. Must go before RAM areas.
; This isn't used by anything else and should not contain data
.area _ENDOFMAP
.area _DATA
.area _INITIALIZED
.area _BSEG
.area _BSS
.area _HEAP
;; TABLE OF VARIABLES (IN RAM)
.area _DATA
_buffer32::
.ds 32 ; buffer space 32 [7000-701F]
snd_addr::
.ds 11 ; sound addresses [7020-702A]
snd_areas::
.ds 61 ; 5 sound slots + NULL (00h) [702B-...]
_no_nmi::
.ds 1
_vdp_status::
.ds 1
_nmi_flag::
.ds 1
_joypad_1::
.ds 1
_keypad_1::
.ds 1
_joypad_2::
.ds 1
_keypad_2::
.ds 1
spinner_enabled::
.ds 1
_spinner_1 = 0x73eb
_spinner_2 = 0x73ec
.module crt0
;; CARTRIDGE HEADER (IN ROM)
.area _HEADER(ABS)
.org 0x8000
.db 0xaa, 0x55 ; no default colecovision title screen => 55 AA
.dw 0 ; no copy of sprite table, etc.
.dw 0 ; all unused
.dw _buffer32 ; work buffer
.dw 0 ; ??
.dw start_program ; start address for game coding
.db 0xc9,0,0 ; no RST 08 support
.db 0xc9,0,0 ; no RST 10 support
.db 0xc9,0,0 ; no RST 18 support
.db 0xc9,0,0 ; no RST 20 support
.db 0xc9,0,0 ; no RST 28 support
.db 0xc9,0,0 ; no RST 30 support
jp spinner_int ; RST38 - spinner interrupt
jp _nmi_asm
;; CODE STARTS HERE WITH NMI
.area _CODE
_nmi_asm:
push af
ld a,#1
ld (_nmi_flag),a ; set NMI flag
;;;
call 0x1fdc ; get VDP status
ld (_vdp_status),a
;;;
ld a,(_no_nmi) ; check if nmi() should be
or a ; called
jp nz,nmi_exit
inc a
ld (_no_nmi),a
push bc
push de
push hl
push ix
push iy
ex af,af'
push af
exx
push bc
push de
push hl
call 0x1f76 ; update controllers
ld a,(0x73ee)
and #0x4f
ld (_joypad_1),a
ld a,(0x73ef)
and #0x4f
ld (_joypad_2),a
ld a,(0x73f0)
and #0x4f
ld (_keypad_1),a
ld a,(0x73f1)
and #0x4f
ld (_keypad_2),a
call decode_controllers
call _nmi ; call C function
;;call 0x1f61 ; play sounds
;;call 0x1ff4 ; update snd_addr with snd_areas
pop hl
pop de
pop bc
exx
pop af
ex af,af'
pop iy
pop ix
pop hl
pop de
pop bc
xor a
ld (_no_nmi),a
nmi_exit:
ld a,(spinner_enabled)
or a
jr z,nmi_end
ei
nmi_end:
pop af
ret
keypad_table::
.db 0xff,8,4,5,0xff,7,11,2,0xff,10,0,9,3,1,6,0xff
; joypads will be decoded as follows:
; bit
; 0 left
; 1 down
; 2 right
; 3 up
; 4 button 4
; 5 button 3
; 6 button 2
; 7 button 1
; keypads will hold key pressed (0-11), or 0xff
decode_controllers:
ld ix, #_joypad_1
call decode_controller
inc ix
inc ix
decode_controller:
ld a,0(ix)
ld b,a
and #0x40
rlca
ld c,a
ld a,b
and #0x0f
or c
ld b,a
ld a,1(ix)
ld c,a
and #0x40
or b
ld 0(ix),a
ld b,a
ld a,c
cpl
and #0x0f
cp #8
jr nz,no_button_3
ex af,af'
ld a,b
or #0x20
ld b,a
ex af,af'
no_button_3:
cp #4
jr nz,no_button_4
ex af,af'
ld a,b
or #0x10
ld b,a
ex af,af'
no_button_4:
ld 0(ix),b
;;;;
ld a,c
cpl
and #0x0f
ld e,a
ld d,#0
ld hl,#keypad_table
add hl,de
ld a,(hl)
ld 1(ix),a
ret
spinner_int:
push af
push hl
call 0x1f88
pop hl
pop af
ei
reti
start_program:
im 1 ; interrupt mode -> rst 38h
di
xor a ; clear carry
ld bc,#0x3b8 ; ram size left
ld hl,#0x7000 ; starting from 7000
ld de,#0x7001
ld (hl),a
ldir ; zero-fill bss
ld bc,#0xFFFE ; switch in code bank
ld a,(bc) ; note that this does NOT set the local pBank
call gsinit ; Initialize global variables.
ld h,#0 ; set dummy sound table
; update snd_addr with snd_areascall set_snd_table
ld hl,#0x0033 ; initialise random generator
ld (0x73c8),hl
; set screen mode 2 text
call 0x1f85 ; set default VDP regs 16K
ld de,#0x4000 ; clear VRAM
xor a
ld l,a
ld h,a
call 0x1f82
; call main rountine
jp _main
.area _GSINIT
gsinit::
ld bc, #l__INITIALIZER
ld a,b
or a,c
jr z, gsinit_next
ld de, #s__INITIALIZED
ld hl, #s__INITIALIZER
ldir
gsinit_next:
.area _GSFINAL
ret
;

 

Kiwi's got the best suggestions there... but to control video corruption you just need to understand the NMI. That may require you to dig below the libraries and look at your CRT0 and the code it calls.

 

The trick is to remember that at the end of a video frame, that NMI code will execute, no matter what else you were doing. In the middle of a memory copy? Too bad! Changing a video register? Not anymore! So it's important to be aware of what the possible race conditions in your code actually are.

 

I took a different approach in libti99Coleco, since I was coming from a different mindset. I refuse to let the NMI own my code. It took a few iterations to get the race conditions out, but it does what I intended now... I can trivially enable and disable the interrupts in my code to ensure that anything I'm doing that might touch the VDP can't be interrupted. Note that I don't disable interrupts in the VDP itself -- that's also a racey operation since it takes two writes, but instead my NMI function doesn't touch the VDP unless it's allowed to by the flags set in the system.

 

A normal NMI, besides the processing that you request, also clears the interrupt from the VDP by reading the VDP status register. Reading the VDP status register has the side effect of changing the internal VDP address. This is why, even if you don't do anything else, the NMI can corrupt your VDP access. I do away with that except when the program explicitly permits it. The net effect of this similar to changing the NMI to a maskable interrupt (by design, that was my goal). If the status register is NOT read (because the program forbid the NMI to do it), then the interrupt will not retrigger until it is. This means my program can delay handling the NMI during critical sections. (Of course, if it delays for longer than 1/60th of a second, this is still safe, but the program will slow down).

 

To see what I did, have a look here. The concept is portable to other CRT0s.

 

https://github.com/tursilion/libti99coleco/blob/master/crt0.s -- look at nmi:

https://github.com/tursilion/libti99coleco/blob/master/vdp.h -- scroll down and look at VDP_INT_ENABLE and VDP_INT_DISABLE -- ENABLE is particularly important because it needs to check for missed interrupt. :)

 

For stack overflow, you just have to watch what you're doing. In a debugger you can tell how roughly much stack you're using by checking the RAM that seems changed.

Link to comment
Share on other sites

You should move this:

 

        call    0x1fdc                   ; get VDP status
        ld      (_vdp_status),a

 

After the nmi_exit: label, this way:

 

 

nmi_exit:
        call    0x1fdc                   ; get VDP status
        ld      (_vdp_status),a

 

This would ensure your internal code DOESN'T get interrupted by another NMI.

 

And if you already moved all VDP code into the NMI it means you're trying to do too much screens updates, maybe you could consider doing some things in odd frames and some in even frames.

Link to comment
Share on other sites

That will certainly help -- the VDP can't start a "new" NMI until the status is cleared, so doing it right at the end guarantees your work is done first, even if you run long. If you're doing all your work in the NMI that will probably save you faster than restructuring your code for my approach.

 

For what it's worth, in the TI community we tend to run with interrupts disabled /all the time/, and just briefly enable them at set times (such as during the main loop) to let interrupt code happen. It's just doing the same thing the other way in order to guarantee the code is more deterministic. You'll see in my library I even have a "VDP_INT_POLL" which just called enable/disable one after the other. If an interrupt is pending, that lets it happen, and if not, we'll check later. ;) (In effect it turns it into a polling situation rather than an interrupt at all.) When you're worried about vertical blank, though, you need to make sure you respond quickly, so generally my code will execute one loop and then wait for the interrupt to do its job (this also frame-locks you nicely.)

 

So.. yeah.. Nanochess for the win. That should fix your corruption issue as long as you aren't doing fast VDP writes - if you run over a frame it will just slow down. If you just replace that code with mine you'll lose controller and spinner reads.

 

The other possible cause of corruption is doing fast writes to the VDP - this is not legal except immediately after the NMI. If you have any (hopefully your library calls them out), then make sure they are at the top of your NMI function and don't copy too many bytes, 1500-2000 bytes is about the top end since your NMI is doing other things before giving you control. Otherwise they tend to drop bytes.

  • Like 1
Link to comment
Share on other sites

Thanks I Just tried moving this after the nmi_exit:. was a little more stable for longer but the corruption happens again after awhile. Trying a mixture of things including additional delays, splitting some updates to every second frame etc.

 

I think I need to simplify what is going on on the screen. A liitle too much at times. I already had to cut back quite a bit. I originally had 10 soldiers & 10 cannons & a tank & a plane all shooting at the same time.

 

You should move this:

        call    0x1fdc                   ; get VDP status
        ld      (_vdp_status),a

After the nmi_exit: label, this way:

nmi_exit:
        call    0x1fdc                   ; get VDP status
        ld      (_vdp_status),a

This would ensure your internal code DOESN'T get interrupted by another NMI.

 

And if you already moved all VDP code into the NMI it means you're trying to do too much screens updates, maybe you could consider doing some things in odd frames and some in even frames.

 

 

 

That will certainly help -- the VDP can't start a "new" NMI until the status is cleared, so doing it right at the end guarantees your work is done first, even if you run long. If you're doing all your work in the NMI that will probably save you faster than restructuring your code for my approach.

 

For what it's worth, in the TI community we tend to run with interrupts disabled /all the time/, and just briefly enable them at set times (such as during the main loop) to let interrupt code happen. It's just doing the same thing the other way in order to guarantee the code is more deterministic. You'll see in my library I even have a "VDP_INT_POLL" which just called enable/disable one after the other. If an interrupt is pending, that lets it happen, and if not, we'll check later. ;) (In effect it turns it into a polling situation rather than an interrupt at all.) When you're worried about vertical blank, though, you need to make sure you respond quickly, so generally my code will execute one loop and then wait for the interrupt to do its job (this also frame-locks you nicely.)

 

So.. yeah.. Nanochess for the win. That should fix your corruption issue as long as you aren't doing fast VDP writes - if you run over a frame it will just slow down. If you just replace that code with mine you'll lose controller and spinner reads.

 

The other possible cause of corruption is doing fast writes to the VDP - this is not legal except immediately after the NMI. If you have any (hopefully your library calls them out), then make sure they are at the top of your NMI function and don't copy too many bytes, 1500-2000 bytes is about the top end since your NMI is doing other things before giving you control. Otherwise they tend to drop bytes.

 

 

You should move this:

        call    0x1fdc                   ; get VDP status
        ld      (_vdp_status),a

After the nmi_exit: label, this way:

nmi_exit:
        call    0x1fdc                   ; get VDP status
        ld      (_vdp_status),a

This would ensure your internal code DOESN'T get interrupted by another NMI.

 

And if you already moved all VDP code into the NMI it means you're trying to do too much screens updates, maybe you could consider doing some things in odd frames and some in even frames.

Link to comment
Share on other sites

I found another possible thing I overlooked. get_char(x,y,c) reads from the vram and wasn't inside the nmi. I'll round them up and see if that helps.

 

 

Corruption shouldn't happen now if you already moved all the display code inside the NMI routine, unless you read the VDP status inside your subroutines or call a subroutine that reads the VDP status.

 

Simplify your code.

Link to comment
Share on other sites

Corruption shouldn't happen now if you already moved all the display code inside the NMI routine, unless you read the VDP status inside your subroutines or call a subroutine that reads the VDP status.

Really? It's a bad idea to interact with the VDP this way from inside the NMI routine?

Link to comment
Share on other sites

Really? It's a bad idea to interact with the VDP this way from inside the NMI routine?

 

Absolutely no. Mecha-9 works with all VDP updates inside the NMI routine, at most 288+128 = 416 bytes are handled in each frame.

 

Most games can be simplified to this kind of screen update.

 

The games which don't require constant screen update can make it outside the NMI routine using a kind of disable_nmi/enable_nmi routines (semaphore-style routines) or waiting for a screen update before doing any VDP usage.

Link to comment
Share on other sites

Right.. the thing you want to be careful of is as soon as you read the VDP status register, that unlocks the VDP and allows a new NMI to start -- it doesn't matter if you are still "in" the NMI code. It matters only that you acknowledged it to the VDP.

 

It shouldn't be a problem to have lots of moving objects... it's only VDP access that can cause corruption. Perhaps you can move your game logic to after the VDP has been updated (to ensure the VDP access happens during the blank), and have the game itself work on CPU RAM-based variables instead. In my own stuff I double-buffer the sprite table that way, for instance. The NMI loads the sprite table but the CPU version is free to be accessed at any time.

Link to comment
Share on other sites

Really? It's a bad idea to interact with the VDP this way from inside the NMI routine?

 

You have just to take care of what kind of console you target.... That 's not a real issue for non european developper. But on european consoles you can do more between 2 NMI , and then if you try your game on a U.S console... there is chance you got lot of VDP Ram corruptions.

 

In fact , that's frustrating for me... for Battle of Hoth and Smurf Challenge, i had to remove few effects from the games to make it work on 60hz consoles.

 

Except that, it is the method the more safe i found to avoid VDP issues.

Link to comment
Share on other sites

At least you're lucky on the Coleco, once you have a ratio that works, it's likely to work on all systems. The TI-99 has separate clocks for the VDP and the 9900, so if you push it right to the edge on that machine variances in the timing will still cause problems on some people's computers. (The saving grace is it's very hard to push to the limit on that machine ;) ).

 

But seriously, there are only two things that cause VDP corruption: either the NMI interrupts a VDP operation in your code and corrupts the VDP state, or you read/write to the VDP too quickly during active screen display. (Besides actual bugs, as I found in my own code ;) ).

 

You can defeat the first by ensuring that NMIs can not happen while you are doing VDP access. This is called a race condition because you are racing the NMI - hoping that you finish what you're doing before it triggers. You can use a flag to keep the NMI handler off the VDP, or you can make sure your VDP access happens in the NMI before you clear the VDP status, so it can't retrigger. Code with the mentality that every single statement can be interrupted in the middle unless you have done something to prevent it. While VDP interrupts are the most blatant, if you have any other structures that are handled in both your NMI and your main code, remember to take care of those too.

 

The second is just a hardware limitation - the Z80 is faster than the VDP, and if you access the VDP too quickly, it will lose data. This goes for both reads and writes, as well as turning around a read after setting an address. The VDP needs a random value up to 8 microseconds to access memory on behalf of the CPU (it's not really random, but from the perspective of the CPU it can't be predicted). If you read or write faster, the data simply doesn't transfer (ie: bytes are lost). However, this is only true when the VDP is drawing the screen, so during the vertical blank (ie: immediately after the NMI triggers), or if the screen is disabled, all VDP cycles are available for CPU access, and you can read or write at full speed.

 

The easy solution to this is just always use the slower access method, always delay between accesses. This works fine, but sometimes you need the performance. In that case, you can use faster access methods immediately after the NMI fires, while the blank is still active. I don't know what the rough limit is on the Coleco, but you have to finish that fast copy before the VDP starts drawing again to avoid losing data. The tricky part to remember is you won't lose data all the time, it entirely depends on when in the VDP state machine your byte lands. Alternately, for large things like loading character sets or such, just blank the VDP screen and you can write at full speed for the entire frame.

 

As for the PAL/NTSC issues... it's true that PAL has more time in the blank and in the frame itself. Assuming you saw corruption, and not just slowdown, you should be able to get a lot of data to the VDP even at 60hz... Make sure you're doing your VDP accesses first in the NMI and save calculations/sound until after the VDP is taken care of, where timing is less important. Some emulators like BlueMSX will display the scanline in the debugger - I'm not sure if it's dead on but it seems close enough to use as a check. Just breakpoint after your VDP work is done and see what scanline you're at. Or, again, just use the slower VDP copies. They will always work.

 

I'll shut up and go away now. ;)

Link to comment
Share on other sites

No luck.

 

My new theory is it is excessive bank switching with no delays

 

captured these 2 separate crashes, one on a real adam & 1 on coolcv. I overlaid the screens and the character pattern on crash is identical! everytime. Like the video memory location got switched and always to the same new position. other than a black or green tank obscuring a couple of tiles the pattern is identical.

 

Screen from real adam (above photo) overlaid on the same character screen from coolcv

tanks messup real system

actual crash in coolcv

tanks messup emulator

Link to comment
Share on other sites

This is just my guess based on my own experience.

 

We all experience video corruption from time to time. This one reminds me of three cases I experienced long time ago in my projects : many times I just typed the parameters in the wrong order or typed the wrong data table name to be uploaded in VRAM, I also saw what updating VRAM in a bad timing can do and now I tend to use NMI for updating VRAM in batch, and I also saw corruption when uploading the sprites table in VRAM without the byte at the end saying where that table ends ($E0 aka 240) which normally is taking care of by using a library routine but I forgot that time this detail mentioned in the TMS 9918/9928 documentation.

 

It's all good frustration to see a problem like this but so satisfying when it's finally fixed.

Link to comment
Share on other sites

It happens it the middle of scene not during vram loading of the tiles or sprites.

 

There is sprite patterns being changed but no new ones being loaded just changed to another already predefined pattern. I had a problem on my first project with sprite 48 for some reason. That always came in corrupted. I think it specifically happens when my tank is moving and changing sprite patterns.

 

in fact if i cycle though the sprites in your ICVGM303 it'll crash when I get to sprite 48 often times. ?? I have no idea why. It's gremlin.

 

I'm going to try a couple more things such as no sprite pattern changeing and see if it does any better.

 

sure is frustrating. But like you said I'll be happy when it's solved and won't give up on it.

 

gremlin

 

This is just my guess based on my own experience.

 

We all experience video corruption from time to time. This one reminds me of three cases I experienced long time ago in my projects : many times I just typed the parameters in the wrong order or typed the wrong data table name to be uploaded in VRAM, I also saw what updating VRAM in a bad timing can do and now I tend to use NMI for updating VRAM in batch, and I also saw corruption when uploading the sprites table in VRAM without the byte at the end saying where that table ends ($E0 aka 240) which normally is taking care of by using a library routine but I forgot that time this detail mentioned in the TMS 9918/9928 documentation.

 

It's all good frustration to see a problem like this but so satisfying when it's finally fixed.

Link to comment
Share on other sites

good point but i'm in the clear there.

 

# SWITCH ROM_AD COL_AD FREE NAME
= ====== ======= ====== ===== ===============
7 0xFFF8 0x00000 0xC000 3759 Bank7
6 0xFFF9 0x04000 0xC000 1857 Bank6
5 0xFFFA 0x08000 0xC000 482 Bank5
4 0xFFFB 0x0C000 0xC000 401 Bank4
3 0xFFFC 0x10000 0xC000 249 Bank3
2 0xFFFD 0x14000 0xC000 312 Bank2
1 0xFFFE 0x18000 0xC000 1681 Bank1
0 0xFFFF 0x1C000 0x8000 538 Fixed

I know I mentioned before, you may want to check your ROM size using hexeditor to see if the data are being overlapped. I don't know if the linker will throw a warning or error if one of the bank is overfilled.

Link to comment
Share on other sites

That looks very much like a VDP register being altered... are you sure you are not trying at some point to write to a negative VDP address (either by accident or on purpose?) I did this in Super Space Acer, on purpose, naively thinking that the address register would be masked anyway, and forgetting that a negative VDP address would be interpreted as register writes.

Link to comment
Share on other sites

It happens it the middle of scene not during vram loading of the tiles or sprites.

 

There is sprite patterns being changed but no new ones being loaded just changed to another already predefined pattern. I had a problem on my first project with sprite 48 for some reason. That always came in corrupted. I think it specifically happens when my tank is moving and changing sprite patterns.

 

in fact if i cycle though the sprites in your ICVGM303 it'll crash when I get to sprite 48 often times. ?? I have no idea why. It's gremlin.

 

I'm going to try a couple more things such as no sprite pattern changeing and see if it does any better.

 

sure is frustrating. But like you said I'll be happy when it's solved and won't give up on it.

 

 

I would be very happy to reproduce this error message with ICVGM on my computer. I've never seen this before.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...