Strange RTP - complaint re: unprovided property due to incorrect value of self?

(EDIT: I was able to simplify the demonstration code, and I realized that I missed some steps in my previous trace, resulting in serious confusion. This version connects more dots and removes unwarranted speculation.)

I ran into this on 6M62 but confirmed that it also happens in 10.1.2.

It’s probably easiest to just start with the demo code:

“Pile RTP”

A pile is a kind of thing. A pile has a number called count. The initial appearance of a pile is "Strewn about here are [count in words] shiny rock[s]."

Place is a room.

There is a pile in Place. It has count 12.

A supporter called a platform is in Place.

Test me with "abstract pile to platform / look / abstract pile to place / look".

From what I can tell, the particular code causing it is found in the initial appearance on supporters rule:

For printing a locale paragraph about a supporter (called the tabletop)
	(this is the initial appearance on supporters rule):
	repeat with item running through not handled things on the tabletop which
		provide the property initial appearance:
		if the item is not a person and the initial appearance of the item is not "" [<-- here]
			and the item is not undescribed:
			now the item is mentioned;
			say initial appearance of the item;
			say paragraph break;
	continue the activity.

The I6 version of this code (6M62 output) is (with comments added):

! For printing a locale paragraph about a supporter ( called the tabletop ) ( this is the initial appearance on supporters rule ):
[ R_94 
	tmp_0 ! Let/loop value, e.g., 'tabletop': supporter
	tmp_1 ! Let/loop value (deallocated by end of phrase)
	tmp_2 ! Let/loop value (deallocated by end of phrase)
	;
	if ((((tmp_0 = parameter_value, (((parameter_value ofclass K6_supporter) && (tmp_0=(parameter_value), true))))) || (tmp_0 = 0,false))) { ! Runs only when pattern matches
	if (debug_rules) DB_Rule(R_94, 94);
	! [2: repeat with item running through not handled things on the tabletop which provide the property initial appearance]
	for (tmp_1=Prop_7(tmp_0,tmp_0,tmp_0,0), tmp_2=Prop_7(tmp_0,tmp_0,tmp_0,tmp_1): tmp_1: tmp_1=tmp_2, tmp_2=Prop_7(tmp_0,tmp_0,tmp_0,tmp_2))
	{! [3: if the item is not a person and the initial appearance of the item is not ~~ and the item is not undescribed]
	    if ((((~~((tmp_1 ofclass K8_person))))) && ((((~~(( BlkValueCompare(GProperty(10, tmp_1,initial), EMPTY_TEXT_VALUE) == 0))))) && (((~~(((Adj_64_t1_v10(tmp_1)))))))))	! <-- BlkValueCompare() generates RTP?
	    {! [4: now the item is mentioned]
	        (Adj_67_t2_v10(tmp_1));
	        ! [5: say initial appearance of the item]
	        say__p=1;! [6: initial appearance of the item]
	        ParaContent(); @push self; print (TEXT_TY_Say) GProperty(10, self=tmp_1,initial);@pull self;  .L_Say22; .L_SayX22;! [7: say paragraph break] ! <-- compare GProperty() arguments here
	        say__p=1;! [8: paragraph break]
	        ParaContent(); DivideParagraphPoint(); new_line; .L_Say23; .L_SayX23;}
	        }
	! [9: continue the activity]
	rfalse;
	RulebookSucceeds(); rtrue;
	} else if (debug_rules > 1) DB_Rule(R_94, 94, 'action');
	rfalse;
];

The RTP ultimately must result from a call to WhetherProvides(). That is the only routine that calls RunTimeProblem() with parameter RTP_UNPROVIDED, which results in the message seen.

From the tracing that I did, it seemed that somehow the value of self as evaluated while printing the initial appearance of the pile was incorrect. The routine that is compiled to print the initial appearance is (with comment added):

[ R_TX_S_134 ;
	#ifdef DEBUG; if (suppress_text_substitution) { print "~Strewn about here are [count in words] shiny rock[s].~"; rtrue; }
	#endif; ! DEBUG
	! [1: ~Strewn about here are [count in words] shiny rock[s].~]
	say__p=1;! [2: ~Strewn about here are ~]
	ParaContent(); print "Strewn about here are ";! [3: count in words]
	ParaContent(); @push self; print (number) say__n=(GProperty(OBJECT_TY, self=self,p15_count));@pull self; ! [4: ~ shiny rock~] ! <-- self set wrong here?
	ParaContent(); print " shiny rock";! [5: s]
	ParaContent(); STextSubstitution();! [6: ~.~]
	ParaContent(); print "."; .L_Say470; .L_SayX468;rtrue;
];

To try to prove this, I built a modified version of the initial appearance on supporters rule:

To force self to (O - object):
	(- self = {O}; -).

To decide which object is current self:
	(- self -).

For printing a locale paragraph about a supporter (called the tabletop)
	(this is the alternate initial appearance on supporters rule):
	repeat with item running through not handled things on the tabletop:
		if item provides the property initial appearance:
			if the item is not a person:
				if the item is not undescribed:
					if the initial appearance of the item is not empty:
						now the item is mentioned;
						let saved self be current self;
						force self to item; 
						say initial appearance of the item;
						force self to saved self;
						say paragraph break;
	continue the activity.


The alternate initial appearance on supporters rule is not listed in any rulebook.

The alternate initial appearance on supporters rule substitutes for the initial appearance on supporters rule.

This change gets rid of the RTP.

The I7 compiler sets the value of self at many points, but the place at which it is set to the platform in this case appears to be

[ ProcessActivityRulebook rulebook parameter  rv;
	@push self;
	if (parameter) self = parameter;	! <-- HERE
	rv = FollowRulebook(rulebook, parameter, true);
	@pull self;
	if (rv) rtrue;
	rfalse;
];

when the printing a locale paragraph about activity is being run with a parameter of the platform. As seen in the generated I6 for the initial appearance on supporters rule above, this value of self is usually temporarily overwritten when printing the initial appearance of item:

@push self; print (TEXT_TY_Say) GProperty(10, self=tmp_1,initial);@pull self;

The same temporary override does not occur when evaluating whether the initial appearance of item is empty:

BlkValueCompare(GProperty(10, tmp_1,initial), EMPTY_TEXT_VALUE) ! <-- should temporarily override self as in preceding example?

The chain of calls to get from this point to the RTP appears to be:

BlkValueCompare()	! receives the initial appearance "text" (really a routine) for the pile as retrieved by GProperty() result used as argument
 TEXT_TY_Compare()	! by way of KOVSupportFunction(), with COMPARE_KOVS task
   TEXT_TY_Compare_Inner()
    TEXT_TY_Temporarily_Transmute()
     TEXT_TY_CastPrimitive()	! suspends RTP
      PrintI6Text()	! executes the initial appearance "text" in a context where (self == platform)?
       R_TX_S_134()	! invocation of initial appearance "text" routine; sets (self = self) when calling GProperty(), leaving undisturbed (self == platform)
        GProperty()	! called with arguments self/platform and count property
         WhetherProvides()	! generates RTP because platform does not have count property, but not displayed because RTPs are suspended

The tracing gets a bit tangled there, so I’m not 100% sure that I have it right, but it does seem that this may be the explanation for observed symptoms.

1 Like

OK, I’ve confirmed that the chain of calls listed above is the cause of the issue. Here’s a 6M62 version with lots of tell-tale code added to demonstrate the full sequence of events:

WARNING: Tracing Nightmare Enclosed
"RTP Chain of Events"

A pile is a kind of thing. A pile has a number called count. The initial appearance of a pile is "Strewn about here are [count in words] shiny rock[s]."

Place is a room.

A pile called test pile is in Place. It has count 12.

A supporter called a platform is in Place.

Test me with "abstract pile to platform / look / abstract pile to place / look".

Include (-

Global TEST_PILE_ARRAY_LOC;
Global TEST_PILE_ROUTINE_LOC;

-) after "Definitions.i6t".

To say show array location:
	(- TEST_PILE_ARRAY_LOC = (+ test pile +).(+ initial appearance +); print TEST_PILE_ARRAY_LOC; -).

To say show routine location:
	(- TEST_PILE_ROUTINE_LOC = ((+ test pile +).(+ initial appearance +))-->1; print TEST_PILE_ROUTINE_LOC; -).

When play begins:
	say "[line break]pile.initial array location = [show array location].";
	say "pile.initial routine location = [show routine location]."

Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! BlockValues.i6t: Comparison
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ BlkValueCompare bv_left bv_right  kind_left kind_right kovs;
	if (bv_left == TEST_PILE_ARRAY_LOC)
		print "^<BlkValueCompare received TEST_PILE_ARRAY_LOC as bv_left argument>";
	if ((bv_left == 0) && (bv_right == 0)) return 0;
	if (bv_left == 0) return 1;
	if (bv_right == 0) return -1;

	kind_left = BlkValueWeakKind(bv_left);
	kind_right = BlkValueWeakKind(bv_right);
	if (kind_left ~= kind_right) return kind_left - kind_right;

	kovs = KOVSupportFunction(kind_left, "impossible comparison");
	if (bv_left == TEST_PILE_ARRAY_LOC && kovs == TEXT_TY_Support)
		print "^<BlkValueCompare will use TEXT_TY_Support for comparison>";
	return kovs(COMPARE_KOVS, bv_left, bv_right);
];

-) instead of "Comparison" in "BlockValues.i6t".

Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Text.i6t: KOV Support
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ TEXT_TY_Support task arg1 arg2 arg3;
	switch(task) {
		CREATE_KOVS:      return TEXT_TY_Create(arg2);
		CAST_KOVS:        TEXT_TY_Cast(arg1, arg2, arg3);
		MAKEMUTABLE_KOVS: return TEXT_TY_Mutable(arg1);
		COPYQUICK_KOVS:   rtrue;
		COPYSB_KOVS:	  TEXT_TY_CopySB(arg1, arg2);
		KINDDATA_KOVS:    return 0;
		EXTENT_KOVS:      return TEXT_TY_Extent(arg1);
		COMPARE_KOVS:     
			!print "^<TEXT_TY_Support returning TEXT_TY_Compare>";
			return TEXT_TY_Compare(arg1, arg2);
		READ_FILE_KOVS:   if (arg3 == -1) rtrue;
				          return TEXT_TY_ReadFile(arg1, arg2, arg3);
		WRITE_FILE_KOVS:  return TEXT_TY_WriteFile(arg1);
		HASH_KOVS:        return TEXT_TY_Hash(arg1);
		DEBUG_KOVS:       TEXT_TY_Debug(arg1);
	}
	! We choose not to respond to: DESTROY_KOVS, COPYKIND_KOVS, COPY_KOVS
	rfalse;
];

-) instead of "KOV Support" in "Text.i6t".

Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Text.i6t: Comparison
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ TEXT_TY_Compare left_txt right_txt rv;
	@push say__comp;
	say__comp = true;
	rv = TEXT_TY_Compare_Inner(left_txt, right_txt);
	@pull say__comp;
	return rv;
];

[ TEXT_TY_Compare_Inner left_txt right_txt
	pos ch1 ch2 capacity_left capacity_right fl fr cl cr cpl cpr;

	if (left_txt == TEST_PILE_ARRAY_LOC)
		print "^<TEXT_TY_Compare received TEST_PILE_ARRAY_LOC as left_txt argument>";

	if (left_txt-->0 & BLK_BVBITMAP_LONGBLOCKMASK == 0) {
		fl = true;
		!print "<fl set>";
	} else print "<fl not set>";
	if (right_txt-->0 & BLK_BVBITMAP_LONGBLOCKMASK == 0) {
		fr = true;
		!print "<fr set>";
	} else print "<fr not set>";

	if (fl && fr) {
		if ((left_txt-->1 ofclass String) && (right_txt-->1 ofclass String))
			return left_txt-->1 - right_txt-->1;
		if ((left_txt-->1 ofclass Routine) && (right_txt-->1 ofclass Routine))
			return left_txt-->1 - right_txt-->1;
		print "<comparing Routine to String>";
		cpl = left_txt-->0; cl = TEXT_TY_Temporarily_Transmute(left_txt);
		cpr = right_txt-->0; cr = TEXT_TY_Temporarily_Transmute(right_txt);		
	} else if (fl) {
		cpl = left_txt-->0; cl = TEXT_TY_Temporarily_Transmute(left_txt);
	} else if (fr) {
		cpr = right_txt-->0; cr = TEXT_TY_Temporarily_Transmute(right_txt);
	}
	if ((cl) || (cr)) {
		print "<recomparing with transmuted version>";
		pos = TEXT_TY_Compare(left_txt, right_txt);
		TEXT_TY_Untransmute(left_txt, cl, cpl);
		TEXT_TY_Untransmute(right_txt, cr, cpr);
		return pos;
	}
	capacity_left = BlkValueLBCapacity(left_txt);
	capacity_right = BlkValueLBCapacity(right_txt);
	for (pos=0:(pos<capacity_left) && (pos<capacity_right):pos++) {
		ch1 = BlkValueRead(left_txt, pos);
		ch2 = BlkValueRead(right_txt, pos);
		if (ch1 ~= ch2) return ch1-ch2;
		if (ch1 == 0) return 0;
	}
	if (pos == capacity_left) return -1;
	return 1;
];

[ TEXT_TY_Distinguish left_txt right_txt;
	if (TEXT_TY_Compare(left_txt, right_txt) == 0) rfalse;
	rtrue;
];


-) instead of "Comparison" in "Text.i6t".

Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! Text.i6t: Transmutation
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ TEXT_TY_Transmute txt;
	TEXT_TY_Temporarily_Transmute(txt);
];

[ TEXT_TY_Temporarily_Transmute txt  x;
	if (txt == TEST_PILE_ARRAY_LOC)
		print "^<TEXT_TY_Temporarily_Transmute received TEST_PILE_ARRAY_LOC as txt argument>"; 
	if ((txt) && (txt-->0 & BLK_BVBITMAP_LONGBLOCKMASK == 0)) {
		x = txt-->1; ! The old value was a packed string
		
		txt-->0 = UNPACKED_TEXT_STORAGE;
		txt-->1 = FlexAllocate(32, TEXT_TY, TEXT_TY_Storage_Flags);
		if (x ~= EMPTY_TEXT_PACKED) TEXT_TY_CastPrimitive(txt, false, x);
		
		return x;
	}
	return 0;
];

[ TEXT_TY_Untransmute txt pk cp x;
	if ((pk) && (txt-->0 == UNPACKED_TEXT_STORAGE)) {
		x = txt-->1; ! The old value was an unpacked string
		FlexFree(x);
		txt-->0 = cp;
		txt-->1 = pk; ! The value earlier returned by TEXT_TY_Temporarily_Transmute
	}
	return txt;
];


-) instead of "Transmutation" in "Text.i6t".


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 == TEST_PILE_ARRAY_LOC) {
		print "^<TEXT_TY_CastPrimitive received TEST_PILE_ARRAY_LOC";
		if (to_txt-->1 == UNPACKED_TEXT_STORAGE)
			print " (transmuted version)";
		print " as to_txt argument>";
	}

	if (from_value == TEST_PILE_ROUTINE_LOC) {
		print "^<TEXT_TY_CastPrimitive received TEST_PILE_ROUTINE_LOC as from_value argument>";
	}

	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) {
		print "<RTP_Buffer-->0 before suspend: ", RTP_Buffer-->0, ">";
		print "<self before suspend: ", (PrintShortName) self, ">";
		print "<TEXT_TY_CastPrimitive suspending RTPs>";
		SuspendRTP();
		.RetryWithLargerBuffer;
		saved_stream = glk_stream_get_current();
		stream = glk_stream_open_memory_uni(buffer, RawBufferSize, filemode_Write, 0);
		!glk_stream_set_current(stream);
		print "^^<BEGIN NORMALLY SUPPRESSED TEXT>^^";

		@push say__p; @push say__pc;
		ClearParagraphing(7);
		if (from_snippet)
			print (PrintSnippet) from_value;
		else {
			if (from_value == TEST_PILE_ROUTINE_LOC)
				print "<TEXT_TY_CastPrimitive calling PrintI6Text on TEST_PILE_ROUTINE_LOC>^^";
			print (PrintI6Text) from_value;
		}
		@pull say__pc; @pull say__p;

		results = buffer + buffer_size - 2*WORDSIZE;
		glk_stream_close(stream, results);
		print "^^<END NORMALLY SUPPRESSED TEXT>^^";
		if (saved_stream) glk_stream_set_current(saved_stream);
		print "<TEXT_TY_CastPrimitive resuming RTPs>";
		if (RTP_Buffer-->0 == RTP_UNPROVIDED)
			print "<RTP_UNPROVIDED error registered>";
		ResumeRTP();

		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".


Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! RTP.i6t: Value Property
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ GProperty K V pr obj;
	if (K == OBJECT_TY) obj = V; else obj = KOV_representatives-->K;
	if (obj == 0) { RunTimeProblem(RTP_PROPOFNOTHING, obj, pr); rfalse; }
	if (obj == (+ test pile +) or (+ platform +))
		print "^<GProperty: ", (PrintShortName) obj, ".", (PrintPropertyName) pr, ", self = ", (PrintShortName) self, ">";
	if (obj provides pr) {
		if (K == OBJECT_TY) {
			if (pr == door_to) return obj.pr();
			if (WhetherProvides(V, false, pr, true)) return obj.pr;
			rfalse;
		}
		if (obj ofclass K0_kind)
			WhetherProvides(V, false, pr, true); ! to force a run-time problem
		if ((V < 1) || (V > obj.value_range)) {
			RunTimeProblem(RTP_BADVALUEPROPERTY); return 0; }
		return (obj.pr)-->(V+COL_HSIZE);
	} else {
		if (obj ofclass K0_kind) {
			if (obj == (+ test pile +) or (+ platform +)) print "<GProperty calling WhetherProvides>";
			WhetherProvides(V, false, pr, true); ! to force a run-time problem
			if (obj == (+ test pile +) or (+ platform +)) print "<GProperty returned from call to WhetherProvides>";
		}
	}
	rfalse;
];

-) instead of "Value Property" in "RTP.i6t".



Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! RTP.i6t: Whether Provides
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

[ WhetherProvides obj either_or p issue_rtp  off i textual a l;
	if (p == (+ count +) ) print "^<WhetherProvides: ", (PrintShortName) obj, ".", (PrintPropertyName) p, ", self = ", (PrintShortName) self, ">";
	if (metaclass(obj) ~= Object) rfalse;
	if (p<0) p = ~p;
	if (either_or) {
		if (p < FBNA_PROP_NUMBER) off = attributed_property_offsets-->p;
		else off = valued_property_offsets-->p;
	} else off = valued_property_offsets-->p;
	if (off<0) {
		if (issue_rtp) RunTimeProblem(RTP_BADPROPERTY, obj);
		rfalse;
	}
	textual = property_metadata-->off; off++;
	
	if (ScanPropertyMetadata(obj, off)) jump PermissionFound;
	if (obj provides KD_Count) {
		l = obj.KD_Count;
		while (l > 0) {
			a = l*2;
			if (ScanPropertyMetadata(KindHierarchy-->a, off)) jump PermissionFound;
			l = KindHierarchy-->(a+1);
		}
	}
	if (issue_rtp) {
		print "<WhetherProvides issuing RTP_UNPROVIDED error!>";
		RunTimeProblem(RTP_UNPROVIDED, obj, textual);
	}
	rfalse;

	.PermissionFound;
		if (either_or) rtrue;
		if (obj provides p) rtrue;
		if (issue_rtp) RunTimeProblem(RTP_UNSET, obj, textual);
		rfalse;
];

[ PrintPropertyName  p  off textual;
	if (p<0) p = ~p;
	off = valued_property_offsets-->p;
	textual = property_metadata-->off;
	print (string) textual;
];

-) instead of "Whether Provides" in "RTP.i6t".

Note that the rule being run in the initial looking action which involves the test pile is the use initial appearance in room descriptions rule. As with the initial appearance on supporters rule, the use initial appearance in room descriptions rule checks to see whether or not the initial appearance of item is empty, and the text output of this comparison is normally suppressed by TEXT_TY_CastPrimitive():

For printing a locale paragraph about a thing (called the item)
	(this is the use initial appearance in room descriptions rule):
	if the item is not mentioned:
		if the item provides the property initial appearance and the
			item is not handled and the initial appearance of the item is [<-- HERE]
			not "":
			increase the locale paragraph count by 1;
			say "[initial appearance of the item]";
			say "[paragraph break]";
			if a locale-supportable thing is on the item:
				repeat with possibility running through things on the item:
					now the possibility is marked for listing;
					if the possibility is mentioned:
						now the possibility is not marked for listing;
				say "On [the item] " (A);
				list the contents of the item, as a sentence, including contents,
					giving brief inventory information, tersely, not listing
					concealed items, prefacing with is/are, listing marked items only;
				say ".[paragraph break]";
			now the item is mentioned;
	continue the activity.

Execution of this rule does not cause an RTP in this case because the parameter of the for printing a locale paragraph activity is the test pile, so self is set to test pile by ProcessActivityRulebook(). As a consequence, when the rule makes the call to BlkValueCompare() in its I6 code, the attempt to produce suppressed text for comparison will not generate an RTP when the routine that prints the initial appearance of test pile is called.

Using >RULES before executing the test me makes it easier to see how the cycles through the tell-tale code correspond to the two different rules.

Compare that situation to the default initial appearance on supporters rule:

For printing a locale paragraph about a supporter (called the tabletop)
	(this is the initial appearance on supporters rule):
	repeat with item running through not handled things on the tabletop which
		provide the property initial appearance:
		if the item is not a person and the initial appearance of the item is not ""
			and the item is not undescribed:
			now the item is mentioned;
			say initial appearance of the item;
			say paragraph break;
	continue the activity.

When the pile is on the platfrom, execution of this rule does cause an RTP because the parameter of the for printing a locale paragraph activity is the platform, so self is set to platform by ProcessActivityRulebook(). As a consequence, when the rule makes the call to BlkValueCompare() in its I6 code, the attempt to produce suppressed text for comparison will generate an RTP when the routine that prints the initial appearance of test pile is called.

The alternate initial appearance on supporters rule does not generate an RTP, but the reason is not what I initially thought. The code tests the initial appearance property in a different way:

For printing a locale paragraph about a supporter (called the tabletop)
	(this is the alternate initial appearance on supporters rule):
	repeat with item running through not handled things on the tabletop:
		if item provides the property initial appearance:
			if the item is not a person:
				if the item is not undescribed:
					if the initial appearance of the item is not empty: [<-- note difference in condition]
						now the item is mentioned;
						let saved self be current self;
						force self to item; 
						say initial appearance of the item;
						force self to saved self;
						say paragraph break;
	continue the activity.

That condition makes use of the following definition from Standard Rules:

Definition: a text is empty rather than non-empty if I6 routine
	"TEXT_TY_Empty" says so (it contains no characters).

and that routine is implemented in I6, and is much shorter and more direct in its approach than comparing to an empty string (i.e. initial appearance of <X> is not ""):

[ TEXT_TY_Empty txt;
	if (txt==0) rtrue;
	if (txt-->0 & BLK_BVBITMAP_LONGBLOCKMASK == 0) { 	! <-- evaluates true
		if (txt-->1 == EMPTY_TEXT_PACKED) rtrue;		! <-- evaluates false
		rfalse;											! <-- returns false without requiring expansion of "text" object
	}
	if (TEXT_TY_CharacterLength(txt) == 0) rtrue;
	rfalse;
];

Forcing a difference in the value of self for the initial appearance on supporters rule is not required, simply changing the condition to use the empty definition for texts is enough:

For printing a locale paragraph about a supporter (called the tabletop)
	(this is the alternate initial appearance on supporters rule):
	repeat with item running through not handled things on the tabletop which
		provide the property initial appearance:
		if the item is not a person and the initial appearance of the item is not empty [<-- change here]
			and the item is not undescribed:
			now the item is mentioned;
			say initial appearance of the item;
			say paragraph break;
	continue the activity.

The alternate initial appearance on supporters rule is not listed in any rulebook.

The alternate initial appearance on supporters rule substitutes for the initial appearance on supporters rule.
2 Likes

I think I ran into this problem a few weeks ago, I couldn’t work out where the RTPs were coming from, but they went away when I replaced the initial appearance with a writing a paragraph about rule so I didn’t dig any further. It took me a good 4 hours to get that far and I had lost all interest in understanding it by then!

It was an initial appearance on a kind, and the RTP happened when the thing of the kind was on a table, so it sounds like the same problem.

As far as what can be done about the issue:

First, changing all Standard Rules instances of is not "" to is not empty seems desirable on the grounds of saving work if nothing else. That would solve this particular issue but leave the potential problem lurking, so it doesn’t seem sufficient as a fix.

Second, it appears that when multiple pile objects are generated, the compiler creates individual R_TX_S_nnn() routines for each instance in a one-to-one relationship. Since these routines are where the problematic reference to self occurs, the compiler could theoretically replace self with the hardcoded object name for a particular pile, making the resulting text indifferent to the value of self. Note that the compiler also seems to create an R_TX_S_nnn() routine for the class object for the pile kind, which could not be correctly handled in this manner (at least, I don’t think so), but which also should never be invoked so incorrect handling may not matter. (It’s not clear to me why all I6 instances of pile don’t have initial properties pointing to the same text object which in turn points to a single common routine, which would save some space and seems like it should work if self is set correctly.)

Third, this issue seems like it could occur during any text comparison, not just comparison to the empty string. It could even conceivably occur while comparing text properties of two objects A and B which both reference their own properties when determining the text to print. In such a case, there is no way to set self to a single value that would be correct for the entire invocation of BlkValueCompare(). It may be desirable to modify the low-level routine PrintI6Text(), for which the 6M62 version (with comment added) is:

[ PrintI6Text x;
	if (x ofclass String) print (string) x;
	if (x ofclass Routine) return (x)();	! <-- HERE
	if (x == EMPTY_TEXT_PACKED) rfalse;
	rtrue;
];

such that it modifies the value of self prior to running the routine that it is given (which is the one from the text object’s -->1 value). To do this, the chain of calls would have to pass the object ID to use for self from higher up, because the routine invoked is an R_TX_S_nnn() routine divorced from any object at the I6 level.

2 Likes

I’ve created I7-2362.

2 Likes