Run-time problem P39 when changing the number of input words in Counterfeit Monkey

I still haven’t given up on the apostrophe stuff, and I though it might be better to make this specific problem a separate topic.

(Note that this no longer has anything to do with curly quotes. From here on we will assume that all quotes are in fact straight, no matter how they look.)

I’ve added a rule that makes ASK WHAT’S WORTH SEEING kind of work:

A first after reading a command rule (this is the really standardize apostrophes rule):
	if the player's command includes "what's":
		replace the matched text with "what is";

We could ask what is worth seeing in the New Church.

>ask what’s worth seeing

“So tell me, what should I be looking for in the New Church?” we ask.

“Other than God?” he asks dryly.

Hm — the volunteers aren’t very well trained, are they? I was expecting something about the points of architectural interest.

*** Run-time problem P39: Attempt to say a snippet value which is currently invalid: words 5 to 4.

The run-time problem clearly has something to do with changing the number of words in the input (the single word WHAT’S is changed to the two word WHAT IS.) But it also has something to do with ASK: the problem won’t happen if you type WHAT’S WORTH SEEING without the ASK.

I really don’t know where the game is trying to actually print the player input, which I assume is what “Attempt to say a snippet value” means. I suspect it may have something to do with the Smarter Parser extension, which stores the input as “the reborn command”, and does string operations on it.

Also, the Subcommands extension does things with snippets which I don’t quite understand.

EDIT: If I include the word THE anywhere in the input, the run-time problem won’t happen. Perhaps this has something to do with it triggering the cut the the rule.

2 Likes

That particular run-time problem (#39) is for code RTP_SAYINVALIDSNIPPET, and it is being issued by a routine PrintSnippet() in Parser.i6t. [EDIT: corrected routine name; I was clearly too tired the first day I was looking at this!]

For the exact error message that you cite, the snippet “raw” value would be 500. Invalid zero-length snippets can be produced by earlier versions of Subcommands, but I think that @Draconis has corrected the issue. (That extension is actually a legitimate suspect in this case, because it looks like the command specifies a topic but no object, so Subcommands may have trouble assigning a snippet to the noun.) What version of Subcommands are you using?

2 Likes

Apparently not the latest version. Thanks for pointing that out! Unfortunately, replacing it with the current one on Github (Version 2/221104) does not seem to affect the issue at hand. The same run-time problem occurs under the same circumstances.

EDIT: To clarify, the problem seems to be that some code responsible for saying the contents of a snippet tries to say word 5 of the original input, “ask what’s worth seeing”, which only contains four words, rather than word 5 of the “replaced” input, “ask what is worth seeing”, which contains five words. That is, it has the word count right, but it is trying to say (the last two words of) the wrong snippet.

I have no idea why it is the last two words, though, or why it is trying to say them in reverse order (words 5 to 4 rather than words 4 to 5).

No, that’s not quite right. A snippet comprising 2 words is represented by a number ending in x02 where x is the starting word number. As @otistdog says, the error message being printed here implies a snippet that would be invalid for any player’s command- one of numeric value 500. This represents words in the player’s command starting at (500/100)=5, and ending at ((500/100)+((500%100)-1)) = ((5+0)-1) = 4

1 Like

To have any real chance of solving this, it will be necessary to pin down the exact code that is causing the RTP_SAYINVALIDSNIPPET.

Unfortunately, the RTP message can also be issued while RTP reporting is “suspended,” so that it will only be reported after the point in the code where it is generated (with the printed report happening just prior to issuing the command prompt). That makes it a lot trickier to track down.

I’m curious to know how the >ASK… command is being processed as an action. What does the error output look like with >ACTIONS and >RULES enabled?

1 Like

It’s quite a bit of output. Here it is.

EDIT: For comparison, here is the output from just typing WHAT’S WORTH SEEING, which won’t cause the run-time error. Not sure if this is helpful in any way.

EDIT 2: And here is the output for THE ASK WHAT’S WORTH SEEING, which also won’t trigger the run-time error.

Hm, the culprit seems to be the new strip interlocutor from input rule. If I add and the player's command does not include "ask/tell/a/t what" to the conditions, the error is gone.

This rule is meant to turn input like ASK BROCK WHY into ASK WHY. But it isn’t supposed to have any effect unless the input refers to “someone talk-eligible”, i.e. the person we are currently talking to, BROCK in the example above, so I’m not sure what is going on here.

EDIT: The same run-time error will occur after adding an after reading a command rule that expands “he’s” to “he is”:

>ask why he’s here

“If you aren’t interested in this place, why do you volunteer here?” we ask.

His left eyelid twitches. “The Rosehip woman,” he says, after a moment. “She’s a looker.”

I’m not sure what’s more disturbing: someone expressing attraction to my mother; that person being a good thirty years her senior; or the use of the phrase “she’s a looker”, which even this old character probably got from a movie.

I’d like to remind the gift shop volunteer that Mrs. Rosehip is married.

*** Run-time problem P39: Attempt to say a snippet value which is currently invalid: words 5 to 4.

Again, it goes away if I add and the player's command does not include "ask/tell/a/t why" to the new strip interlocutor from input rule.

Based on the placement, I’d guess that this is a deferred report – the report comes very late in the turn processing, and it does not seem to be related to any particular output. What that means is that it’s happening during a call to TEXT_TY_CastPrimitive().

[EDIT: It turns out there are other ways than TEXT_TY_CastPrimitive() to reach PrintSnippet()! Also, errors in the original untested code below have been fixed.]

If you think you have it tracked down to a particular rule, I would suggest attempting to prove that out. You can try to display the RTP “early” with a special phrase:

To force pending RTP with code (N - number):
	(- if (RTP_Buffer-->0 ~= NULL) { print "(CODE ", {N}, ") "; RunTimeProblemShow(); ClearRTP(); } -).

That should produce no output unless a deferred RTP is waiting. You can insert it anywhere you like into your rules (such as the new strip interlocutor from input rule) like so:

force pending RTP with code 1;
[suspect code here]
force pending RTP with code 2;

Use different code numbers for different instances of the phrase, so that you can tell where it happens. For the example above, if you see a code 2 output but not a code 1 output, you know that it happens in the bracketed suspect code.

You might also want to try to figure out where the invalid snippet value is coming from in the first place. Something like the following should identify objects with invalid subcommand snippets (but may have an awful lot of output in a large game):

To decide whether the raw snippet for (O - object) is zero-length:
	(- (({O}.parsed_number)%100 == 0) -).

To decide which number is the raw value of (SN - snippet):
	(- {SN} -).

First before doing anything (this is the look for bad snippets rule):
	repeat with O running through objects:
		if the raw snippet for O is zero-length:
			say "ZERO-LENGTH SNIPPET for [O]: [raw value of subcommand of O]."

Note that all of the above is code for 6M62.

3 Likes

Far as I can tell, the zero-length snippet is for the gift shop volunteer. (ZERO-LENGTH SNIPPET for gift shop volunteer: 100.)

He is the actor of the action the gift shop volunteer discussing what is worth seeing in the New Church, and of course there is no part of the command WHAT IS WORTH SEEING which refers to him.

I still haven’t found where exactly the attempt to read the subcommand of the actor happens, though.

(By the way, I had to change
(- ({O.parsed_number}%100 == 0) -)
to
(- ({O}.parsed_snippet%100 == 0) -)
to make it compile.)

Yes, sorry about the error – you made the correct change.

I’m a little surprised that the only zero-length subcommand has a raw value of 100 instead of 500, as this does not fit with the error message that you posted above. Is the RTP always code P39 and does it always say words 5 to 4?

If you’re up for a little surgery on template files, you can try to make the following change (for 6M62 Glulx):

Text.i6t: Glulx Version
Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Text.i6t: Glulx Version
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

#ifnot; ! TARGET_ZCODE
[ TEXT_TY_CastPrimitive to_txt from_snippet from_value
	len i stream saved_stream news buffer buffer_size memory_to_free results;

	if (to_txt == 0) BlkValueError("no destination for cast");

	buffer_size = (TEXT_TY_BufferSize + 2)*WORDSIZE;
	
	RawBufferSize = TEXT_TY_BufferSize;
	buffer = RawBufferAddress + TEXT_TY_CastPrimitiveNesting*buffer_size;
	TEXT_TY_CastPrimitiveNesting++;
	if (TEXT_TY_CastPrimitiveNesting > TEXT_TY_NoBuffers) {
		buffer = VM_AllocateMemory(buffer_size); memory_to_free = buffer;
		if (buffer == 0)
			FlexError("ran out with too many simultaneous text conversions");
	}

	if (unicode_gestalt_ok) {
		SuspendRTP();
		.RetryWithLargerBuffer;
		saved_stream = glk_stream_get_current();
		stream = glk_stream_open_memory_uni(buffer, RawBufferSize, filemode_Write, 0);
		glk_stream_set_current(stream);

		@push say__p; @push say__pc;
		ClearParagraphing(7);
		if (from_snippet) print (PrintSnippet) from_value;
		else print (PrintI6Text) from_value;
		@pull say__pc; @pull say__p;

		results = buffer + buffer_size - 2*WORDSIZE;
		glk_stream_close(stream, results);
		if (saved_stream) glk_stream_set_current(saved_stream);
		ResumeRTP();
		RunTimeProblemShow();	! ADDED

		len = results-->1;
		if (len > RawBufferSize-1) {
			! Glulx had to truncate text output because the buffer ran out:
			! len is the number of characters which it tried to print
			news = RawBufferSize;
			while (news < len) news=news*2;
			i = VM_AllocateMemory(news*WORDSIZE);
			if (i ~= 0) {
				if (memory_to_free) VM_FreeMemory(memory_to_free);
				memory_to_free = i;
				buffer = i;
				RawBufferSize = news;
				buffer_size = (RawBufferSize + 2)*WORDSIZE;
				jump RetryWithLargerBuffer;
			}
			! Memory allocation refused: all we can do is to truncate the text
			len = RawBufferSize-1;
		}
		buffer-->(len) = 0;

		TEXT_TY_CastPrimitiveNesting--;
		BlkValueMassCopyFromArray(to_txt, buffer, 4, len+1);
	} else {
		RunTimeProblem(RTP_NOGLULXUNICODE);
	}
	if (memory_to_free) VM_FreeMemory(memory_to_free);
];
#endif; 

-) instead of "Glulx Version" in "Text.i6t".

It’s a minor change that just causes display of any deferred RTP immmediately after coming out of suspend mode, so it should print the RTP while the relevant rule is running, making it much easier to spot with >RULES or >RULES ALL enabled. Note that this would supersede the force pending RTP with code... phrase.

(Come to think of it, it might be desirable to change the template code to work this way in general. It’s not clear to me why it’s desirable to defer the report until the routine for prompt production.)

I realise now that I had become lazy and typed ASK WHAT’S WORTH instead of ASK WHAT’S WORTH SEEING, so it said words 4 to 3 instead. But if I use the longer command instead, it still says ZERO-LENGTH SNIPPET for gift shop volunteer: 100 , not 500.

So it’s always “word (N+1) to N” where N is the number of words in the entered command?

That seems to be the case.

With the updated Text.i6t, the run-time problem seems indeed to occur in the new strip interlocutor from input rule.:

...

[Rule “remove stray punctuation rule” applies.]
[Rule “new strip interlocutor from input rule” applies.]

*** Run-time problem P39: Attempt to say a snippet value which is currently invalid: words 4 to 3.

[Rule “replace highlight with hilight rule” applies.]

...

I could never make those force pending RTP with code N show any output, though, so I don’t know which exact line causes it.

If the raw value of the gift shop volunteer’s subcommand is 100, then I don’t think that can be the cause. If PrintSnippet() is fed a value of 100, it won’t throw an RTP – it just does nothing.

Proximity to a rule’s name in the >RULES output does not necessarily point out the culprit. An RTP can be caused by a rule preamble being evaluated, for example, and that might be the case here. Were you using >RULES or >RULES ALL for the debug output in your last post?

I think the next thing to look at is which rule(s) are checked between the new strip interlocutor from input rule and the replace highlight with hilight rule.

Right. Here are the rules checked between new strip interlocutor from input rule and the error message (that should be enough, right?) with RULES ALL active.

https://gist.github.com/angstsmurf/8f614f91f251ada05d1c8333cf025658

If anyone wants to play along, I created a branch with the testing code included here:
https://github.com/i7/counterfeit-monkey/tree/snippet-bug

Just make a (6M62) debug build, and after the initial questions (Y, ANDRA) type >GONEAR VOLUNTEER and then >ASK WHAT’S WORTH

EDIT: Or type ACTIONS and RULES ALL before the ASK command if you want more debug output, of course.

What are the next lines after the RTP message? The ones that talk about the replace highlight with hilight rule?

Here is the rest of the output, starting at the line before the RTP:

https://gist.github.com/angstsmurf/088b54495dbacdadb02c55af7fc13017

OK. You are right that the error occurs in the new strip interlocutor from input rule. The specific line causing the RTP is:

	let M be the substituted form of the matched text;

It is the I6 global to hold the snippet of the matched text (called matched_text) that is being set to 500.

This variable is set in response to the condition:

if the player's command includes "[someone talk-eligible]":

on the preceding line, which is an interesting use of the phrase. The relevant code is compiled as I6 in this form:

if (((matched_text=SnippetIncludes(Consult_Grammar_1434,players_command))))

with supporting routine

[ Consult_Grammar_1434
	range_from ! call parameter: word number of snippet start

	range_words ! call parameter: snippet length

	original_wn ! first word of text parsed

	group_wn ! first word matched against A/B/C/... disjunction

	w ! for use by individual grammar lines

	rv ! for use by individual grammar lines

	;
	wn = range_from; original_wn = wn; rv = GPR_PREPOSITION;
	    w = ParseTokenStopped(ROUTINE_FILTER_TT, Noun_Filter_348);	! filter is logic for "talk-eligible"
	    if (w == GPR_FAIL) jump Fail_1; rv = w;
	    if ((range_words==0) || (wn-range_from==range_words)) return rv;
	    .Fail_1; rv = GPR_PREPOSITION; wn = original_wn;
	return GPR_FAIL;
];

and template routine

[ SnippetIncludes test snippet w1 w2 wlen i j;
	w1 = snippet/100; w2 = w1 + (snippet%100) - 1;
	if ((w2<w1) || (w1<1)) {
		if ((w1 == 1) && (w2 == 0)) rfalse;
		return RunTimeProblem(RTP_INCLUDEINVALIDSNIPPET, w1, w2);
	}
	if (metaclass(test) == Routine) {
		wlen = snippet%100;
		for (i=w1, j=wlen: j>0: i++, j--) {
			if (((test)(i, 0)) ~= GPR_FAIL) return i*100+wn-i;	! *any* non-GPR_FAIL result treated as success
		}
	}
	rfalse;
];

SnippetIncludes() is being fed the player's command as the snippet parameter, so it always takes the form of 100 plus the number of words in the entered command. For a 5-word command, w1 si set to 1 and w2 is set to 5. The provided test parameter is a routine, so wlen is also set to 5.

The routine runs a loop using the provided test routine, in this case Consult_Grammar_1434(). The routine is fed parameters (1,0), then (2,0) on to (5,0), looking for a result that is not GPR_FAIL. In this case, it does get such a result on the fifth cycle, i.e. on the call Consult_Grammar_1434(5,0).

The result that it receives is the object ID for the gift shop volunteer object. I think that this is happening because of a parser inference, but I haven’t traced it out. It makes sense, though, because only one object can be the current interlocutor.

As I understand it, the parser’s logic will only make an inference of that type when it thinks that it’s past the end of the entered command. If that’s correct, then in theory the parsing should fail instead of returning the current interlocutor; inference should not be happening because the adjusted player's command is >ASK WHAT IS WORTH SEEING, so ParseToken() should be rejecting the word “seeing” as applicable to the gift shop volunteer.

2 Likes