summaryrefslogtreecommitdiffstats
path: root/2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt
diff options
context:
space:
mode:
Diffstat (limited to '2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt')
-rw-r--r--2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt630
1 files changed, 630 insertions, 0 deletions
diff --git a/2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt b/2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt
new file mode 100644
index 0000000..3d65563
--- /dev/null
+++ b/2020/subtitles/emacsconf-2020--27-state-of-retro-gaming-in-emacs-chip8--vasilij-wasamasa-schneidermann.vtt
@@ -0,0 +1,630 @@
+WEBVTT
+
+00:00:00.880 --> 00:00:04.520
+Hello everyone and welcome to my talk, "The State of Retro Gaming and Emacs."
+
+00:00:06.960 --> 00:00:08.639
+First of all, a little bit about myself.
+
+00:00:08.639 --> 00:00:12.000
+My name is Vasilij Schneidermann. I'm 28 years old.
+
+00:00:12.000 --> 00:00:14.719
+I work as a cyber security consultant at msg systems,
+
+00:00:14.719 --> 00:00:17.359
+and test other people's web applications
+
+00:00:17.359 --> 00:00:20.160
+and review the source code for security problems.
+
+00:00:20.160 --> 00:00:22.080
+You can reach me by email.
+
+00:00:22.080 --> 00:00:25.039
+I have my own self-hosted git repositories,
+
+00:00:25.039 --> 00:00:28.160
+and I have a blog where you can occasionally find new posts by me
+
+00:00:28.160 --> 00:00:32.160
+on all kinds of things, not just Emacs things.
+
+00:00:32.160 --> 00:00:34.559
+The motivation about this one...
+
+00:00:34.559 --> 00:00:37.600
+I found that Emacs is the ultimate procrastination machine,
+
+00:00:37.600 --> 00:00:39.600
+and there are lots of fun demonstrations.
+
+00:00:39.600 --> 00:00:41.200
+I'll go over a few of them.
+
+00:00:41.200 --> 00:00:45.840
+For example, someone made a thing to order salad for himself online,
+
+00:00:45.840 --> 00:00:48.239
+so he doesn't have to walk over to the shop.
+
+00:00:48.239 --> 00:00:51.760
+There's plenty of IRC bots. There's some game things.
+
+00:00:51.760 --> 00:00:55.600
+There's an emulator for the Z-machine
+which you can use to play zork.
+
+00:00:55.600 --> 00:00:57.440
+And so I asked myself, at this point,
+
+00:00:57.440 --> 00:00:59.920
+can you actually emulate retro games at 60fps?
+
+00:00:59.920 --> 00:01:02.079
+I looked around a bit and found some projects,
+
+00:01:02.079 --> 00:01:06.159
+but none that were actually able to do it at 60fps.
+
+00:01:06.159 --> 00:01:08.000
+So I set out to do my own one,
+
+00:01:08.000 --> 00:01:09.200
+and looked out for a console
+
+00:01:09.200 --> 00:01:11.119
+that you can actually emulate at that speed,
+
+00:01:11.119 --> 00:01:14.690
+using Emacs with its very, very limited rendering.
+
+00:01:16.320 --> 00:01:19.200
+And here's the project, chip8.el.
+
+00:01:19.200 --> 00:01:20.560
+It's pretty much finished.
+
+00:01:20.560 --> 00:01:24.000
+It clocks into under 1000 source lines of code.
+
+00:01:24.000 --> 00:01:26.159
+It supports the superchip 8 extensions.
+
+00:01:26.159 --> 00:01:27.280
+It runs at full speed.
+
+00:01:27.280 --> 00:01:29.600
+All games behave okay, as far as I'm concerned,
+
+00:01:29.600 --> 00:01:31.680
+and yeah, I'm pretty happy with it.
+
+00:01:31.680 --> 00:01:34.479
+It's very much the hello world of emulation,
+
+00:01:34.479 --> 00:01:40.880
+and I might, maybe, do some other emulation projects in the future.
+
+00:01:40.880 --> 00:01:43.360
+Now, for the section which is the longest:
+
+00:01:43.360 --> 00:01:45.439
+bunch of fun facts about chip8.el
+
+00:01:45.439 --> 00:01:49.200
+which I've learned during this project.
+
+00:01:49.200 --> 00:01:51.759
+So what the hell is chip8 anyway?
+
+00:01:51.759 --> 00:01:54.960
+First of all, unlike many other emulation game things,
+
+00:01:54.960 --> 00:01:56.799
+it's not a console, but a VM.
+
+00:01:56.799 --> 00:02:00.000
+It was designed for easy porting of home
+computer games.
+
+00:02:00.000 --> 00:02:02.320
+It wasn't terribly successful,
+
+00:02:02.320 --> 00:02:05.439
+but there's still a small community of enthusiasts writing games for it,
+
+00:02:05.439 --> 00:02:09.119
+and there are even a few demos.
+
+00:02:09.119 --> 00:02:11.039
+This VM has system specs.
+
+00:02:11.039 --> 00:02:14.720
+It has a very, very simple 8-bit cpu with 16 registers,
+
+00:02:14.720 --> 00:02:17.280
+and 36 fixed-size instructions.
+
+00:02:17.280 --> 00:02:19.680
+You have a whole 4 kilobyte of RAM.
+
+00:02:19.680 --> 00:02:22.080
+You have a stack with 16 return addresses.
+
+00:02:22.080 --> 00:02:25.760
+The resolution is 64 by 32 black/white pixels.
+
+00:02:25.760 --> 00:02:28.000
+Rendering is done by drawing sprites.
+
+00:02:28.000 --> 00:02:29.200
+These are drawn in XOR mode,
+
+00:02:29.200 --> 00:02:31.840
+meaning that if you draw a sprite and set a bit,
+
+00:02:31.840 --> 00:02:35.040
+it just flips over from black to white or white to black.
+
+00:02:35.040 --> 00:02:39.360
+For sound, you have a monotone buzzer that can just beep at one frequency.
+
+00:02:39.360 --> 00:02:43.120
+Most unusually, there's a hexadecimal keypad as input,
+
+00:02:43.120 --> 00:02:48.480
+so the keys are basically zero to nine and a to f.
+
+00:02:48.480 --> 00:02:50.720
+So how does this whole thing work?
+
+00:02:50.720 --> 00:02:52.400
+It runs at an unspecified speed.
+
+00:02:52.400 --> 00:02:53.040
+You'll probably have to do some fine-tuning
+
+00:02:53.040 --> 00:02:56.080
+to find the speed you're happy with.
+
+00:02:56.080 --> 00:02:58.080
+Sound and delay timers exist.
+
+00:02:58.080 --> 00:03:01.120
+They count down at 60fps down to 0.
+
+00:03:01.120 --> 00:03:05.120
+This is done so that you can play a sound at some specific time.
+
+00:03:05.120 --> 00:03:07.840
+The game itself is loaded with a fixed offset into RAM.
+
+00:03:07.840 --> 00:03:10.480
+The program counter is set to exactly that offset,
+
+00:03:10.480 --> 00:03:11.920
+and from there it enters the game loop
+
+00:03:11.920 --> 00:03:13.280
+where it decodes an instruction,
+
+00:03:13.280 --> 00:03:14.800
+executes it for the side effects,
+
+00:03:14.800 --> 00:03:18.130
+and just loops and does this ad infinitum.
+
+00:03:19.599 --> 00:03:22.720
+So the game loop was the first thing where we ran into problems.
+
+00:03:22.720 --> 00:03:25.120
+The usual game approach is to do stuff,
+
+00:03:25.120 --> 00:03:26.640
+figure out how long to wait,
+
+00:03:26.640 --> 00:03:29.280
+wait for exactly that much, and repeat.
+
+00:03:29.280 --> 00:03:31.680
+This doesn't work well in Emacs at all, because, well,
+
+00:03:31.680 --> 00:03:34.959
+user input, basically.
+
+00:03:34.959 --> 00:03:37.760
+Emacs is designed to just do whatever it needs to do
+
+00:03:37.760 --> 00:03:39.040
+whenever you enter user input
+
+00:03:39.040 --> 00:03:42.319
+instead of doing things at one specific time.
+
+00:03:42.319 --> 00:03:46.640
+If you try to do interruptable sleep, well, you get unpredictable behavior.
+
+00:03:46.640 --> 00:03:50.480
+For example, it can be the timer doesn't run at all at the next time
+
+00:03:50.480 --> 00:03:52.560
+because you've accidentally cancelled it.
+
+00:03:52.560 --> 00:03:55.120
+If you do uninterruptable sleep, it freezes instead ,
+
+00:03:55.120 --> 00:03:56.720
+which isn't what we want either.
+
+00:03:56.720 --> 00:04:00.560
+So I went for timers, which forced me to do inversion of control,
+
+00:04:00.560 --> 00:04:02.560
+meaning that I have to write code in the style
+
+00:04:02.560 --> 00:04:04.879
+where it just calls timer,
+
+00:04:04.879 --> 00:04:06.560
+and this allows this input to happen
+
+00:04:06.560 --> 00:04:11.040
+and for things to progress at roughly the speed I want to.
+
+00:04:11.040 --> 00:04:14.159
+So there's the timer function which is called at 60fps
+
+00:04:14.159 --> 00:04:17.359
+and I have to be very careful to not do too much in it.
+
+00:04:17.359 --> 00:04:21.305
+And, say, this function executes CPU cycles,
+
+00:04:21.305 --> 00:04:26.479
+decrement the sound/delay registers, and redraw the screen.
+
+00:04:26.479 --> 00:04:28.800
+So to map this whole system to Emacs Lisp,
+
+00:04:28.800 --> 00:04:31.199
+I've used just integers and vectors
+
+00:04:31.199 --> 00:04:33.120
+which contain even more integers.
+
+00:04:33.120 --> 00:04:35.040
+This is used for the RAM, registers,
+
+00:04:35.040 --> 00:04:37.040
+return stack, key state, screen,
+
+00:04:37.040 --> 00:04:38.508
+and so on and so forth.
+
+00:04:38.508 --> 00:04:41.520
+Basically, what you would do if you were writing C.
+
+00:04:41.520 --> 00:04:43.360
+All of this is stored in global variables.
+
+00:04:43.360 --> 00:04:45.600
+I'm not using any lists at all.
+
+00:04:45.600 --> 00:04:48.400
+As a side effect, there's no consing going on at all.
+
+00:04:48.400 --> 00:04:50.080
+There are no extra objects created
+
+00:04:50.080 --> 00:04:53.199
+which would trigger garbage collection processes.
+
+00:04:53.199 --> 00:04:55.600
+Getting this right was rather tricky, actually,
+
+00:04:55.600 --> 00:04:58.240
+and there were some hidden garbage collection problems
+
+00:04:58.240 --> 00:05:01.759
+which I had to resolve over time.
+
+00:05:01.759 --> 00:05:03.759
+So, decoding instructions.
+
+00:05:03.759 --> 00:05:06.800
+For this, you have to know that all instructions are two bytes long,
+
+00:05:06.800 --> 00:05:08.880
+and the arguments are encoded inside them.
+
+00:05:08.880 --> 00:05:11.440
+For example, the jump to address instruction
+
+00:05:11.440 --> 00:05:15.120
+is encoded as one and three hex digits.
+
+00:05:15.120 --> 00:05:18.400
+The type is extracted masking with #xF000
+
+00:05:18.400 --> 00:05:20.400
+and then shifting it by 12 bits.
+
+00:05:20.400 --> 00:05:23.520
+Mask means you perform the binary AND.
+
+00:05:23.520 --> 00:05:28.400
+You can do the same with the argument by masking with #0xFFF and no shift.
+
+00:05:28.400 --> 00:05:30.560
+If you do this long enough, you'll find common patterns.
+
+00:05:30.560 --> 00:05:32.639
+For example, addresses are always encoded like this
+
+00:05:32.639 --> 00:05:34.880
+using the last three nibbles.
+
+00:05:34.880 --> 00:05:36.160
+In the code, you'll find a big cond
+
+00:05:36.160 --> 00:05:40.070
+which dispatches on the type and executes it for the side effects.
+
+00:05:41.440 --> 00:05:45.919
+For testing, I've initially just executed the ROM until I've hit C-g,
+
+00:05:45.919 --> 00:05:49.039
+and then use the debug command to render the screen to a buffer.
+
+00:05:49.039 --> 00:05:53.199
+Later on, I found tiny ROMs that just display a static test screen,
+
+00:05:53.199 --> 00:05:57.280
+for example, logo, and looked whether it looked right.
+
+00:05:57.280 --> 00:05:58.800
+I added instructions as needed
+
+00:05:58.800 --> 00:06:00.720
+and went through more and more and more ROMs.
+
+00:06:00.720 --> 00:06:04.000
+And later I wrote a unit test suite as a safety net.
+
+00:06:04.000 --> 00:06:07.840
+This unit test suite, it just sets up an empty emulator state,
+
+00:06:07.840 --> 00:06:09.199
+executes some instructions,
+
+00:06:09.199 --> 00:06:14.880
+and then looks whether the expected side effects have happened.
+
+00:06:14.880 --> 00:06:18.319
+For debugging, I usually use edebug, but this was super ineffective,
+
+00:06:18.319 --> 00:06:21.600
+because, well, you don't really want to step through big cons
+
+00:06:21.600 --> 00:06:23.680
+doing side effects for every single cycle,
+
+00:06:23.680 --> 00:06:26.880
+when it can take like 100 cycles for things to happen.
+
+00:06:26.880 --> 00:06:29.680
+Therefore I've set up logging.
+
+00:06:29.680 --> 00:06:32.639
+Whenever I logged something and couldn't figure out the error,
+
+00:06:32.639 --> 00:06:37.039
+I compared my log output with the instrumented version of another emulator,
+
+00:06:37.039 --> 00:06:40.479
+and if the logs diverge, then I have figured out where the bug lies
+
+00:06:40.479 --> 00:06:42.720
+and could look deeper into it.
+
+00:06:42.720 --> 00:06:44.960
+Future project idea might be a chip 8 debugger,
+
+00:06:44.960 --> 00:06:49.440
+but I doubt I'll ever go into it.
+
+00:06:49.440 --> 00:06:51.759
+For analysis, I initially wrote a disassembler,
+
+00:06:51.759 --> 00:06:54.400
+which is a very simple thing but super tedious,
+
+00:06:54.400 --> 00:06:56.639
+especially if you wanted to add advanced functionality,
+
+00:06:56.639 --> 00:06:58.720
+for example, analysis or thinking of what part is data,
+
+00:06:58.720 --> 00:07:00.000
+what part is code.
+
+00:07:00.000 --> 00:07:03.360
+I had this great idea for using the radare 2 framework
+
+00:07:03.360 --> 00:07:06.479
+and adding analysis and disassembly plug-in for it.
+
+00:07:06.479 --> 00:07:08.400
+So I looked into this. Found, okay,
+
+00:07:08.400 --> 00:07:10.319
+you can write plugins in C
+
+00:07:10.319 --> 00:07:12.639
+but also in Python, so I wrote one in Python,
+
+00:07:12.639 --> 00:07:14.720
+and then discovered there's actually an existing one in core,
+
+00:07:14.720 --> 00:07:18.400
+which you have to enable explicitly by passing an extra argument.
+
+00:07:18.400 --> 00:07:21.680
+I've tried it and found it's not exactly as good as my own one,
+
+00:07:21.680 --> 00:07:24.160
+so I improved this one and submitted pull requests
+
+00:07:24.160 --> 00:07:26.610
+until it was at the same level.
+
+00:07:28.080 --> 00:07:30.720
+Rendering was the trickiest part of this whole thing,
+
+00:07:30.720 --> 00:07:34.319
+because, well, I decided against using a library.
+
+00:07:34.319 --> 00:07:37.120
+Not like there would have been any usable library for this.
+
+00:07:37.120 --> 00:07:40.880
+My usual approach of creating SVG files was too expensive.
+
+00:07:40.880 --> 00:07:45.120
+It just created too much garbage and took too long time.
+
+00:07:45.120 --> 00:07:47.360
+I then tried creating mutating strings.
+
+00:07:47.360 --> 00:07:52.479
+This was either too expensive, just like SVGs, or too complicated.
+
+00:07:52.479 --> 00:07:57.280
+I tried changing SVG tiles, which created gaps between the lines.
+
+00:07:57.280 --> 00:08:00.720
+Then I tried to create an xpm file which was backed by a bool vector
+
+00:08:00.720 --> 00:08:02.400
+and mutating this bool vector,
+
+00:08:02.400 --> 00:08:04.000
+but the image caching effect
+
+00:08:04.000 --> 00:08:06.479
+made it just every nth frame to appear,
+
+00:08:06.479 --> 00:08:08.879
+which wasn't good either.
+
+00:08:08.879 --> 00:08:11.440
+Then I had the idea to just use plain text
+
+00:08:11.440 --> 00:08:13.120
+and paint the individual characters
+
+00:08:13.120 --> 00:08:14.800
+with a different background color.
+
+00:08:14.800 --> 00:08:17.120
+This had perfect, perfect performance.
+
+00:08:17.120 --> 00:08:19.280
+There were many optimization attempts until I got there,
+
+00:08:19.280 --> 00:08:21.199
+and it was very, very stressful.
+
+00:08:21.199 --> 00:08:26.160
+I wasn't sure whether I would ever get to accept the performance at all.
+
+00:08:26.160 --> 00:08:28.560
+For sound you only need to do a single beep,
+
+00:08:28.560 --> 00:08:31.280
+so technically, it shouldn't be difficult to emulate it.
+
+00:08:31.280 --> 00:08:33.039
+However, doing this is hard because
+
+00:08:33.039 --> 00:08:37.200
+Emacs officially only supports synchronous playback of sounds.
+
+00:08:37.200 --> 00:08:41.360
+But there's also Emacs process, which you can launch in asynchronous way.
+
+00:08:41.360 --> 00:08:44.720
+So I looked into it and found that mplayer has a slave mode
+
+00:08:44.720 --> 00:08:48.640
+and mpv supports listing on the fifo for commands.
+
+00:08:48.640 --> 00:08:53.760
+So I've created a pipe, started a paused MPV in loop mode,
+
+00:08:53.760 --> 00:08:56.560
+and always send in pause and unpause command to the FIFO,
+
+00:08:56.560 --> 00:08:58.000
+and that way I could control
+
+00:08:58.000 --> 00:09:02.640
+when to start beeping and stop beeping.
+
+00:09:02.640 --> 00:09:04.160
+So yeah, that's it so far.
+
+00:09:04.160 --> 00:09:07.200
+It was a very educational experience.
+
+00:09:07.200 --> 00:09:10.320
+I have tried out a bunch of games which were,
+
+00:09:10.320 --> 00:09:14.320
+well, I almost say the worst ports of classic games I've ever tried.
+
+00:09:14.320 --> 00:09:15.680
+It wasn't terribly fun to play them,
+
+00:09:15.680 --> 00:09:18.555
+but was fun to improve the emulator
+
+00:09:18.555 --> 00:09:21.760
+until, well, things worked good enough.
+
+00:09:21.760 --> 00:09:25.120
+I've learned a lot about how computers work at this level,
+
+00:09:25.120 --> 00:09:28.880
+so, maybe, maybe I'll in the future make another emulator,
+
+00:09:28.880 --> 00:09:34.000
+but I'm not sure whether anything more advanced, like an Intel 8080 emulator,
+
+00:09:34.000 --> 00:09:36.560
+will actually run in Emacs fast enough,
+
+00:09:36.560 --> 00:09:37.839
+but it's still an interesting idea,
+
+00:09:37.839 --> 00:09:40.800
+because then you could actually have an OS inside Emacs
+
+00:09:40.800 --> 00:09:43.120
+and fulfill that one specific meme.
+
+00:09:43.120 --> 00:09:45.440
+But if I try to do most serious stuff,
+
+00:09:45.440 --> 00:09:47.040
+I'll probably use Chicken Scheme,
+
+00:09:47.040 --> 00:09:49.920
+which is my preferred language for serious projects,
+
+00:09:49.920 --> 00:09:53.279
+and write a NES game emulator.
+
+00:09:53.279 --> 00:09:57.839
+And that's it. Thank you.