raphnet.net banner

RetroChallenge, April 2018



twitter@raphnetlabs
Contents: Summary | Introduction | April 3 update | April 9 update | April 15 update | April 22 update | April 30 final update | References | Disclaimer

Summary

One unlikely duo

One unlikely duo

This will be my first participation in the RetroChallenge, and I would like to attempt the following:

And if I have enough time:

goto top Up


Introduction

At the time personal computers were a new thing, decicated video monitors were expensive. Composite video output was therefore a common feature on early PCs, so users could conveniently use the TV they already owned. Just like for video games consoles.

Computers such as IBM PCs had composite video output on CGA cards, but limited graphical capabilities (4 colors) and no joystick ports, unless an expansion card was installed. On the other hand, computers from the Tandy 1000 series (from 1984) offered 16-color graphics, two built-in game ports and enhanced sound hardware. Much better suited for video games!

The NES came out in 1985 and as everyone certainly knows, it also connected directly to TVs. The NES also came with a light gun accessory known as the NES Zapper, which worked by detecting light emitted by the screen. To avoid interference from other light sources in the room, the NES Zapper is tuned for TV displays operating at a ~60 Hz vertical refresh rate, and ~15 kHz horizontal refresh rate.

It think the Zapper should be usable even if a Tandy 1000 or CGA card is driving the TV instead of a NES console. The video timing should be identical, at least within some small tolerances that probably won't matter. So I think it is an interesting thing to try, and given that the Tandy 1000 EX computer I will use and the NES Zapper are from the same era, it is at least plausible that someone could have bought a NES Zapper to use with his Tandy 1000, provided a suitable adapter and compatible game(s) had existed. (Maybe it did an I don't know!?)

For RC2018/04, I'd like to try to make the required Zapper adapter and code a game that uses it. For the game itself, I will reuse the graphics library and tooling I made for RATillery. But the game code itself, the graphics (and music if I get there) will be new material created from April 1 to April 30 as per the RC2018/04 rules.

If I have time (and if I don't, I'll probably do it later anyway) I would like to test a theory. Because most VGA monitors do not support horizontal refresh rate of 15 kHz (too low), the Zapper probably cannot be used (will test to confirm). But then, I think there is still a way to use an unmodified Zapper with a VGA monitor and I would like to give it a try.

goto top Up


April 3 update

I first tested Duck Hunt to confirm that the Zapper still works and that it had no problem with the monochrome monitor I planned to use for the time being. (I plan to begin using the large color TV later). Duck Hunt worked fine, so I connected the Tandy 1000 EX and prepared a floppy for the project.

Testing Duck Hunt

Testing Duck Hunt

Setting up

Setting up



Next target: Building an adapter. Using the joystick port was obvious due to the presence of a 5 volt output (to supply the Zapper) and two button inputs (one for the trigger, one for the light detect output).

I located a NES controller extension and the end of an old DIN-6 cable in my parts bin. After figuring out the color code for both cables, I wired them into an adapter according to the table below:

Joystick portZapperComment(s)
5v5v
GNDGND
Button 1TriggerPulled to GND by the trigger switch
Button 2DetectContinuously pulled to GND, but released when light is detected. (more on this later)
I was very careful to double check everything, I even probed the Tandy 1000 EX joystick port to make sure the 5v supply was where I expected it.
DIN6 from the bin

DIN6 from the bin

Figuring out the pinout

Figuring out the pinout

Checking the voltage

Checking the voltage

Wired adapter

Wired adapter



The state of the two joystick button inputs can be monitored by polling port 0x201. The two significant bits reflect the button state. I wrote a simple basic program displaying the value from port 0x201. I arranged for a blinking white rectangle to be present so I could point the Zapper at it and see the effect.

I also had a scope wired to the zapper detection output to observe its behavior and timing closely.

Input test program

Input test program

Trigger released, no light

Trigger released, no light

Trigger on, no light

Trigger on, no light

Trigger on, light detected

Trigger on, light detected



So it worked! But light detection was not reliable. In the rightmost picture above (the one showing a white rectangle) the number 176 was only shown occasionally and briefly.

I looked at the output from the scope and saw that the signal went high for approx. 2ms. And after thinking about it, it makes sense. While our eyes does not see it, the block on the screen is never completely lit at once. The Zapper actually sees the beam drawing each new scanline as the previous one is already faded. Once the beam is lower than the block, the zapper does not detect anything until the beam reaches the bottom of the screen and vertical refresh takes place (every 16ms / 60Hz).

As I am writing this, I suspect that aiming the Zapper at a completely white screen would result in a much longer pulse. (Need to try this!)
Test with an oscilloscope

Test with an oscilloscope

The pulse

The pulse



The code I had used so far was reading the IO port and displaying the value in a loop. Interpreted BASIC being already slow, writing numbers on the screen in decimal between each sampling of the IO port is obviously much too slow.

I made a new program that polled the port in a tight loop without screen updates, waiting for a trigger first (worked very well) and then waiting for light (using a loop as a timeout to detect misses). But it still failed to reliably sense pulses from the Zapper.

So detecting a 2ms pulse by polling an IO port in BASIC is probably impossible on this slow machine. But it does not matter, I was using BASIC only as a temporary convenience for quick experimentation. The game will of course be written in assembly. So the next thing I will do now is to implement a simple test in assembly, so see if it will be fast enough and reliable.


goto top Up


April 9 update

As planned, I redid the test in assembly language and now everything seems to work fine even though it is an incomplete implementation where one can easily cheat by aiming at non-black screen areas, or even by pointing the Zapper at another screen when pulling the trigger... But at least the feasibility of the project is now confirmed!

Miss

Miss

Hit

Hit



The procedure I used is detailed below:

Correcting the problems mentioned in the first paragraph should be possible by doing the hit detection in two passes:
Only shots meeting the two conditions, that is no light in the first frame followed by light in the second, shall be treated as hits.

If there are several targets, drawing the white rectangles can be done in extra frames. The exact moment where the zapper finally sees light again will depend on which target was hit.

All this is in fact pretty much how the game Duck Hunt does things, except that in Duck Hunt, the whole screen becomes black instead of only the targets. Why is that? Is it for technical reasons? My next tests will probably tell. To be continued...

goto top Up


April 15 update

This week, still no sight of a game, but the basics are under control and I think I have a good grasp of the capabilities of the Zapper. I should have a playable demo soon!

Passive object rejection

The "cheating" issues I wrote about in my previous update are solved as planned. Drawing black for one frame, white in the next, while monitoring the zapper to make sure its output follows exactly. Consider the pictures below: The square in the middle of the screen is not shootable (passive), while the one near the upper left corner is. Pulling the trigger at the former reports a miss, but shooting at the later is correctly recognized as a hit. An important concept for games, where shooting passive objects such as the scenery or score should do nothing.

Shootable object, reported as a hit

Shootable object, reported as a hit

Non-shootable object, reported as a miss

Non-shootable object, reported as a miss

Passive object rejection code

Passive object rejection code


Of course, this technique causes flickering of the shootable object when pulling the trigger.

Getting the Y value by timing

The simple detection test I wrote about last week waits for vertical retrace and then monitors the Zapper during the full frame. Why? Because the exact moment the detection pulse will come is not known in advance. It depends on the vertical position of the object being shot at.

So this week it occurred to me that by timing how long it takes from the end of vertical refresh until light is first detected, it should be possible to get the Y value corresponding to the screen area being pointed at by the Zapper. I wrote a new test program showing a tall vertical bar, added code to count after how many cycles first light appears, and code to time how many cycles the frame lasts. (I use the term cycle here since it refers to iterations of the loop that polls the zapper for light and the video status register. The exact counts depend on CPU speed.)

On this 200 pixel high display, Y is roughly equivalent to [cycles_to_first_light] * 200 / [frame_total_cycles]. The test program computes the Y value and draws a red/pink square at the current height next to the vertical bar. In practise there is a scale error, which I suppose is caused by the 40 extra black lines the video adapter probably adds to generate 240p NTSC video. I'll investigate and correct for this only if I need to use it in a game and require extra precision...

I wonder if some NES games used this technique, I have not done research on games other than Duck Hunt.. In any case, for a game with a great number of targets that are not vertically aligned, the process of drawing the targets in black (or blanking the screen) and then in white could be done in parallel rather than one by one.

Y=162

Y=162

Y=125

Y=125

Y=78

Y=78

Y=42

Y=42



While I was at it, I added colors to test how the Zapper reacts. They are all reliably detected, even from good distance. On a black background, it might be possible to get away without even drawing white over the object...

Also, on the right of the screen, the series of lines of increasing thickness was to test how many scanlines were required for detection. But it turns out even a single scanline is detected.

goto top Up


April 22 update


goto top Up


April 30 final update


goto top Up


References


goto top Up


Disclaimer

I will publish the schematics for the adapter, as well as the source code for the demo game. The following disclaimer will apply.
I cannot be held responsible for any damages that could occur to you or your equipment while following the procedures present on this page. Also, I GIVE ABSOLUTELY NO WARRANTY on the correctness and usability of the informations on this page. Please note, however, that the procedures above have worked in my case without any damages or problems.

Now you cannot say that I did not warn you :)

goto top Up


Trademarks used in this site are the property of their respectives owners.
Copyright © 2002-2018, Raphaël Assénat
Website coded withWebsite coded with vimLast update: April 15, 2018 (Sunday)