The question is not stupid, opposite. It's not trivial subject.
Basically you don't return to BASIC from NEX files. With current version of NEX file (V1.2) you will, if nothing else, lose the information about where was the system stack located (plus it's super easy to produce code which does overwrite vital parts of NextZXOS, unless you put extra effort into avoiding all system banks). The best way to "exit" from NEX app is to restore system configurations you did to non-obvious things (nextregisters affecting system too deeply across soft-reset, like altROM stuff, etc.. you will probably not hit any of these any time soon, if you are learning Z80 assembly and experimenting with screen/keyboard/sound) and do `nextreg 2,1` to trigger soft-reset. (BTW, this does nothing in CSpect emulator, so follow with infinite loop in code to just keep hanging in CSpect).
So NEX format is mostly targetting people taking over the machine, definitely. Like games. (and it is quite similar to snapshot formats in many ways, which is why it is rather destructive to the NextZXOS, unless you go quite some lengths to avoid that at least under common scenarios and you can not produce completely OS-safe NEX at all, any NEX file you produce, I can tamper with the NextZXOS before loading it enough to make the two clash .. but that's like academic stuff, most of the users will power on Next and load the NEX file, which makes the conditions somewhat stable and you can assume a thing or two)
Then there is DOT-command format, where the startup procedure is considerably different and returning back to OS is normal, but you have only 8kiB of space in the trivial variant, being launched from within the divMMC memory area. You can still extend your workspace by allocating memory from NextZXOS, and eventually loading further banks of code, so there are dot-commands longer than 8kiB and using more data, but you need to behave and use correct services to stay OS friendly.
- this is excellent tool mostly for command line tools, or generally utility software, where you expect as user to return back to main menu/command line.
And final common option are regular SAVE/LOAD blocks of ZX BASIC (and NextBASIC). You can collect the regular tape blocks into single TAP file to make the distribution look more compact and easier to manipulate (copying file to card, etc.). You can then in the initial small BASIC loader do the correct things like CLEAR, and LOAD further blocks of CODE or allocate banks in BASIC, etc.. ie. having regular BASIC code already running at the top level, you just do the usual ZX Spectrum stuff to incorporate any machine code into it without destroying your top level BASIC environment. - this is also IMO preferred way for modern classic ZX production, because by using the regular SAVE/LOAD commands you get package which is either directly runnable on various disk systems, or very easy to patch for some specific one of them. The other way of distributing disk images tightly coupled with particular disk system makes it difficult to run such SW on ZX without it, like for example in recent days I have seen new release of disk-mag for ZX scene distributed as MDOS D80 file (old Czech/Slovak disk system from Didaktik Skalica company) -> I can't run that on my Next (only in emulators which support these disk images).
I wouldn't underestimate the TAP form for distribution even of the Next-specific software, as TAP is long-time well defined universal format. The only downside to it is that when you launch it from NextZXOS browser, it will ask about HW configuration before loading the first part, so for Next-specific software there's extra press of "N" to select Next... I find that completely acceptable for the benefits mentioned above.
(I may sound very vague, not telling you precise instructions, but that's intentional, because I'm not sure myself, what are the precise rules, depends what you have on mind, it's quite a list and most of the things are not relevant to many use cases - plus I'm personally all about taking over the machine and not using the BASIC or OS services at all, so my experience with preserving it correctly is minimal - also I'm not aware of some thorough detailed manual for NextZXOS describing these in one place.. one part is to get familiar with NextBASIC in the first place, then there is documentation about NextZXOS service API, describing also requirements on the SW side to make them work, but I think there are few more unwritten rules, which you can accidentally break.)
Finally there are classic snapshot formats like SNA, Z80, SNX ... those are by their nature destructive to the NextZXOS, and while for example I use SNA a lot in sjasmplus to quickly prototype/check something, I'm always doing that with the knowledge I'm taking full control over machine, and destroying OS variables is ok in such case.
Now with your specific TEST example - I would do this...
Your code as is is OK-ish toward ZX BASIC. You can save it as CODE block from address 32768 (0x8000) (it's 13 bytes long if I'm counting it correctly, but you can use the assembler symbols to not calculate it manually).
Then you need BASIC loader which will prepare the NextBASIC for such code block and start the code:
Code: Select all
10 CLEAR 32767 : REM moves stack under the 32768 where your code will be loaded
20 LOAD "your_code" CODE 32768 : REM load your code into memory
30 RANDOMIZE USR 32768 : REM and start it
Now you can pack both basic loader and the code block together into TAP file, and launch it on Next, it will return back to OS normally.
The PITA part is how to produce the BASIC loader if you are primarily interested into assembler and you are working for example in sjasmplus. That one has example TapLib in distribution for similar trivial loader, and EMPTYTAP/SAVETAP directives to build TAP file directly from asm source, but it's still not completely trivial. For your custom BASIC loader you either need to understand the binary form of tokenized BASIC and re-create it by using the DB/DW statements in assembly, or "cheat" by preparing the BASIC part on the Next itself, save it into TAP file, and then start every build of assembly by copying this loader-only TAP to final file as start, then add other blocks to it by SAVETAP...
But what is your reasoning to get back to BASIC in the first place, and can't you just soft-reset your app? (with CSpect itself it's even less clear what's your plan, I usually test my code and hit escape to close the CSpect completely, never been in need to get "back into the basic").