Jump to content
Starsickle

RETROTrek - Early Development Thread

Recommended Posts

28 minutes ago, SlithyMatt said:

Ah, that's the harder part in BASIC. Unfortunately, BASIC doesn't support pointers, much less far pointers, so you'll still need to poke the bank number and peek at addresses to read the values back. That means one byte at a time, so even if you are storing strings, you'll need to deal with them as arrays of character indices.

Oh...nuts. That's not good. So, at current, there's no way to simply reference a bank and a name?

So, if I wanted to continue, I would need something to reliably read....some place within a given bank and bytewidth in order to properly cast information and return it.

So, something like:

10 REM //SUBROUTINE - BANK ACCESSOR
20 REM //INPUTS - B% - BANK #, A% - ADDRESS?, W% - BITWIDTH,
30 REM //IFS ON W% BEING 1,2, OR 3 ASSIGN R AS RETURN AND RETURN
40 RETURN
	

Yeah, so This means I need documentation on both the banks used, the memory addresses statically assigned, and - basically no dynamic assignment or allocation or names within the banks.

Lame. Depressing, even. I will have to cut features.

Edited by Starsickle

Share this post


Link to post
Share on other sites

Yah, there are two separate (but equal!) feature requests for BASIC commands to easily move memory between Low RAM, banked RAM, banked ROM, and Video RAM.

In some ways, the separation of these memories means each is kind of like a separate device; presumably, an abstraction layer that treats them as separate devices might be part of the solution.

(1) how to deal with type data, e.g. can you move memory by type?  E.G. "float poke/peek", "uint poke/peek", and "string poke/peek"?
(2) what are the most useful destinations for banked data? Some brand of memcpy() is useful but not enough.
(3) how to avoid excessive POKE and PEEK altogether.  
(4) should you be able to PRINT directly from banked memory, avoiding an intermediate variable?
(5) can we arrive at uniform/overloaded functions that can copy from both banked RAM and ROM and VIDEO RAM?

Germane topics include the BASIC 7 command "POINTER", as well as BLOAD and BSAVE.

Discussion at: https://github.com/commanderx16/x16-rom/issues/127
and https://github.com/commanderx16/x16-rom/issues/17

Edited by rje

Share this post


Link to post
Share on other sites
45 minutes ago, rje said:

Yah, there are two separate (but equal!) feature requests for BASIC commands to access bank memory better than POKE and PEEK.  In effect, proposals to do memory copies.  Issues to be decided include:

(1) how to deal with type data, e.g. can you move memory by type?

(2) what are the most useful destinations for banked data?  Apparently, memcpy() is useful but not enough.

(3) how to avoid excessive POKE and PEEK altogether.  

(4) should you be able to PRINT directly from banked memory, avoiding an intermediate variable?

Yeah, another consequence of having arrived a bit early to the party. There's still a lot to be designed and built, and maybe, in my own amateur-hour-millennial way - can help through suffering - that's fine. XD

Not the place for this, BUT since it's relevant to the immediate problem:

1 - This would be ideal. we're dealing with 512K, so we have plenty of room to create intermediate structures for managing segments of addresses and thus data. The Low Ram presumably already does this (The C64 certainly does) - so why not allow the High Ram to do the same, if the programmer wishes it? It's the most novel resource of the machine. In essence, asking a bank to act in a similar manner to the Low Ram isn't a stretch in terms of design.

2 - Probably solved by some method related to 1. 

4 - Yes. If you can solve the type enforcement, you automatically have the path towards running commands directly on higher ram.

I'd throw this on the issue posts, but I worry that I might be a bit out of my own depth and the comments unwelcome.

 

As for RetroTrek itself, I'm kinda halted except for small things. I'm currently implementing things in an untested state, namely combat mechanic functions like checking if the shields are down, damaging a random system, etc. The bottom and top bars are much better now; they and the SYSTEMS panel now have lots of useful information. The top bar now shows the current RAM free.

It seems like, as soon as this is over, that things like generating the sector map will be easier, but expensive and slow. I will want something to display "PROCESSING..." while that intense stuff goes on. I am wondering if I can make a standard for subroutine inputs and outputs such that it uses the fewest variables but takes the fewest lines/chars to document in-code.

Edited by Starsickle

Share this post


Link to post
Share on other sites
5 minutes ago, Starsickle said:

Oh...nuts. That's not good. So, at current, there's no way to simply reference a bank and a name?

You can store bank numbers and addresses into BASIC variables, but you can't automatically dereference those pointers in BASIC. So, you can load a data file to a set of banks with LOAD, and use BASIC variables to keep track of the bank numbers that contain different kinds of data, or arrays that contain bank and address pairs, all of which will need to be hardcoded based on a static map of your data file. If you have variable-length data, you can traverse that in basic and store length values, too. But definitely, you're hitting the top end of what's feasible with BASIC. It may be time to consider using C or assembly to create some routines to handle that data that you can use SYS calls to run. I just posted a video that can help get you started on using cc65: 

 

 

But if you want to continue with BASIC, you can do something like this:

Say you have a data file (DATA.BIN) larger than 8k, so that you have values you need to access at offsets $1234 (FU) and $2468 (BR). So, you load it starting at bank 3 (BM), meaning it will spill into bank 4 (BS), putting those values in RAM at 3:B234 and 4:A468.

10 BM = 3

20 BS = BM + 1

30 LOAD "DATA.BIN",8,3,$A000

40 FP = $A000 + $1234

50 BP = $A000 + $2468 - $2000

60 POKE $9F61,BM

70 FU = PEEK(FP)

80 POKE $9F61,BS

90 BR = PEEK(BP)

 

Share this post


Link to post
Share on other sites

Honestly, if the workflow in C is nothing more than do what I do in C Projects? Shove it into some machine  - get a PRG file? I might consider it. But I would want ALL of C. All of it. Checking the video; it's promising, but I'm uncertain.

I have a little experience with C, but my experience was rife with environment setup and workflow problems than actual design and programming. Thus is the destiny of Windows C/++ development without an IT department.

I think, at least for now - I'm best served IRL by waiting on the design and engineering issues at hand before diving any deeper.

Share this post


Link to post
Share on other sites

Developing with C for Windows is a bad comparison to cc65 and the X16. By necessity it is much, much simpler. You'll want just a single PRG and just link everything to that. If you use my makefile, you are all set to create a set of .c and .h file with a sinlgle main function and link it all together. You won't need to link a bunch of libraries in, as the basic clib and X16 kernal stuff is automatically linked in if you reference it.

And I'm not sure what you mean by "ALL of C". Do you mean a particular C standard, like C18? C is by its very nature a systems language, so its use is tightly constrained by your target system. In this case, it's as simple a system as you can realistically use C for. So, no heap, no floating point math, and no structs on the stack. But otherwise, anything that you would do with a C program is possible, and that pretty much covers what you can do with the X16, anyway. BASIC does give you floating point, but at an enormous cost because it's for everything all the time.

What you will get with cc65 is the ability to have named pointers, structs and scalar variables. And those names can be larger than 2 characters! You can easily reference strings and binary data blocks anywhere in memory and use the standard C functions with them. Doing that stuff in BASIC is a complete nightmare and completely negates the "ease" of BASIC programming relative to C.

Just some things to consider, as you obviously want to create a very involved game, and I'd hate to see you stymied by something that is easily overcome.

Edited by SlithyMatt
apostrophes!
  • Like 2

Share this post


Link to post
Share on other sites
Quote

I

t seems like, as soon as this is over, that things like generating the sector map will be easier, but expensive and slow. I will want something to display "PROCESSING..." while that intense stuff goes on. I am wondering if I can make a standard for subroutine inputs and outputs such that it uses the fewest variables but takes the fewest lines/chars to document in-code.

You can also give the user something to read while the map is generating: for example, a command summary, or a selection of useful tips.  Something that fits on one screen.  (Yeah, I know, you might have to bank THAT as well).

One other option, in the spirit of "divide and conquer", is to have a separate, GALACTIC CONTROL program that generates maps, and writes them to files, and allows you to specify the "current" map.

Then the main program would simply load the "current" map.

Edited by rje

Share this post


Link to post
Share on other sites

Not the best screenshot, but here's the last time it worked without running out of memory:
RetroTrek-WIP19.thumb.png.a115a24cde3c12e01726b6c1bf7b2028.png

I was just about to get the Mask and Player Position "@" working.

  • Like 1

Share this post


Link to post
Share on other sites

That's actually an exciting layout, Starsickle.  All those views and statuses and so on... and the reverse text on the bottom... the classic greentext terminal on black... very nice.

Edited by rje

Share this post


Link to post
Share on other sites
On 9/16/2020 at 11:11 PM, SlithyMatt said:

You can store bank numbers and addresses into BASIC variables, but you can't automatically dereference those pointers in BASIC....

Yes, that's the crux of it. We need some V2 Basic hackers to have a go at some of the proposals in Issues on GitHub, or it will not happen in RomBasic. xForth debugging took me straight through to the end of summer vacation, and learning the ins and outs of ca assembly isn't really going to happen in the middle of the Semester, so that's not going to be me. That is, not unless it is the associative arrays in High RAM as device 7, which is a Kernel level expansion that Basic can use, and so can be programmed in acme, otherwise I am just on the sidelines cheering.

My number one extension I would cheer for is HDIM to allocate arrays within $A000-$BFFF, and then if you have a parallel data structure across multiple banks, simply change the bank and the arrays reference all the parallel data in that bank.

Edit: It just occurred to me that USER() might be give an array fill from HighRAM routine, that's a second thing that could be attempted from Acme.

 

Edited by BruceMcF

Share this post


Link to post
Share on other sites

As a practical example, here's a hexdump of a character record from my game-in-development.

00000000  58 10 4a 4f 52 4e 20 20  20 20 20 20 20 20 20 20  |X.JORN          |
00000010  20 00 39 38 37 36 37 38  4d 34 20 50 20 20 00 00  | .987678M4 P  ..|
00000020  00 41 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |.A..............|
00000030  00 00 31 30 30 30 30 30  30 31 30 30 30 30 30 30  |..10000001000000|
00000040  30 30 4b 48 41 41 4c 4f  20 20 20 20 20 20 20 20  |00KHAALO        |
00000050  20 00 39 38 37 36 37 38  55 34 20 50 20 20 00 00  | .987678U4 P  ..|
00000060  00 45 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |.E..............|
00000070  00 00 30 30 30 31 30 30  30 31 30 30 30 30 30 30  |..00010001000000|
00000080  30 30 41 52 52 4c 41 4e  52 4f 55 47 48 4c 20 20  |00ARRLANROUGHL  |
00000090  20 00 39 38 37 36 37 38  56 34 20 50 20 20 00 00  | .987678V4 P  ..|
000000a0  00 50 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |.P..............|
000000b0  00 00 30 31 30 30 30 30  30 31 30 30 30 30 30 30  |..01000001000000|
000000c0  30 30 54 48 52 4f 59 53  53 20 20 20 20 20 20 20  |00THROYSS       |
000000d0  20 00 39 38 37 36 37 38  44 34 20 50 20 20 00 00  | .987678D4 P  ..|
000000e0  00 53 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |.S..............|
000000f0  00 00 30 30 31 30 30 30  30 31 30 30 30 30 30 30  |..00100001000000|
00000100  30 30                                             |00|

The first two characters are throwaways that normally would indicate the target address.  Because I load to a specific address, the X16 ignores them, so I code them with the codes for ASCII "X" and the decimal 16... X16, get it?  

This means the X16 sees this as a 256 byte file.

The file itself has four records for four characters.  A 15 character name with a zero at the end; a six-digit string representing the characters "personality profile" (a displayable string); a letter denoting species ("U" is human); a character denoting number of terms served in the previous career (they're all "4" here); a readied weapon and armor; a character denoting assigned position aboard a ship ("E" is engineering, for example); and sixteen digits (stringified) representing levels in sixteen skills.

All of this is playable data, although what I need primarily is name, ship position, and skill level.

I load this file, along with the ship data and the player's game state, like so:

load "SHIP.BIN",8,1,      $A000 :rem bank1 to $A3ff
load "PLAYER.BIN",8,1,    $A400 :rem bank1 to $A4ff
load "CHARACTERS.BIN",8,1,$A500 :rem bank1 to $A5ff
	

I get at the player data with a long series of PEEK statements.  This is superior to a long series of READ statements, but I think reading from a SEQ file would be more convenient (for transfer to main RAM) but not convenient for memory management.  Overloading INPUT# and GET# to treat banks as input channels would make it easy to transfer data from banks to main RAM.  Thus banks become a kind of buffer to give a kind of random access to SEQ files.  But it doesn't allow direct use of banked RAM.  


 

Edited by rje

Share this post


Link to post
Share on other sites

That's beyond my practical experience and understanding. (I see those graphs a lot, but I'm only half sure how to read them.) I am not good at systems-level allocation and Read/Write. I'd take up 40kb making sure the code for putting a single ship into and out of a stack was usable by my own self.

Of note for consideration for features, though, is the way that The C64 and BASICV2 stores strings versus storing character arrays. This should be the subject of careful consideration for the X16 and C64 sharable code, as being able to cap various string into character arrays in a practical way means that you save memory. Much like we have Byte, Short, Int, and Double in higher level languages - so it should be with Characters and Strings on these lower level systems.

Of course, there is the system's requirement for compatibility, but I'm at the point where I won't compress things any more in order to maintain readability without documentation.

I don't even really know how to read and write data and files to any given filesystem. (My early tries in the EMU did not work). I'm basically going to wait until the GitHub Issues requests are designed and built and someone does some more key work before I dive back in. Someone has to do the system-level work that people like me can take advantage of. It's just not our bag.

Immediately, the program needs its execution broken up into module files as much as possible so they can run in sequence. I grasp how this can be done, and it goes something like this:

This here would be a data allocation and static data init file:

14 REM // MC% //THE MASTER COUNTER - USED FOR PROGRAM JUMPING.
16 REM // LS% //LOADED FILE STATE - CONTROLLING THE LOADED FILE.
	20 DIM RGN$(3,3,3) : DIM SCTR$(3,3,3)
21 LS%=1
22 END

which is called by the main program file below:

15 IF LS%=0 THEN : GOSUB 100 : REM //DATA NOT LOADED.
16 IF LS%=1 THEN : GOSUB 200 : REM //DATA LOADED, PROGRAM NOT.
17 IF LS%=2 THEN : GOSUB 1000 : REM //PROGRAM LOADED. GO MAIN LOOP.
18 GOTO 4000
	100 REM //ROUTINE - LOAD ANOTHER FILE AND INIT DATA.
101 LOAD "INITDEMO.PRG", 8,1
	200 REM //ENGINE SETUP AND INITIALIZATION=====================================
201 VN$="0.0.1" : VD$="SEPTEMBER 15TH, 2020"
202 FC%=5 : BC%=0 : FI%=0 : BI%=5 : COLOR FC%, BC%
203 SX%=640 : SY%=480
204 DIM GD%(3)
205 LS%=2
206 GOTO 1000
	
1000 REM //MAIN LOOP
1001 PRINT "DISPLAY SCREEN"
1002 PRINT "GET INPUT"
1003 PRINT "PROCESS INPUT"
1004 PRINT "LOOP"
1005 GOTO 1000
	3000 END
	4000 STOP

This is janky as hell, but I otherwise don't know how else to quickly manage program execution other than a counter or a control state variable that always hits the top of a given program file. During this, there's so many potential hazards in the program section of memory, in the call stack of memory, and all of that that I just don't know what's going on with the machine to make a safe, solid call about designing a program that is only limited by the media it's being loaded from.

I've returned to other projects, but I want to keep checking in on the X16. This kind of work is beyond my capability and patience. I don't want to give up, but I don't know if I can seriously continue short-to-mid-term.

Share this post


Link to post
Share on other sites
1 hour ago, Starsickle said:

15 IF LS%=0 THEN : GOSUB 100 : REM //DATA NOT LOADED.
16 IF LS%=1 THEN : GOSUB 200 : REM //DATA LOADED, PROGRAM NOT.
17 IF LS%=2 THEN : GOSUB 1000 : REM //PROGRAM LOADED. GO MAIN LOOP.


The simpler syntax for this is :
ON LS% GOSUB 100,200,1000 ....

Also, try to avoid integer variables. They are actually slower in BASIC, and while they use less space to store, you actually use one byte more every time you reference it. So if you reference LS% 4 times (including setting it the first time), you actually use more space than if you just called it "LS".

 

  • Like 1

Share this post


Link to post
Share on other sites
2 hours ago, TomXP411 said:

The simpler syntax for this is :
ON LS% GOSUB 100,200,1000 ....

Also, try to avoid integer variables. They are actually slower in BASIC, and while they use less space to store, you actually use one byte more every time you reference it. So if you reference LS% 4 times (including setting it the first time), you actually use more space than if you just called it "LS".

Will do - this is much like a Switch statement? This will save many lines, and thus many bytes.

Share this post


Link to post
Share on other sites
47 minutes ago, Starsickle said:

Will do - this is much like a Switch statement? This will save many lines, and thus many bytes.

That is exactly the poor man's switch statement.

Quote

I don't even really know how to read and write data and files to any given filesystem.

Sounds like we need a tutorial... and Matt can do it.

H'm, but the code I tried... failed.  I'm getting "Device not present error" when I use device 8, and "Illegal device number error" when I use device 1.  Alas.

 

Share this post


Link to post
Share on other sites
8 hours ago, rje said:

Sounds like we need a tutorial... and Matt can do it.

H'm, but the code I tried... failed.  I'm getting "Device not present error" when I use device 8, and "Illegal device number error" when I use device 1.  Alas.

Coming out of CS and programming, I'm the guy that wants everyone to document and tutorial everything, even for development. Over and over you can see that when the energy is spent to make things easier, a platform flourishes. I'd volunteer to the a wiki or a github, but there's a lot that remains unsettled and there's just some things I don't know right now.

I DO plan on posting whatever tutorials I can create myself once the program matures into a small engine for content, but getting to that point will take time. I have years of experience in Java, years of experience in scripting languages, and the most important part of that is having mature code laying around to be copied and pasted.

Heh. Maybe I should start a java project and see what can be done in two weeks with AWT/Swing, which I've grown quite sick of in Eclipse...

Share this post


Link to post
Share on other sites
12 hours ago, rje said:

Sounds like we need a tutorial... and Matt can do it

Hey, who's controlling my content? 🤣

If file I/O is that challenging to folks, I can put that in the queue. The current state of the emulator and ROM is that only loading and saving whole files with the host file system is working 100%. There are still issues with the SD Card emulation and with byte-wise file I/O, so it may be a bit premature to do a deep dive.

The best practice for now is to set aside a chunk of RAM (banked RAM is perfect for this) to load your entire file into and then get the data you need by directly referencing it in memory. If your file(s) are too big to store in RAM, consider making them smaller (this is an 8-bit platform, after all!) and split them up in a way that makes sense. Some of my projects use formatted filenames so that I can programmatically select the file needed and even determine which starting bank to load it to.

Share this post


Link to post
Share on other sites
15 hours ago, rje said:

That is exactly the poor man's switch statement.

Sounds like we need a tutorial... and Matt can do it.

H'm, but the code I tried... failed.  I'm getting "Device not present error" when I use device 8, and "Illegal device number error" when I use device 1.  Alas.

 

You have to mount an image file. The emulator cannot write directly to the file system. (I suspect that for saving to the host file system, the emulator traps LOADs and SAVEs without a device number, rather than emulating a disk drive.)

Once you do that, OPEN, GET#, INPUT#, and PRINT# work just fine on the latest version of the emulator. I've already written some code to test that and confirmed it works.

 

  • Like 1

Share this post


Link to post
Share on other sites
6 hours ago, Starsickle said:

Coming out of CS and programming, I'm the guy that wants everyone to document and tutorial everything, even for development. Over and over you can see that when the energy is spent to make things easier, a platform flourishes. I'd volunteer to the a wiki or a github, but there's a lot that remains unsettled and there's just some things I don't know right now.

I DO plan on posting whatever tutorials I can create myself once the program matures into a small engine for content, but getting to that point will take time. I have years of experience in Java, years of experience in scripting languages, and the most important part of that is having mature code laying around to be copied and pasted.

Heh. Maybe I should start a java project and see what can be done in two weeks with AWT/Swing, which I've grown quite sick of in Eclipse...

I've asked about this, and even volunteered to do documentation work, but @Perifractic told me that's already covered. The documentation is sparse right now, because this is a work in progress. 

As you suggest, a platform without complete documentation is difficult to develop for. But that won't be a problem here - these guys are committed to not just providing good hardware, but good documenation.

 

  • Like 1

Share this post


Link to post
Share on other sites
19 minutes ago, TomXP411 said:

You have to mount an image file. The emulator cannot write directly to the file system. (I suspect that for saving to the host file system, the emulator traps LOADs and SAVEs without a device number, rather than emulating a disk drive.)

You don't have to mount an image file, unless you want to test the actual SD Interface code in the Kernal. For the emulator, it indeed traps LOAD and SAVE calls and just does native fopen/fread/fwrite calls to interface with the host file system, if no SD card image is specified. However, you do need to specify device 8 for this work, and SAVE definitely writes out to the host file system correctly, as long as the segment of RAM you are writing out is in low RAM ($0000 to $9EFF) and not banked memory.

Share this post


Link to post
Share on other sites

It traps LOAD and SAVE just fine, but I do think Tom's right that I need to mount the SD image before manipulating SEQ files.

 

Share this post


Link to post
Share on other sites
Posted (edited)
12 minutes ago, SlithyMatt said:

You don't have to mount an image file, unless you want to test the actual SD Interface code in the Kernal. For the emulator, it indeed traps LOAD and SAVE calls and just does native fopen/fread/fwrite calls to interface with the host file system, if no SD card image is specified. However, you do need to specify device 8 for this work, and SAVE definitely writes out to the host file system correctly, as long as the segment of RAM you are writing out is in low RAM ($0000 to $9EFF) and not banked memory.

I was talking about reading and writing sequential files, which is why I talked about OPEN, INPUT#, etc.

Currently, using INPUT# with sequential files is much faster than LOADing to upper memory and then using PEEK and POKE loops.  I don't recommend using disk files to buffer dynamic game data, however, since every write to SD reduces its endurance. So for stuff that's changing over time, use upper memory. For stuff that's static, just re-read from storage as needed.

 

Edited by TomXP411

Share this post


Link to post
Share on other sites
1 minute ago, TomXP411 said:

You completely missed the point... this was about reading and writing sequential files, not using LOAD and SAVE. 

 

Sorry, too much crosstalk. Definitely, fast-changing data should be kept in RAM and not paged out all the time to "disk". I'm not sure what the point of SEQ files is with the X16, as that seems to be a Commodore-specific approach borne of the limitations of the era.

Share this post


Link to post
Share on other sites
Posted (edited)
15 minutes ago, SlithyMatt said:

Sorry, too much crosstalk. Definitely, fast-changing data should be kept in RAM and not paged out all the time to "disk". I'm not sure what the point of SEQ files is with the X16, as that seems to be a Commodore-specific approach borne of the limitations of the era.

I'd actually been wondering if the RAM banks could be abstractly treated as a device, with secondary numbers 0-255 representing bank numbers, thereby allowing transfer via INPUT# and PRINT#...  yeah it's clunky.

Devices 8-15 are all "disk drives" and 16-30 are "unknown".  They're assumed to be on the serial port, but MUST they be?

Suppose device 16 represents the RAM banks and device 17 represents the ROM banks.

1 OPEN 1,16,2,"0:DUMMY STRING,S,R".  :rem read from device 16 (RAM Bank) #2 (Bank 2).  Ugly!!
2 INPUT#1, A$, B, C$, D%, E$, F$
3 CLOSE 1

It treats each bank as a SEQ file, kinda.  Yeah it's ugly.  Supa-ugly.  And there are questions in my mind about how to issue commands.  But... it might could let us drag typed data in and out of RAM banks.

 

Edited by rje
  • Like 1

Share this post


Link to post
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.

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.


×
×
  • Create New...

Important Information

Please review our Terms of Use