Not necessarily ignore, but it seems reasonable to throw an error when they’re executed instead of when they’re first seen. Having the mere existence of an unknown opcode be an error is going to cause a problem with any opcodes added in the future; it’s not always reasonable for compiler writers to pull each new opcode out into its own routine (for example, if it involves the stack in any way).
You can’t decode or skip past an unknown opcode because you don’t know what its operands are (particularly a branch or storer). Any errors really need to be raised at the time of decoding.
Hm. Would it be possible for the JIT compiler to mark that branch as dead when it encounters an unknown opcode?
For unknown opcodes there’s no way to know whether there is a byte for a storer or up to two bytes for a branch. Only the basic operands are self documenting. So there’s no way to know where the next instruction begins.
Also the missing opcode error usually occurs not because of an actual unknown opcode, but because a bad branch or call has occurred and the program counter is not aligned to an actual instruction. So that’s not something to ignore either.
Glulx should be safer for unknown opcodes as I think all operands are listed in the operands bytes after the opcode number.
Right; I mean if an unknown opcode is encountered, treat it like a @quit and resume decoding at the next target of a previously-encountered branch or jump. If execution ever goes down the bad branch, crash then. Dialog emits code like this when entering a div, for example:
! set foreground color
@loadw 32 0 -> l1 ! standard revision
@jl l1 $0101 ?background ! standard 1.1
@set_true_color fgcolor $FFFE
The @set_true_color could be pulled out into its own routine, but that adds an extra routine call to every div and span change for not much benefit. I’ve been assuming so far that these kind of checks are enough to avoid illegal opcode errors, but if they’re not, that adds a big obstacle to defining new opcodes in future Standard revisions.
We are getting rather off-topic, though, and given that there hasn’t been a new Standard revision in decades, I may be worrying over nothing. We’ll see if it actually becomes a problem.
Ahh, yes that would work.
Though no promises when, if ever, I’ll implement it.
I was glad to see my Voxam interpreter already handles the reported case but I found a gap I need to cover.
The current implementation is that unknown EXT opcodes at 128 and above decode as ext_private, a rider-less no-op: the operands are consumed by their own types byte, no store or branch is assumed, and play steps past as if the instruction were never there, which is exactly the entry the diagnosis says ZVM needs. And EXT:0x80 specifically isn’t even skipped: draw_image is implemented (the arc_image band renders in the browser, shell, and pygame window when a Blorb hangs art), and a stray call with no band claimed passes quietly. Rabenstein is in my corpus with a recording, so this is exercised continuously.
However, checking the Standard’s own words just now turned up a subtlety worth acting on. §14.2.1 says unknown extended opcodes in EXT:30 to EXT:255 should be simply ignored (“perhaps with a warning message somewhere off-screen”). Voxam’s skip floor is 128: I treat 30–127 (the reserved-for-future-Standards band) as loud errors, on the reasoning that it’s the Standard’s own table space. That was a loud-beats-wrong instinct, but it’s actually contrary to the cited text: the ignore range starts at 30, not 128. Fidelity-with-citations should win here, and the Standard’s parenthetical shows how to keep both principles: skip it and warn. I’ll implement that.
**ARCTURUS 2.0 IS OUT: **
all about 8-bit optimization (arcc 2.0.2, Cosmos 1.18.1)
NOTE: Before you read further… I want to emphasise that I had to benchmark the 2.x changes against “something”. Hibernated 2 exists as both a PunyInform build and an Arcturus build. So the game was an ideal candidate, holding the same rooms, same puzzles and the same opening commands. PunyInform is also a system that many people, including myself, can relate to a lot. This comparison is rather meant scientific than competitive and the results don’t make PunyInform less awesome.
What I did: both stories played through the same interpreter, measured cycle-exact on an Atari 8-bit 64K computer using Varuna (my own Atari 8-bit Z-machine version 5 interpreter). A first measurement already found Arcturus generating better code than PunyInform per instruction, but executing three times as many of them per turn. When I started working on Arcturus it was all about the features and efficient Z-code in size but version 2 is now about optimising 8-bit performance.
Arcturus 2.0 removes any object loop instead of tuning it. The compiler already knows which objects own which words, so every story file now carries a word-to-owners index: The cost of a command now follows the words you typed, never the size of your game, so this matters more, not less, as games grow.
The numbers, z-machine instructions per command, Hibernated 2:
command PunyInform Arcturus 1.x Arcturus 2.0
push grill 1,885 6,239 1,280
take spray oil ~2,400 10,272 1,733
go north ~2,000 1,716 1,716
ten-command session ~18,035 53,385 16,700
Verb turns now run fewer instructions than the PunyInform build, on top of already-cheaper instructions. And measured on the Atari, wall-clock CPU per command:
command PunyInform Arcturus 2.0
push grill 1.69s 0.90s
take spray oil 2.45s 1.30s
talk to vlad 1.91s 1.61s
go north 2.03s 1.41s
ten-command session 17.1s 12.5s
The Arcturus build is also still the smaller one: 134,072 bytes against 137,728 on disk, and where it really hurts an 8-bit machine, dynamic memory, 6,535 bytes (Arcturus) against 10,078 (Puny) which on the same interpreter also means fourteen more cache slots for the pager and fewer disk reads at boot.
Nothing changes in your source and nothing needs to be declared. arcc --update brings version 2 in, and your performance will be significantly improved on 8-bit machines.
That’s some truly impressive gains. Congratulations!
Thank you very much Fredrik!
![]()
I’m not sure if I should ask my question here, but oh well:
An object with the fixed attribute can be moved onto or into a container/supporter object using the verbs Insert or Put in/on. Isn’t there an implicit Take action that handles the fixed attribute check?
Source file: examples/features/adjectives.storyarc
>get couch
The red couch stays exactly where it is.
>open chest
Open.
>insert couch in chest
Done.
That was a real bug and almost couldn’t believe this remained unnoticed. It is fixed now arcc --update
Another sneak peek. This is Ganymede, the Arcturus reference interpreter for the Commodore 64. Soon to be released together with the other retro interpreters that I’ve been working on, all will be able to carry Arcturus images and close the loop for authors of lillustrated adventure games to support a wide range of hosts from retro computers to modern desktops.
The Ganymede build utility will export to the following media: .D64, .D81, Tape, Cartridge (!). The .crt export is particuarly interesting. Ganymede is the first Z-machine interpreter for a 1980s home computer that boots and runs directly from cartridge ROM, at least to my knowledge. There also is no Z-machine interpreter for C64 available that supports tape operations.. except:. when Infocom gave ZZAP!64 the permission to bundle Mini-Zork for the ZZAP!64 Mega tape, they released it with a z3 interpreter that also supported tape operations. But I never got it running. So I guess that doesn’t fully count ![]()
As the screenshot of Ganymede running Hibernated 2 shows, the interpreter will work just fine with text-only games.


