I just want to clarify that I have full respect and admiration for Logiker's work here. When I show that the program can't be edited, that's not a flaw in the program: that's just an interesting consequence of the optimizations used, and it was part of how I discovered the optimizations used. Further, it turns out that this program WILL load on SOME real Commodore 64s. I was using my 64C which as shown, generally starts up with RAM set to patterns like AA AA 55 55 with some variations. This requires further study, but some C64s will boot with patterns with more FF FF 00 00, and I think those zeros will allow the program to load and re-link without crashing. We might look into this more in a future video.
Though as observed in other comments, even that isn't enough. The state of memory is whatever it is at startup plus the value of all bytes that have been set since. One simple load followed by new would be enough to create a memory image that is incompatible with the tricks demonstrated. This is why it's best (though perhaps boring) to stick to the documented ways of doing things! :)
A C64 would load and run a terminal program but a 64C, the later design, would not. That is, unless you first load and run something like SpeedScript, type a few random characters, then quit out of that program and load the terminal program. Then running is possible without a hang and everything works fine. I discovered that the terminal program does not initialize RAM properly, and the values in RAM would inhibit success of the terminal program. Then I wrote a short ML routine that would initialize RAM properly. That avoided loading a whole 7 kb word processor program just to init a few bytes. This video reminded me of the situation with the program loadings of the past, so that's why I decided to post this to all. Hope this helps!
You got to admire the fact that these computer used to be “simple” enough so you (well robin) can explain in such detail how it works….and then discover that hey you can chop a few bytes off here and there and it will recalculate some and just load and run it anyway….
Wonderful analysis :) I wonder why the screen colors changed, though, what is happening with the editor under the hood due to the AA AA ? Logiker could have left out the horizontal and vertical bars to save two bytes as well, to fix the final 00 00 sequence, but it would have changed the result somewhat. I added the extended graphics "plus" for seven characters to use and changed the order ( + - | L R U D), and it looks slightly different (using CCS 64 v3.9.2 with USB VIC20 Keyboard via USB).
I do not have real hardware to test it on at this point, sadly, but if the contest is writing a 32 byte program that runs, I think I can see why the choices that were made were used. 1. The rules probably do not require listing of the program in some technical metaphysical sense, so the fact that list doesn't work on real hardware may not be an issue. 2. Editing of the program is also probably not a pre-requisite, so no bad there. 3. The bad link to the next line will never be used to actually read memory, so it would not be an issue in running the program. 4. The fact that the program is not terminated with a NULL next line link after the zero of the last line is not an issue because the GOTO will always start scanning at the beginning of the program where it will find line zero without needing to follow any next line links. Great analysis though, and a great job on the part of the original programmer finding exactly the right rules to violate to come up with something that works (if my theories are correct). These types of contests are as much about what the rules don't say as they are about what the rules do say, not unlike the International Obfuscated C Coding Competition. Edit: This all presupposes that LOAD"FILE",8,1 is an acceptable rule. Because really, a "non-conforming but functional" BASIC program is not really much different conceptually than using ,8,1 to load a 32 byte ML program.
Thanks, I should have made it more clear in the video that the only actual criticism I have is if the program won't run on real hardware. All the other things (like being unable to edit) were just interesting side effects of the optimizations and were actually more-or-less how I discovered some of the optimizations. Out of habit I think I used words like "fixing" but that implies the program is broken, when really it's just optimized in unusual/interesting ways.
@@8_Bit agreed. The indeterminate state of RAM after boot, plus whatever might be left over from previous runs of other programs, means that this is a way to have uninitialized memory that BASIC isn't prepared to deal with as you've clearly illustrated. Regardless, good analysis.
Nice, I never thought those space optimizations were possible. Also, on the topic of programming, it would be cool to see a video about accessing the disk drive with assembly.
Great video, Robin. Not terminating the BASIC program could be even more dangerous if there's not a zero past $81f for a while (in uninitialized RAM). The machine will hang after the load trying to find the end of the "statement" at $820.
2:42 It might look better to use RND(1) or RND(π) since positive arguments produce "better" random numbers. 7:01 If he's assuming that $00s will be present after the program, he could skip the line-terminating $00 as well. 7:23 It makes perfect sense - if you know exactly how Commodore BASIC programs are stored. 11:30 Luckily, BASIC does a CLR when you enter a new line that initializes all of the other BASIC pointers. 12:35 Clever. He can skip the low byte of the link address since BASIC relinks the lines after LOADing and the only important thing is that the link high byte not be $00. 14:24 Oh, it's the file length and not the number of bytes loaded into RAM that counts.
RND(0) uses the timer as a seed to generate a random number each time, while RND(1) continues from the previous seed, which on reset is a default number, so the first run would always show the same maze. Only negative numbers set a specific seed to produce a random number, which would always be the same (since you'd reset the seed each time). I don't think that after reset, RND(PI) gives a different outcome then RND(1) ; use negative PI instead, but in line 0, and use line 1 for the loop to get the same random maze on each run.
Interesting! I found a bug in a demo a while back that manifested depending on initial memory conditions. In that case, it was the seed of an LFSR random number generator. The memory location was initialized to zero in a popular emulator, a value that is usually an invalid seed for an LFSR. This caused the demo to act very strangely. The actual bug was using the wrong addressing mode to set the seed. It was reading a zero page memory address instead of loading the accumulator with a literal value. As long as that memory location was non-zero, the bug was masked.
I never thought I'd hear of a hex edited basic program. Sadly my C64 is out of commission at the moment. I was trying to replace the cartridge port. I got half the pins desoldered before I needed a break, but my back was so sore I decided to shelve it for another weekend. Sadly I'm about 99% sure I broke a trace. I depend on the cartridge port because I only have a U2+. So without it the C64 is basically useless. Hopefully the remaining pins are less troublesome and no more broken traces. Actually every pin is at least partially desoldered. It's just the last little bit of cleanup giving me trouble.
I've always wondered what causes "undefined" memory to look the way it does on power-up. When I use my Amiga 1200 to play uninitialized memory through the speaker, I get some interesting echo effects to go with the usual 32-bit boundary checkerboard pattern. I'd love to investigate that further, but no way am I going to circuit-bend my precious 1200. 8)
Free mini-game hidden in this video... when the program runs, try to pause on a full frame of maze before the program is stopped. I did manage it around 18 min mark.
After watching I decided to look up 'orthogonal'. I had always just thought it meant truly 2D. It seems orthogonal means it consists of lots of right angles :S
Specifically that the 2D area's coordinates uses two right angled axis with identical spacing on a flat surface. If the display made the C64 pixels exactly 1:1 in real size, then looking at the diagonal maze at a 45 degree angle, it's still orthogonal from a certain point of view :)
I don't believe it's the listing that needed to be 32 bytes but the total space it took up in memory or on disk. It memory, PRINT is already stored just as its token. D'oh, beaten by 17 seconds 🤦
Is there a place in memory that is guaranteed to have 2 zeroes? zero page or ROM. Using that location as the pointer to the next line would make it run on both emulator and hardware? Or does c64 basic scan the line until it reaches zeroes and doesn't use the offset for linear execution?
If you type the program in, it will work fine. The problem was the trick Logiker used to make the file only 32 bytes on disk by truncating the end of the file, which assumed RAM would be initialized a certain way. When you type it in by hand, BASIC automatically adds the zeros in RAM that were cut from the disk file to get it down to 32 bytes.
I really wish those 8-bit computers had assemblers built-in. Heck, when I upgraded to an Amiga, I didn't even get BASIC anymore, let alone an assembler.
@@Waccoon Geez, really? The first-gen model came with one of the best BASICs ever made. I guess once Microsoft got into the GUI business themselves, they didn't want to keep helping the competition.
@@stevethepocket You're thinking of Microsoft AmigaBASIC, which did not ship with the first Amigas. The A1000 came with ABASIC, which was written by MetaComCo (the same company that wrote the Amiga's infamously lousy filesystem, TripOS). ABASIC was pretty awful, and was incredibly unreliable. I ported a number of ABASIC programs to AMOS in the 90's.
@@Waccoon - TRIPOS formed the *entirety* of AmigaDOS, not just the file system. And while I wouldn't call it great, I wouldn't call it lousy either. Keep in mind this is during the age when MS-DOS 4/5 will annihilate a partition (of particular sizes) during a chkdsk or Apple thought it was a good idea to use the upper bits of the address registers for idiotic status flags even though that was the exact example that the MC68000UM used when they were telling you NOT to use those bits. Yeah, it's annoying to have to deal with BCPL pointers, and they could have used sector/cluster addressing instead of byte addressing, and the checksum/next entries in the user data block were inefficient (but made recovery a lot easier), but that was par for the course. I mean, we just finished watching a video where direct sector editing was done on the C64...which is probably the king of disk disasters, unfortunately.
@@LordRenegrade TripOS was not the whole DOS system. Amiga originally commissioned another company to write their DOS layer, but that team failed to deliver on time, and TripOS was used as a backup plan. A lot of stuff was added and merged into TripOS by the Amiga team, even though the official user manuals were very similar. However, back then people called the system AmigaDOS instead of AmigaOS, so there has always been a lot of confusion as to what went where. TripOS assumed a hard drive was always available. Man, I hated not being able to get directory listings just because the DIR executable was not on a boot disk. That was stupid.
wouldn't say it's fixing as such.. it's just that in these modern times OSes need to protect programs from spying on each other and usually only give out memory that's been zeroed out, where as retro hw with direct RAM access gets whatever the cell happened to have. so it's not that the emulator is doing anything incorrect, it just fails to crash.
they could randomize the initial RAM of course to make non-deterministic assumptions crash more often ..but it wouldn't really be failing like the real thing unless they'd model in semiconductor thermodynamics. that's what I'd call an emulator ;)
@@snooks5607 They already do set the virtual RAM to emulate the real thing. However, as Robin noted (and apparently the vast majority of commenters missed) different revisions of the C64 initialized RAM to different values. So the default in VICE is to emulate one of the common patterns (FF FF 00 00) by default, but you can EASILY change it to 55 55 AA AA with a few mouse clicks.
Robin, what would be the best reference guide to do something like this for the Vic20? I’m trying to track down a C64 locally but not having much luck. I can find Vic20”s and Atari 8bits easy.
For this specific kind of information with the inner workings of BASIC, Programming the VIC by Raeto West is probably the best reference guide, but the actual book is rare and expensive now. But a PDF is easy to find. Mapping the VIC, and the VIC-20 Programmer's Reference Guide are also generally excellent, and probably a bit easier to buy, but don't have much info about this very particular bit of nerdery.
FWIW, a LOT of the stuff that works on the C64 also works on the VIC20. It's kind of an embarrassment how little BASIC was enhanced from the VIC20 to the C64.
Yeah, BASIC is nearly identical on the VIC20 and C64; the ROMs are in different locations and there's some adjustments for the different sized screens and so on, but there's not a lot of difference. On the bright side, this choice on the C64 allowed for a nice clean memory map which makes it very easy to extend or replace BASIC. BASIC 7.0 on the C128 is much more rich, but also quite a bit slower, and has a very convoluted memory map by comparison.
Considering the two bytes of the line number are 00 00 for line 0, could you not just set the next line pointer to be 0803 to terminate the program without relying on memory being zeroed?
That's a good thought but the C64 still does a relink after the load and will "fix up" this pointer. So 0803 gets changed to 0820 based on seeing the 0 (end of statement) at 081f. Then it'll happily keep going looking at the uninitialized memory at 0820 for another statement like before.
Sounds like emulator authors should initialise memory with random values. Wouldn't take much code. Just a for loop with a call to rand() in it. Wouldn't add much time to startup unless you're trying to emulate something modern with gigabytes of RAM.
They already do. As a matter of fact, VICE can perfectly emulate any type of Commodore machine on boot, you just have to tell it what the values are and how often they repeat/invert.
Yeah, the GOTO is slightly faster than RUN and because they're both tokenized down to a single byte (like all C64 BASIC commands), they're the same size. And arguably GOTO is "more correct" because that's the intended way to endlessly loop in BASIC, rather than re-RUNning over and over again. Not that it actually matters :)
@@futu1983 I mean, it sort of is. Just like you can represent binary in fewer characters using hexadecimal. But you're right, it's not traditional compression with Huffman trees, etc.
Here's another shorter version of what I think is an orthogonal variation. Not sure if it qualifies as orthogonal: ua-cam.com/video/CzS9Svu6uRk/v-deo.html
The tricks here to cram this basic program into 32 bytes are sure commendable. But it's technically not 32 bytes, since it uses part of the basic interpreter and c64 bios to achieve its goal.
Nearly all of these restricted-size compos allow free use of whatever ROM (or RAM) the host system happens to have in an agreed-upon standard configuration. Usually the restriction is on the file size. I've seen compos that have other restrictions but they often seem fairly arbitrary and difficult to determine rule compliance too.
I crammed an autorun machine code version in 27 bytes, but I didn't use any really interesting tricks. I had thought of an evil trick to try and shave off two bytes by reusing existing bytes as non-printing characters in the character table, but ... hmm. I guess I could shave it down to 26 bytes by moving stuff around (01 is a non-printing character).
Okay, I managed to cram it down to 23 bytes, and confirmed it worked on real hardware (C128 in C64 mode). I did a lot of crazy stuff I'm not proud of to make it fit in 23 bytes and I don't think I can squeeze it down any further. I had to hunt around zero page until I found a byte pair guaranteed to be set to 255,0 (alternatively, 0,1 would have worked). PEEK(58) and PEEK(59) fit the bill. I also had to do a lot of wrangling to ensure the stack bytes I was recycling would always be non-printing characters. It ended up in the MIDDLE of my character lookup table, and I needed to make sure both the autorun return address and the future return address that my JSR CHROUT would push would be non-printing characters. It's incredibly ugly. OTOH, my bigger 27 byte version is relatively elegant and robust, and can be readily expanded to include more characters.
Yeah, to be clear, all BASIC commands are tokenized to one byte, so RUN, GOTO, GOSUB and whatever else just take one byte each. Normally GOTO requires a line number parameter, which is stored in plain PETSCII text, so that's an extra 1 to 5 bytes, but due to a quirk of the interpreter, an absent line number is equivalent to GOTO 0, and it happens to execute slightly faster than RUN too.
1:40 If you squint and look at it "diagonally" you can see that it's both the cubicles and jail cells in TRON 17:50 Agree, side thumb from me. Any time an emulator doesn't do what the hardware does means it should be rounded to the nearest real hardware ;-) 19:00 You mean there's a chance this wasn't Turbo Macro Pro? Crazy talk. I do like the graphic at the top.
I noticed I was saying it strangely during editing; I think I added a little on-screen caption about it at one point. I don't know where I picked that up from! I'll say it (more) correctly in the next video which should be released really soon.
@@8_Bit you could add shift+'+'. Bold plus or cross. and have more diverse labyrinth. It will add one more byte. Also you cold leave horizontal and vertical line with bold plus and have similar effect and shorten it for 3 bytes. Just 'ad-just' MID value.
Perhaps I missed something: Does the program run on real hardware if the exact sequence of commands is: LOAD"10PRINT",8,1 RUN In my other comment I theorize that this code should run, even though listing and editing are problematic.
No, it will not. The three chopped off bytes need to have been initialized to 0 in order for the program to run. Evidently these bytes will be initialized to zero in an emulator, but not on real hardware. [edit added:] Ah, actually I read your other comment and maybe it would work. Hmm ...
Not necessarily but maybe (even probably I'd guess). It depends on the contents of uninitialized RAM at $820 and beyond. After the load, if the pointer at $820 is not 0, BASIC will scan to the end of what it thinks is another valid BASIC statement. I've found that if it doesn't hit a zero in 255 bytes it'll get stuck in an infinite loop and never return the BASIC prompt. If there's a zero somewhere before that you should get a prompt back and it should run. On my Ultimate 64 it seems that memory often has zeros in this whole area but not always. Sometimes it's pretty random looking garbage.
@@johnscarfone I think it wouldn't matter what is at 0820 or beyond, as the GOTO will prevent the interpreter from ever having to read any bytes thereafter. Edit: By which I mean: As long as you LOAD with ,8,1, and do not try to list the basic program but only run it, I think it will work on real hardware. Ultimately, it is easy to test in an emulator by writing garbage bytes at 0801, 0820, and 0821 (if I am remembering the offsets off the top of my head, as well as the variable table pointer / end of BASIC pointer in zero page. I think I shall try it.
@@casaderobison2718 It doesn't while you're running the program but what I described happens after the load finishes but before the prompt comes back. This is the relink process where that first skipped byte gets fixed up that Robin was talking about. A side effect of not having those two zeros at the end is that it'll try to fix up more (garbage) BASIC statement(s). You can make it get stuck during this process with well chosen garbage. 🙂
@@johnscarfone But the relink doesn't happen if you use ,8,1. If one loads with ,8,1 there is no relink, so does run work at that point on real hardware.
RUN and GOTO are basically interchangeable in this case, yes. I believe I benchmarked the two against each other and GOTO is marginally faster, so I'd choose GOTO. As for GOTO being harmful, no Commodore BASIC programmer believes that :)
Lol, if you think GOTO is "harmful", I wonder what you think about the machine code JMP instruction - and why it, and all CPU instruction equivalents, are NOT considered "harmful". You might also give yourself a serious shock once you dive into the assembly output of virtually every compiled program...!!! Important concept: so-called 'spaghetti code' is...everywhere, by design.
@@ChrisM541 Um, what? en.wikipedia.org/wiki/Considered_harmful Maybe consider that it's you that is lacking knowledge before leaping in with your diatribe?
Wow... all that work for a pretty ugly maze. Sadness for sure there. The detailed exploration, on the other hand, exposed the "fraud" and educated people interested in these details in a very nice way! The side scroller routine? Pretty cool! Question: Why does printing this way to the bottom of the screen result in scrolling TWO lines instead of one? This is a Commodore BASIC ROM question.... so far, it's a mystery. Can we patch it to only scroll one line?
@@snooks5607 As I said the actual program was pretty much garbage though I will say I liked the approach of using mid$ and more characters. It gives me an idea for some other things. It was the analysis and the tools and techniques along with the particular details Robin added which were the real value and interest here.
@@csbruce That makes more sense than anything else I suppose. But C64 was never intended to have an 80 column display. Now if someone were to say "this is an artifact of the Commodore BASIC origins and having been ported from an 80 column display system" I'd be like ah... okay. But how do we fix it? It's strange behavior and I don't like it at all.
I just want to clarify that I have full respect and admiration for Logiker's work here. When I show that the program can't be edited, that's not a flaw in the program: that's just an interesting consequence of the optimizations used, and it was part of how I discovered the optimizations used. Further, it turns out that this program WILL load on SOME real Commodore 64s. I was using my 64C which as shown, generally starts up with RAM set to patterns like AA AA 55 55 with some variations. This requires further study, but some C64s will boot with patterns with more FF FF 00 00, and I think those zeros will allow the program to load and re-link without crashing. We might look into this more in a future video.
Fascinating! Great video Robin.
This is the kind of content you only find here. Keep 'em coming!
What I'm getting from this is, we should write our emulators to fill the RAM with random bytes on boot.
You can configure vice for different patterns.
Though as observed in other comments, even that isn't enough. The state of memory is whatever it is at startup plus the value of all bytes that have been set since. One simple load followed by new would be enough to create a memory image that is incompatible with the tricks demonstrated. This is why it's best (though perhaps boring) to stick to the documented ways of doing things! :)
Some emulators do that, i know fceux (NES) lets you choose all 00 all ff or random
Always so cool to see what kind of optimization and general programming tricks people come up with. Very nice. 👍
The new patrons scroller at the end is looking great!
A C64 would load and run a terminal program but a 64C, the later design, would not. That is, unless you first load and run something like SpeedScript, type a few random characters, then quit out of that program and load the terminal program. Then running is possible without a hang and everything works fine. I discovered that the terminal program does not initialize RAM properly, and the values in RAM would inhibit success of the terminal program. Then I wrote a short ML routine that would initialize RAM properly. That avoided loading a whole 7 kb word processor program just to init a few bytes. This video reminded me of the situation with the program loadings of the past, so that's why I decided to post this to all. Hope this helps!
That was the most polite “calling out somebody’s bs” I’ve ever seen. 😂
I added a pinned comment to clarify that it appears this program will work on some real C64s due to differences in the way RAM powers up.
@@8_Bit Oh, I know. It’s a fascinating rabbit hole you found there. I’m more or less joking. More or less. Haha!
You got to admire the fact that these computer used to be “simple” enough so you (well robin) can explain in such detail how it works….and then discover that hey you can chop a few bytes off here and there and it will recalculate some and just load and run it anyway….
The pattern reminds me of fabric weave. Like it was woven randomly. Very cool!
Basically random.
Theseus (c64 basic game) has the same pattern only solvable.
Wonderful analysis :) I wonder why the screen colors changed, though, what is happening with the editor under the hood due to the AA AA ?
Logiker could have left out the horizontal and vertical bars to save two bytes as well, to fix the final 00 00 sequence, but it would have changed the result somewhat.
I added the extended graphics "plus" for seven characters to use and changed the order ( + - | L R U D), and it looks slightly different (using CCS 64 v3.9.2 with USB VIC20 Keyboard via USB).
Fantasic breakdown as always, thank you so much Robin! Your C64 skills are legendary!
I do not have real hardware to test it on at this point, sadly, but if the contest is writing a 32 byte program that runs, I think I can see why the choices that were made were used.
1. The rules probably do not require listing of the program in some technical metaphysical sense, so the fact that list doesn't work on real hardware may not be an issue.
2. Editing of the program is also probably not a pre-requisite, so no bad there.
3. The bad link to the next line will never be used to actually read memory, so it would not be an issue in running the program.
4. The fact that the program is not terminated with a NULL next line link after the zero of the last line is not an issue because the GOTO will always start scanning at the beginning of the program where it will find line zero without needing to follow any next line links.
Great analysis though, and a great job on the part of the original programmer finding exactly the right rules to violate to come up with something that works (if my theories are correct). These types of contests are as much about what the rules don't say as they are about what the rules do say, not unlike the International Obfuscated C Coding Competition.
Edit: This all presupposes that LOAD"FILE",8,1 is an acceptable rule. Because really, a "non-conforming but functional" BASIC program is not really much different conceptually than using ,8,1 to load a 32 byte ML program.
Thanks, I should have made it more clear in the video that the only actual criticism I have is if the program won't run on real hardware. All the other things (like being unable to edit) were just interesting side effects of the optimizations and were actually more-or-less how I discovered some of the optimizations. Out of habit I think I used words like "fixing" but that implies the program is broken, when really it's just optimized in unusual/interesting ways.
@@8_Bit agreed. The indeterminate state of RAM after boot, plus whatever might be left over from previous runs of other programs, means that this is a way to have uninitialized memory that BASIC isn't prepared to deal with as you've clearly illustrated. Regardless, good analysis.
Great sleuthing. That was really cool.
Nice, I never thought those space optimizations were possible.
Also, on the topic of programming, it would be cool to see a video about accessing the disk drive with assembly.
Great video, Robin. Not terminating the BASIC program could be even more dangerous if there's not a zero past $81f for a while (in uninitialized RAM). The machine will hang after the load trying to find the end of the "statement" at $820.
Great video, great explanation.
2:42 It might look better to use RND(1) or RND(π) since positive arguments produce "better" random numbers.
7:01 If he's assuming that $00s will be present after the program, he could skip the line-terminating $00 as well.
7:23 It makes perfect sense - if you know exactly how Commodore BASIC programs are stored.
11:30 Luckily, BASIC does a CLR when you enter a new line that initializes all of the other BASIC pointers.
12:35 Clever. He can skip the low byte of the link address since BASIC relinks the lines after LOADing and the only important thing is that the link high byte not be $00.
14:24 Oh, it's the file length and not the number of bytes loaded into RAM that counts.
Does BASIC relink lines after ,8,1 loading?
@@IsaacKuo It does. Even if you load something not in the BASIC area it'll still try to relink the BASIC area.
@@johnscarfone Cool, thanks for the info!
RND(0) uses the timer as a seed to generate a random number each time, while RND(1) continues from the previous seed,
which on reset is a default number, so the first run would always show the same maze.
Only negative numbers set a specific seed to produce a random number, which would always be the same (since you'd reset the seed each time).
I don't think that after reset, RND(PI) gives a different outcome then RND(1) ; use negative PI instead, but in line 0, and use line 1 for the loop to get the same random maze on each run.
Interesting! I found a bug in a demo a while back that manifested depending on initial memory conditions. In that case, it was the seed of an LFSR random number generator. The memory location was initialized to zero in a popular emulator, a value that is usually an invalid seed for an LFSR. This caused the demo to act very strangely. The actual bug was using the wrong addressing mode to set the seed. It was reading a zero page memory address instead of loading the accumulator with a literal value. As long as that memory location was non-zero, the bug was masked.
Mind blown - again! 🤯 😁
0xf0 00 and 0xAA 0x55 look like sequences a ram test would write.
The C64 RAM test is non-destructive, so the values materialize for hardware reasons.
@@csbruce It must be some weirdness with asynchronous DRAMs.
Trippy!
I never thought I'd hear of a hex edited basic program. Sadly my C64 is out of commission at the moment. I was trying to replace the cartridge port. I got half the pins desoldered before I needed a break, but my back was so sore I decided to shelve it for another weekend. Sadly I'm about 99% sure I broke a trace. I depend on the cartridge port because I only have a U2+. So without it the C64 is basically useless. Hopefully the remaining pins are less troublesome and no more broken traces. Actually every pin is at least partially desoldered. It's just the last little bit of cleanup giving me trouble.
Sorry to hear that, hopefully you'll figure it out next session!
@@8_Bit there have been times I wish I had a $200 desoldering iron, but I can't justify it for just 1 machine. lol
thanks robin, loved that... time to watch it all again.
I've always wondered what causes "undefined" memory to look the way it does on power-up. When I use my Amiga 1200 to play uninitialized memory through the speaker, I get some interesting echo effects to go with the usual 32-bit boundary checkerboard pattern. I'd love to investigate that further, but no way am I going to circuit-bend my precious 1200. 8)
Free mini-game hidden in this video... when the program runs, try to pause on a full frame of maze before the program is stopped. I did manage it around 18 min mark.
Really interesting, I like how you explained that.
After watching I decided to look up 'orthogonal'. I had always just thought it meant truly 2D. It seems orthogonal means it consists of lots of right angles :S
Specifically that the 2D area's coordinates uses two right angled axis with identical spacing on a flat surface.
If the display made the C64 pixels exactly 1:1 in real size, then looking at the diagonal maze at a 45 degree angle, it's still orthogonal from a certain point of view :)
Awesome, thanks for sharing
Great work explaining all that!
A-maze-ing insights
Nice video again! Instead of "print" can the abbreviation "?" be used to save bytes?
PRINT is crunched to a single byte token. You only see the PRINT text when it is being listed.
I don't believe it's the listing that needed to be 32 bytes but the total space it took up in memory or on disk. It memory, PRINT is already stored just as its token.
D'oh, beaten by 17 seconds 🤦
@@casaderobison2718 Yes of course... I was thinking that after I posted! Thank you!
Nice hack, and the most frustrating Commodore Basic debugging I witnessed (editing long lines > 2 comes close second)
I wonder if there are any other entries to the competition that use other tricks that would be neat to look at too.
Is there a place in memory that is guaranteed to have 2 zeroes? zero page or ROM. Using that location as the pointer to the next line would make it run on both emulator and hardware?
Or does c64 basic scan the line until it reaches zeroes and doesn't use the offset for linear execution?
There are two zeros at $0803 in the actual program.
@@ownpj I must be getting blind.
I'm baffled. I entered Logiker's "10 print" into my "real" Commodore 64C and it ran perfect (both with the zero and with the period) the first time.
If you type the program in, it will work fine. The problem was the trick Logiker used to make the file only 32 bytes on disk by truncating the end of the file, which assumed RAM would be initialized a certain way. When you type it in by hand, BASIC automatically adds the zeros in RAM that were cut from the disk file to get it down to 32 bytes.
Your credits scrolling program is missing the fade out on the last line. 🤓
Great. I really should have learned machine language and how to work with a monitor back in the 80s.
I really wish those 8-bit computers had assemblers built-in. Heck, when I upgraded to an Amiga, I didn't even get BASIC anymore, let alone an assembler.
@@Waccoon Geez, really? The first-gen model came with one of the best BASICs ever made. I guess once Microsoft got into the GUI business themselves, they didn't want to keep helping the competition.
@@stevethepocket You're thinking of Microsoft AmigaBASIC, which did not ship with the first Amigas. The A1000 came with ABASIC, which was written by MetaComCo (the same company that wrote the Amiga's infamously lousy filesystem, TripOS).
ABASIC was pretty awful, and was incredibly unreliable. I ported a number of ABASIC programs to AMOS in the 90's.
@@Waccoon - TRIPOS formed the *entirety* of AmigaDOS, not just the file system. And while I wouldn't call it great, I wouldn't call it lousy either. Keep in mind this is during the age when MS-DOS 4/5 will annihilate a partition (of particular sizes) during a chkdsk or Apple thought it was a good idea to use the upper bits of the address registers for idiotic status flags even though that was the exact example that the MC68000UM used when they were telling you NOT to use those bits.
Yeah, it's annoying to have to deal with BCPL pointers, and they could have used sector/cluster addressing instead of byte addressing, and the checksum/next entries in the user data block were inefficient (but made recovery a lot easier), but that was par for the course. I mean, we just finished watching a video where direct sector editing was done on the C64...which is probably the king of disk disasters, unfortunately.
@@LordRenegrade TripOS was not the whole DOS system. Amiga originally commissioned another company to write their DOS layer, but that team failed to deliver on time, and TripOS was used as a backup plan. A lot of stuff was added and merged into TripOS by the Amiga team, even though the official user manuals were very similar. However, back then people called the system AmigaDOS instead of AmigaOS, so there has always been a lot of confusion as to what went where.
TripOS assumed a hard drive was always available. Man, I hated not being able to get directory listings just because the DIR executable was not on a boot disk. That was stupid.
Fascinating.
I prefer diogonal.
You're making my head hurt again... ;-) Now we know what needs fixing in the emulator next.
wouldn't say it's fixing as such.. it's just that in these modern times OSes need to protect programs from spying on each other and usually only give out memory that's been zeroed out, where as retro hw with direct RAM access gets whatever the cell happened to have. so it's not that the emulator is doing anything incorrect, it just fails to crash.
they could randomize the initial RAM of course to make non-deterministic assumptions crash more often ..but it wouldn't really be failing like the real thing unless they'd model in semiconductor thermodynamics. that's what I'd call an emulator ;)
@@snooks5607 They already do set the virtual RAM to emulate the real thing. However, as Robin noted (and apparently the vast majority of commenters missed) different revisions of the C64 initialized RAM to different values. So the default in VICE is to emulate one of the common patterns (FF FF 00 00) by default, but you can EASILY change it to 55 55 AA AA with a few mouse clicks.
Robin, what would be the best reference guide to do something like this for the Vic20? I’m trying to track down a C64 locally but not having much luck. I can find Vic20”s and Atari 8bits easy.
For this specific kind of information with the inner workings of BASIC, Programming the VIC by Raeto West is probably the best reference guide, but the actual book is rare and expensive now. But a PDF is easy to find. Mapping the VIC, and the VIC-20 Programmer's Reference Guide are also generally excellent, and probably a bit easier to buy, but don't have much info about this very particular bit of nerdery.
FWIW, a LOT of the stuff that works on the C64 also works on the VIC20. It's kind of an embarrassment how little BASIC was enhanced from the VIC20 to the C64.
Yeah, BASIC is nearly identical on the VIC20 and C64; the ROMs are in different locations and there's some adjustments for the different sized screens and so on, but there's not a lot of difference. On the bright side, this choice on the C64 allowed for a nice clean memory map which makes it very easy to extend or replace BASIC. BASIC 7.0 on the C128 is much more rich, but also quite a bit slower, and has a very convoluted memory map by comparison.
Considering the two bytes of the line number are 00 00 for line 0, could you not just set the next line pointer to be 0803 to terminate the program without relying on memory being zeroed?
That's a good thought but the C64 still does a relink after the load and will "fix up" this pointer. So 0803 gets changed to 0820 based on seeing the 0 (end of statement) at 081f. Then it'll happily keep going looking at the uninitialized memory at 0820 for another statement like before.
Sounds like emulator authors should initialise memory with random values. Wouldn't take much code. Just a for loop with a call to rand() in it. Wouldn't add much time to startup unless you're trying to emulate something modern with gigabytes of RAM.
They already do. As a matter of fact, VICE can perfectly emulate any type of Commodore machine on boot, you just have to tell it what the values are and how often they repeat/invert.
@@AureliusR That's awesome!
Why the ;:goto though? is it faster then just ;:run?
Yeah, the GOTO is slightly faster than RUN and because they're both tokenized down to a single byte (like all C64 BASIC commands), they're the same size. And arguably GOTO is "more correct" because that's the intended way to endlessly loop in BASIC, rather than re-RUNning over and over again. Not that it actually matters :)
@@8_Bit Ah ok! Thanks for the explanation! I guess the run command re initializes stuff.
With this kind of maze, you can never be sure that you can get to the other side.
What about having two routines bin2base64.asm and base642bin.asm for compressing programs into Base64 or decompressing Base64 back into you program.
Base64 isn't compression.
@@futu1983 65535 =Jzz Base64, so so you are typing one character less.
@@futu1983 I mean, it sort of is. Just like you can represent binary in fewer characters using hexadecimal. But you're right, it's not traditional compression with Huffman trees, etc.
Thanks. Here's a version for the TRS-80 MC-10: ua-cam.com/video/h99jU_szJu4/v-deo.html
Here's another shorter version of what I think is an orthogonal variation. Not sure if it qualifies as orthogonal: ua-cam.com/video/CzS9Svu6uRk/v-deo.html
Sure looks orthogonal to me, great work!
The tricks here to cram this basic program into 32 bytes are sure commendable. But it's technically not 32 bytes, since it uses part of the basic interpreter and c64 bios to achieve its goal.
Sagan said it best ua-cam.com/video/5_vVGPy4-rc/v-deo.html
Nearly all of these restricted-size compos allow free use of whatever ROM (or RAM) the host system happens to have in an agreed-upon standard configuration. Usually the restriction is on the file size. I've seen compos that have other restrictions but they often seem fairly arbitrary and difficult to determine rule compliance too.
Do this random maze in machine language assembler.
I crammed an autorun machine code version in 27 bytes, but I didn't use any really interesting tricks. I had thought of an evil trick to try and shave off two bytes by reusing existing bytes as non-printing characters in the character table, but ... hmm. I guess I could shave it down to 26 bytes by moving stuff around (01 is a non-printing character).
Okay, I think I've figured out a way to get it down to 25 bytes ...
Okay, I managed to cram it down to 23 bytes, and confirmed it worked on real hardware (C128 in C64 mode). I did a lot of crazy stuff I'm not proud of to make it fit in 23 bytes and I don't think I can squeeze it down any further. I had to hunt around zero page until I found a byte pair guaranteed to be set to 255,0 (alternatively, 0,1 would have worked). PEEK(58) and PEEK(59) fit the bill.
I also had to do a lot of wrangling to ensure the stack bytes I was recycling would always be non-printing characters. It ended up in the MIDDLE of my character lookup table, and I needed to make sure both the autorun return address and the future return address that my JSR CHROUT would push would be non-printing characters. It's incredibly ugly. OTOH, my bigger 27 byte version is relatively elegant and robust, and can be readily expanded to include more characters.
Couldn't you replace the GOTO with RUN to save a byte?
It uses a trick so the GOTO only consumes one byte (no line number, defaults to GOTO0).
Yeah, to be clear, all BASIC commands are tokenized to one byte, so RUN, GOTO, GOSUB and whatever else just take one byte each. Normally GOTO requires a line number parameter, which is stored in plain PETSCII text, so that's an extra 1 to 5 bytes, but due to a quirk of the interpreter, an absent line number is equivalent to GOTO 0, and it happens to execute slightly faster than RUN too.
1:40 If you squint and look at it "diagonally" you can see that it's both the cubicles and jail cells in TRON
17:50 Agree, side thumb from me. Any time an emulator doesn't do what the hardware does means it should be rounded to the nearest real hardware ;-)
19:00 You mean there's a chance this wasn't Turbo Macro Pro? Crazy talk. I do like the graphic at the top.
Now if someone can figure out how to put those two maze patterns together.
I've not heard orthogonal pronounced like that before. Is that a Canadian thing?
I noticed I was saying it strangely during editing; I think I added a little on-screen caption about it at one point. I don't know where I picked that up from! I'll say it (more) correctly in the next video which should be released really soon.
Couldn't you use RND(0)*7 instead of RND(0)*6+1 ? Saves 2 bytes ? and a addition!
The call to MID$() needs a string position between 1 and 6. 0 will produce an error.
multiplication and division first, then addition and subtraction
@@csbruce non-zero indexing 🤮
there is no "shift+" sign
Sorry, I don't understand this comment, but I'm intrigued.
@@8_Bit you could add shift+'+'. Bold plus or cross. and have more diverse labyrinth. It will add one more byte.
Also you cold leave horizontal and vertical line with bold plus and have similar effect and shorten it for 3 bytes.
Just 'ad-just' MID value.
Perhaps I missed something: Does the program run on real hardware if the exact sequence of commands is:
LOAD"10PRINT",8,1
RUN
In my other comment I theorize that this code should run, even though listing and editing are problematic.
No, it will not. The three chopped off bytes need to have been initialized to 0 in order for the program to run. Evidently these bytes will be initialized to zero in an emulator, but not on real hardware.
[edit added:]
Ah, actually I read your other comment and maybe it would work. Hmm ...
Not necessarily but maybe (even probably I'd guess). It depends on the contents of uninitialized RAM at $820 and beyond. After the load, if the pointer at $820 is not 0, BASIC will scan to the end of what it thinks is another valid BASIC statement. I've found that if it doesn't hit a zero in 255 bytes it'll get stuck in an infinite loop and never return the BASIC prompt. If there's a zero somewhere before that you should get a prompt back and it should run.
On my Ultimate 64 it seems that memory often has zeros in this whole area but not always. Sometimes it's pretty random looking garbage.
@@johnscarfone I think it wouldn't matter what is at 0820 or beyond, as the GOTO will prevent the interpreter from ever having to read any bytes thereafter.
Edit: By which I mean: As long as you LOAD with ,8,1, and do not try to list the basic program but only run it, I think it will work on real hardware. Ultimately, it is easy to test in an emulator by writing garbage bytes at 0801, 0820, and 0821 (if I am remembering the offsets off the top of my head, as well as the variable table pointer / end of BASIC pointer in zero page. I think I shall try it.
@@casaderobison2718 It doesn't while you're running the program but what I described happens after the load finishes but before the prompt comes back. This is the relink process where that first skipped byte gets fixed up that Robin was talking about. A side effect of not having those two zeros at the end is that it'll try to fix up more (garbage) BASIC statement(s). You can make it get stuck during this process with well chosen garbage. 🙂
@@johnscarfone But the relink doesn't happen if you use ,8,1. If one loads with ,8,1 there is no relink, so does run work at that point on real hardware.
Or you could have just replaced the ‘0’ character with the ‘.’ character directly. 😏
Or just typed in the "NEW" command and cursored up to reenter the program fresh (with the '.' replacement). But what would we have learnt then?
Let’s try something else for the outro music (that won’t punish the ears of the listener.), eh?
Not gonna lie, actually kinda like it
I like it too, but I won't use it next video, promise.
Why not replace the numberless GOTO with RUN? After all, GOTO is considered harmful.
RUN and GOTO are basically interchangeable in this case, yes. I believe I benchmarked the two against each other and GOTO is marginally faster, so I'd choose GOTO. As for GOTO being harmful, no Commodore BASIC programmer believes that :)
>GOTO is considered harmful
By who, dumb people? I use at least one goto in my C programs on principle.
Lol, if you think GOTO is "harmful", I wonder what you think about the machine code JMP instruction - and why it, and all CPU instruction equivalents, are NOT considered "harmful". You might also give yourself a serious shock once you dive into the assembly output of virtually every compiled program...!!!
Important concept: so-called 'spaghetti code' is...everywhere, by design.
@@ChrisM541 Um, what?
en.wikipedia.org/wiki/Considered_harmful
Maybe consider that it's you that is lacking knowledge before leaping in with your diatribe?
Wow... all that work for a pretty ugly maze. Sadness for sure there. The detailed exploration, on the other hand, exposed the "fraud" and educated people interested in these details in a very nice way! The side scroller routine? Pretty cool!
Question: Why does printing this way to the bottom of the screen result in scrolling TWO lines instead of one? This is a Commodore BASIC ROM question.... so far, it's a mystery. Can we patch it to only scroll one line?
The C64 will scroll "logical" 80-column lines if they're linked together by PRINT wrapping around the 40th column, which always happens here.
all that work, one line of code? or you mean the analysis of the optimization? but that doesn't have anything to do with the maze.
@@snooks5607 As I said the actual program was pretty much garbage though I will say I liked the approach of using mid$ and more characters. It gives me an idea for some other things. It was the analysis and the tools and techniques along with the particular details Robin added which were the real value and interest here.
@@csbruce That makes more sense than anything else I suppose. But C64 was never intended to have an 80 column display. Now if someone were to say "this is an artifact of the Commodore BASIC origins and having been ported from an 80 column display system" I'd be like ah... okay. But how do we fix it? It's strange behavior and I don't like it at all.
@@anjinmiura6708 so mid$ was good, was it the "print" or "rnd" that was garbage?