Yeah, I agree that test above is incorrect.
The only thing it should potentially care about IMO is if header.static_start < header.globals_addr + 480
Yeah, I agree that test above is incorrect.
The only thing it should potentially care about IMO is if header.static_start < header.globals_addr + 480
Yeah, that is as it should be. Some corners had to be cut to make a 2.5 KB Z-code game.
It relies heavily on printing to memory for parsing, but apart from that it uses quite limited Z-machine features - no objects, no dictionary, no scan_table or copy_table operations.
Note that the source code is also available from the IFDB page. It doesn’t use print_unicode.
LOL, I can download the .z5 just fine but the scandalous, dangerous source code is gated by the UK online safety act page.
-Dave
!% -~S
!% $OMIT_UNUSED_ROUTINES=1
!% $ZCODE_LESS_DICT_DATA=1
!% $OMIT_SYMBOL_TABLE=1
!% $ZCODE_COMPACT_GLOBALS=1
!% $ZCODE_MAX_INLINE_STRING=4000
!% $TRANSCRIPT_FORMAT=1
Release 2; Serial "251002";Abbreviate"ntain pass""the rive""treasure"" to the"
"You ""thing "" the ""sword""troll""ragon""The ""ing""ive"" a "" it" ".^";
#Ifv3;Global p1=Room;Global p2; Global p3;Object Room " ";#Endif;Global w=_w;
Global y=_y;Global t=_t;Global f=_f;Global g=_g;Global k=_k;Global h=_h;Array e
->1 3 2 2 6 1 3 1 2 6 5 5 5 5 5 4 5 5 1 7 4 1 5 1 1;Array _g->5 7 7 6 4 13 15
15 15 6 9 11 11 11 10 5 7 6 4 4 9 11 10 9 2;Array c-->"stick""rope""key""ring"
"sword""treasure""mountain pass""mountain pass""gate to the north""gate to the
south""dragon""troll";Array _y-->3 11 5 21 0 20 12 17 18 19 25 1;Array b-->"on
a plain""in the forest""in the desert""in a swamp""in the mountains""by the
river""in a cave";Array u-->'n//''s//''e//''w//''take''jump''drop''kill''give'
'i//''quit''q//';Array _h-->"Road is blocked""What???""You can't take it!"
"Where is it?""You pick it up""You drop it""The rope hangs across the river"
"Nothing happens""You kill the dragon with the sword.""You unlock the gate.";
Array _f->43;Array _t->26;Array _w->1 2 4 8;Array d-->1 (-1) 5 (-5);Array _k->3
*12;[ps a;@output_stream 3 f;print(string)a;@output_stream -3;];[main p q r m a
s x v x3;print"The Dragon and the Troll^^Steal the treasure^";for(x=0:x<12:x++)
{ps(c-->x);for(p=0:p<3:p++)k->(3*x+p)=f->(2+p);}p=1;r=1;m=1;.i;s=m;m=0;if(s==0)
m=1;.j;if(m){print(string)h-->(m-1),"^";p=a;m=0;}print"^You are ",(string)b-->(
e->(p-1)-1),"^You see ^";for(x=0:x<12:x++)if(y-->x==p)print(string)c-->x,"^";if
(q&&p==10 or 5)print(string)h-->6,"^";if(r){r=0;print"The troll wants its ring
back.^";}g->17=6;g->18=4;if(y-->2<0){g->17=7;g->18=6;if(p==18 or 19){print
(string)h-->9,"^";}}print"You can go ";for(x=0:x<4:x++)if(g->(p-1)&(w->x))print
(address)u-->x,",";new_line;.n;print "? ";f->0=40;t->0=6;read f t;v=0;if(t->1>0
)for(x=0:x<12:x++)if(t-->1==u-->x)v=x+1;if(v==0){print"Eh?^";jump n;}a=p;switch
(v){1,2,3,4:m=(g->(p-1))&(w->(v-1));if(m)p=p+(d-->(v-1));jump i;5:if(t->1>1)for
(x=0:x<12:x++){x3=3*x;if(k->x3==f->(t->9)&&k->(x3+1)==f->(1+t->9)&&k->(x3+2)==f
->(2+t->9)&&y-->x==p){m=5-2*(x>5);if(m==5)y-->x=-1;jump j;}}m=2;jump j;7:if(t->
1>1)for(x=0:x<12:x++){x3=3*x;if(k->x3==f->(t->9)&&k->(x3+1)==f->(1+t->9)&&k->(
x3+2)==f->(2+t->9)){m=6-2*(y-->x>-1);if(m==6)y-->x=p;if(p==10&&x==1&&y-->x==p){
m=7;q=1;g->9=14;y-->1=0;}if(p~=1||x~=5)jump j;.z;print"When you give the chest
to the troll he gives you a kingdom and lots of gold. You live happily ever
after.^";jump o;}}m=2;jump j;6:p=p-(5*(p==17))+(5*(p==12));if(y-->0>0||a==p)m=8
;jump j;10:print"Inventory:^";for(x=0:x<12:x++)if(y-->x<0)print(string)c-->x,
"^";jump j;8:if(p==25&&y-->10==p&&y-->4<0){m=9;g->24=10;y-->10=0;jump j;}print
"Something kills you.";9:if(p==1&&y-->3<0){y-->3=0;y-->4=-1;print"You get a
sword by the troll.^";jump j;}if(p==1&&y-->5<0)jump z;m=8;jump j;}.o;print
"^Press [RETURN] to exit.^";read f t;];
That’s… gloriously awful, I love it!
Favorite death message ever:
“Something kills you.”
OMG, Henrik, think of the children.
Yeah, this check is definitely too aggressive. There’s no need at all to even check the property defaults table here, because the full object table is checked later. Looking at the history, this line evolved around 15 years ago to look like this, with no real good reason for it. I’ll fix this up.
Edit: Hah, and the globals are properly checked a few lines below as well. This check serves no purpose whatsoever other than to break well-formed games. Oops.
I tried building Yazmin from source but Xcode refused to do anything because I need to set up dev accounts or something. Haven’t touched Xcode in several years. I was hoping I could step through the debugger to figure out what was going wrong.
Looking at the address it was crashed on, it was the second byte of a @jz instruction in a completely different function than the one I was expecting to be called, sigh.
I guess the next thing to try would be a full walkthrough (if there is one) for Anchorhead that I could run through my interpreter to see if there are any other diverges. Closest I’ve come to this before was doing something similar with Zork I. It got pretty far then but diverged, possibly due to RNG differences.
EDIT - sounds like some things are randomized so that’s not going to work for Anchorhead.
Random aside… it seems if you try to mention @inform_assemblywithout backquoting it, every time you edit the message it will replicate.
Just checked, and Infocom does it without a qualm. That is, if you say there are always 240 globals, then the highest unused ones overlap with array space.
In Zork 3, the globals start at $1F10; the highest global is 191; then arrays start at $2090 with the C-TABLE array.
I don’t know if anybody’s tested what happens if you use a global that overlaps array space. The common behavior is “bytes is bytes”, but there might be an interpreter that “hoists” globals into a native array for efficiency.
I once did a search through their releases and as I remember it it started sometime around 1987. My guess is that they modified Zilch around this time to squeeze in Mini-Zork.
I’m actually very curious about this—I’m working with an 8-bit platform where dynmem access over >48KB is comparatively expensive, so copying globals offline makes a huge speed difference in larger games that store globals high. Is there any genuine hazard in doing this? The I6 games I’ve tested don’t seem to care.
I expect there is no hazard, because both ZIL and Inform are careful to avoid this kind of collision.
Managed to get Yazmin to build. Doesn’t seem to have any built-in tracing but I did uncomment a printf with the current PC
Here’s a print function (which, admittedly, it shouldn’t even be calling here)
000c20 jz TOS ?c31 [ a0 00 d0 ]
000c23 loadb local0 0 → TOS [ 50 01 00 00 ]
The PC values Yazmin prints out are
00c20
00c25
00c26
00c2b
Which explains the crash. For some reason it thinks jz is a longer instruction than it really is.
0xA0 is the “variable” form of 1op jz. 00 is TOS. 0xD0 is 0b1101’0000. So the branch is negated, and only one byte, and jumps forward 16 bytes. Not sure how it could be decoding that?
Okay, looking further back something has gone wrong much sooner, and it’s massively mis-decoding things. It managed to jump into a completely different routine. So I need to look at all the program counter values and see where it’s gone haywire.
Does Mini-Zork do something weird with global variables? I missed the context of this.
Nothing weird but Mini-Zork r34 use compact globals. My guess is that they introduced this when trying to fit Zork 1 on a C64 tape.
The Standard allows games to write to globals via RAM:
It is legal for games to alter any of the tables stored in dynamic memory above the header, provided they leave the tables in legal states.
But I don’t think I’ve ever seen it happen, and can’t think of any reason why it would be useful to. Putting a value into a register is always cheaper than putting it into RAM.
A few of the interpreter quirks that the Dialog compiler has to cope with:
@undo before the first @save_undoI believe all of these are fixed now, but older interpreters still exist, so it’s good for a compiler to know about them.
But I don’t think I’ve ever seen it happen, and can’t think of any reason why it would be useful to.
We were just talking about the idea of changing one object’s property table address to point to a different object’s property table. (As a cheap runtime way to make the objects identical and not have to duplicate property data.)
Just a hypothetical, but it’s an example of altering the object table dynamically.
I’ve thought about altering a dict word’s flags dynamically too, for nasty parsing hacks. Never did it. (I have altered the I6 grammar table dynamically, but that’s not a Z-machine table.)
Finally found the source of the divergence. It’s coming from a “short” jump instruction. My code thinks the jump target is c9f, and Yazmin thinks it’s ca0, and everything goes to hell from there.
-Dave
void ZMProcessor::jump() {
log(“jump”, false, false);
int16_t offset = static_cast<int16_t>(getOperand(0));
_pc = _pc + offset + 1;
}
My guess is that code isn’t accounting for the operand size.
Yup, fixed it:
void ZMProcessor::jump() {
log(“jump”, false, false);
int16_t offset = static_cast<int16_t>(getOperand(0));
advancePC();
_pc = _pc + offset - 2;
}