RTP "*** Value handling failed: impossible comparison ***" when trying to create list of texts involved in relation

I’m trying to do something with a relation of things to texts, which seems to be OK as a concept according to WWI 13.13 Relations involving values.

Problem code
"Bad List?"

Place is a room.

A thingy is a kind of thing.

Thingy1 is a thingy.

Word linking relates a thingy to various texts. The verb to wordlink means the word linking relation.

Thingy1 wordlinks "good" and "bad".

Requesting words is an action applying to nothing. Understand "words" as requesting words. The requesting words action has a list of texts called words found.

Report requesting words:
    say "STARTING.";
    repeat with P running through thingy:
	    say "POPULATING.";
	    let L be the list of texts to which P relates by the word linking relation; [causes RTP]
	    say "DISPLAYING.";
	    showme the number of entries in L;
	    do nothing.

Test me with "relations / words".

The attempt to create a list of the texts related to a thing causes an RTP, however:

Bad List?
An Interactive Fiction
Release 1 / Serial number 211227 / Inform 7 build 6M62 (I6/v6.33 lib 6/12N) SD

Place

>test me
(Testing.)

>[1] relations
word linking:
  Thingy1 >=> bad
  Thingy1 >=> good

>[2] words
STARTING.
POPULATING.
*** Value handling failed: impossible comparison ***

*** Run-time problem P49: Memory allocation proved impossible.


[ The game has finished ]

I’ve seen a few posts with “value handling” related error messages, but most are old and none of them seem to mention “impossible comparison”.

Am I misunderstanding the use of the phrase?

1 Like

If you change word linking to be various thingies to various texts, it works. And if you go backwards, it works:

Link wording relates various texts to a thingy. The verb to linkword means the link wording relation.
"good" linkwords thingy1. "bad" linkwords thingy1.

Instead of jumping:
repeat with P running through thingies begin;
    let L be the list of texts that relate to P by the link wording relation;
    say "[P] [L]";
end repeat;

…but I don’t know why your original doesn’t work.

I suspected it may be something to do with the way that texts are considered to be too numerous to iterate through, effectively infinite like numbers, which is why Inform won’t let you say ‘repeat with T running through texts’, but substituting numbers for texts in otistdog’s original example code works fine…

Well, WWI 13.13 says about the phrase in question:

This phrase produces a list of all Y such that the given value V relates to Y by the given relation. Example: suppose partnership relates various texts to various texts.

Regarding the first sentence: My understanding was that the whole point of this phrase (along with its siblings in that section) is for use in cases where there aren’t finite sets of instances of the kind (i.e. arithmetic values and texts). I didn’t think to test against numbers, but thank you for suggesting that, drpeterbatesuk. It doesn’t work for the situation I’m trying to represent, but it does show that, in principle, this should be OK.

Regarding the second sentence: The example uses a various-to-various relation, but the first paragraph of the section says:

We can create relations in groups, one to various relations, various to one relations, one to one relations, and various to various relations for any combination of kinds.

so it doesn’t seem like the phrases should be limited by the type of relation (and the compiler does accept it as written). Still, it doesn’t seem like there is any particular problem using a various-to-various relation for my purposes, so that’s a good workaround. Thank you, Zed.

Looking at the generated I6 code, it seems that maybe something about the setup of the relation is going wrong. Most built-in relations get a line like:

Array Rel_Record_69 -->
    0 (1293) RELATION_TY MAX_POSITIVE_NUMBER NULL NULL
    "lock-fitting relation" ...

but the equivalent array for this relation gets only:

Array Rel_Record_71 -->
    1;

And, more to the point, there does not appear to be a routine Rel_Handler_71() that seems to be necessary for the relations machinery to decide how to perform the test requested by the phrase.

Am I correct in thinknig that’s an issue at the I7 compiler level? If so, the workaround is the best option for now, so I’ll take Zed’s answer as a solution.

Having banged my head against it some today, I’m feeling ready to generalize that 6M62 doesn’t like one-to-various relations that have texts on either side.

Lab is a room.

xyz relates one text to various numbers.
The verb to abc means the xyz relation.

lt is always {"a","b","c","d","e","f","g","h","i","j","k"}.

when play begins:
repeat with i running through lt begin;
  say "i: [i].";
  now i abcs a random number from 1 to 100;
end repeat;

produces:

i: a.
i: b.
i: c.
i: d.
i: e.
i: f.
Glulxe fatal error: Memory access out of range (302E333C)

But it works with one text to one number or various texts to various numbers.

1 Like

Interesting. The output when compiled for Z-machine is slightly different but fails in the same place:

i: a.
i: b.
i: c.
i: d.
i: e.
i: f.
*** Value handling failed: impossible hashing ***

*** Run-time problem P49: Memory allocation proved impossible.


[ The game has finished ]
1 Like

I’ve come back around to looking at this one again (on 6M62). Interestingly, the relation works if the number of mapped elements is smaller (up to 5 values worked OK; note: Z-machine output shown):

>RELATIONS
xyz:
  b >=> 31
  c >=> 86
  e >=> 21
  d >=> 92
  a >=> 11

And also with the full original list if a constant value is used instead of a random number:

when play begins:
[force report of r71;]
repeat with i running through lt begin;
  say "i: [i].";
  now i abcs 7;
end repeat;

>RELATIONS
xyz:
  k >=> 7	! one mapping only, because a number can have only one text

The Array declaration for Rel_Record_71 is not actually malformed. There is some stuff involved here that I haven’t come across any documentation for, specifically the idea of a “dynamic relation” that is initialized on startup.

As can be seen right after the array declaration in Zed’s example code, there is a CreateDynamicRelations() routine. This is executed on startup, and when executed it overwrites the value of 1, which is apparently a placeholder, with a new short block value pointing to the heap:

Array Rel_Record_71 -->
	1;
[ CreateDynamicRelations 
	i ! loop counter
	rel ! new relation
	;
	BlkValueCreate(KD8_relation_of_texts_to_numbe, Rel_Record_71);	! see below
	RELATION_TY_Name(Rel_Record_71, "xyz");
	RELATION_TY_OToVAdjective(Rel_Record_71, true);
	InitialiseRelation_71();
];

...

[ BlkValueCreate strong_kind short_block  kovs;

	kovs = KOVSupportFunction(strong_kind, "impossible allocation");
	short_block = kovs(CREATE_KOVS, strong_kind, short_block);	! causes RELATION_TY_Create(KD8_relation_of_texts_to_numbe, 0, Rel_Record_71)

	#ifdef BLKVALUE_TRACE; print "Created: ", (BlkValueDebug) short_block, "^"; #endif;

	! The new value is represented in I6 as the pointer to its short block:
	return short_block;
];

...

[ RELATION_TY_Create kov from sb rel i skov handler;	! kov = KD8..., from = 0, sb = Rel_Record_71
	rel = FlexAllocate((RRV_DATA_BASE + 3*RRP_MIN_SIZE)*WORDSIZE,
		RELATION_TY, BLK_FLAG_WORD+BLK_FLAG_MULTIPLE);
	if ((from == 0) && (kov ~= 0)) from = DefaultValueFinder(kov);
	if (from) {
		for (i=0: i<RRV_DATA_BASE: i++) BlkValueWrite(rel, i, BlkValueRead(from, i), true);
		if (BlkValueRead(from, RRV_HANDLER) == EmptyRelationHandler) {
			handler = ChooseRelationHandler(BlkValueRead(rel, RRV_KIND, true));
			BlkValueWrite(rel, RRV_NAME, "anonymous relation", true);
			BlkValueWrite(rel, RRV_PERMISSIONS,
				RELS_TEST+RELS_ASSERT_TRUE+RELS_ASSERT_FALSE+RELS_SHOW, true);
			BlkValueWrite(rel, RRV_HANDLER, handler, true);
			BlkValueWrite(rel, RRV_STORAGE, RRP_MIN_SIZE-1, true);
			BlkValueWrite(rel, RRV_DESCRIPTION, "an anonymous relation", true);
			BlkValueWrite(rel, RRV_USED, 0, true);
			BlkValueWrite(rel, RRV_FILLED, 0, true);
		}
	} else {	! this block executed
		handler = ChooseRelationHandler(kov);
		BlkValueWrite(rel, RRV_NAME, "anonymous relation", true);
		BlkValueWrite(rel, RRV_PERMISSIONS,
			RELS_TEST+RELS_ASSERT_TRUE+RELS_ASSERT_FALSE+RELS_SHOW, true);
		BlkValueWrite(rel, RRV_STORAGE, RRP_MIN_SIZE-1, true);
		BlkValueWrite(rel, RRV_KIND, kov, true);
		BlkValueWrite(rel, RRV_HANDLER, handler, true);
		BlkValueWrite(rel, RRV_DESCRIPTION, "an anonymous relation", true);
		BlkValueWrite(rel, RRV_USED, 0, true);
		BlkValueWrite(rel, RRV_FILLED, 0, true);
	}

	return BlkValueCreateSB1(sb, rel);	!	sb = Rel_Record_71, rel = (new relation storage)
];

...

[ BlkValueCreateSB1 short_block val;
	if (short_block == 0)
		short_block = FlexAllocate(WORDSIZE, 0, BLK_FLAG_WORD) + BLK_DATA_OFFSET;
	short_block-->0 = val;	! <--- overwrites Rel_Record_71 with pointer to newly-created relation storage
	return short_block;
];

So whatever is going wrong here may just be something in the support code for this kind of relation. Given that it is reliably crashing after a certain number of new records are added, it may be something to do with flex resizing.

This was fixed in 10.1. (10.1 fixed a lot of things.)
I7-2083; git commit of fix

OK, great!

It looks like the causes for our two examples were different, but the following will backport both changes into 6M62:

RelationKind.i6t: Hash Core Relation Handler
Include (-

! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
! RelationKind.i6t: Hash Core Relation Handler (MODIFIED)
! ==== ==== ==== ==== ==== ==== ==== ==== ==== ====

! MODIFIED
[ HashCoreRelationHandler rel task kx ky X Y mult  sym rev at tmp fl;
	if (task == RELS_SET_VALENCY) {
		return RELATION_TY_SetValency(rel, X);
	} else if (task == RELS_DESTROY) {
		! clear
		kx = KOVIsBlockValue(kx); ky = KOVIsBlockValue(ky);
		if (~~(kx || ky)) return;
		at = BlkValueRead(rel, RRV_STORAGE);
		while (at >= 0) {
			fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);
			if (fl & RRF_USED) {
				if (kx) BlkValueFree(BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1));
				if (ky || ~~(fl & RRF_SINGLE))
					BlkValueFree(BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2));
			}
			at--;
		}
		return;
	} else if (task == RELS_COPY) {
		X = KOVIsBlockValue(kx); Y = KOVIsBlockValue(ky);
		if (~~(X || Y)) return;
		at = BlkValueRead(rel, RRV_STORAGE);
		while (at >= 0) {
			fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);
			if (fl & RRF_USED) {
				if (X) {
					tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1);
					tmp = BlkValueCopy(BlkValueCreate(kx), tmp);
					BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 1, tmp);
				}
				if (Y || ~~(fl & RRF_SINGLE)) {
					tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2);
					tmp = BlkValueCopy(BlkValueCreate(BlkValueWeakKind(tmp)), tmp);
					BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, tmp);
				}
			}
			at--;
		}
		return;
	} else if (task == RELS_SHOW) {
		print (string) BlkValueRead(rel, RRV_DESCRIPTION), ":^";
		! Z-machine doesn't have the room to let us pass sym/rev as parameters
		switch (RELATION_TY_GetValency(rel)) {
			RRVAL_SYM_V_TO_V:
				sym = 1;
				tmp = KOVComparisonFunction(kx);
				if (~~tmp) tmp = UnsignedCompare;
			RRVAL_O_TO_V:
				rev = 1;
		}
		for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
			fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);
			if (fl & RRF_USED) {
				X = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1);
				Y = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2);
				if (fl & RRF_SINGLE) {
					if (sym && tmp(X, Y) > 0) continue;
					print "  ";
					if (rev) PrintKindValuePair(ky, Y);
					else PrintKindValuePair(kx, X);
					if (sym) print " <=> "; else print " >=> ";
					if (rev) PrintKindValuePair(kx, X);
					else PrintKindValuePair(ky, Y);
					print "^";
				} else {
					for (mult=1: mult<=LIST_OF_TY_GetLength(Y): mult++) {
						fl = LIST_OF_TY_GetItem(Y, mult);
						if (sym && tmp(X, fl) > 0) continue;
						print "  ";
						if (rev) PrintKindValuePair(ky, fl);
						else PrintKindValuePair(kx, X);
						if (sym) print " <=> "; else print " >=> ";
						if (rev) PrintKindValuePair(kx, X);
						else PrintKindValuePair(ky, fl);
						print "^";
					}
				}
			}
		}
		return;
	} else if (task == RELS_EMPTY) {
		if (BlkValueRead(rel, RRV_USED) == 0) rtrue;
		if (X == 1) {
			HashCoreRelationHandler(rel, RELS_DESTROY);
			for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
				tmp = RRV_DATA_BASE + 3*at;
				BlkValueWrite(rel, tmp, 0);
				BlkValueWrite(rel, tmp + 1, 0);
				BlkValueWrite(rel, tmp + 2, 0);
			}
			BlkValueWrite(rel, RRV_USED, 0);
			BlkValueWrite(rel, RRV_FILLED, 0);
			rtrue;
		}
		rfalse;
	} else if (task == RELS_LOOKUP_ANY) {
		if (Y == RLANY_GET_Y or RLANY_CAN_GET_Y) {
			at = HashCoreLookUp(rel, kx, X);
			if (at >= 0) {
				if (Y == RLANY_CAN_GET_Y) rtrue;
				tmp = RRV_DATA_BASE + 3*at;
				fl = BlkValueRead(rel, tmp);
				tmp = BlkValueRead(rel, tmp + 2);
				if (fl & RRF_SINGLE) return tmp;
				return LIST_OF_TY_GetItem(tmp, 1);
			}
		} else {
			for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
				tmp = RRV_DATA_BASE + 3*at;
				fl = BlkValueRead(rel, tmp);
				if (fl & RRF_USED) {
					sym = BlkValueRead(rel, tmp + 2);
					if (fl & RRF_SINGLE) {
						if (KOVIsBlockValue(ky)) {
							if (BlkValueCompare(X, sym) ~= 0) continue;
						} else {
							if (X ~= sym) continue;
						}
					} else {
						if (LIST_OF_TY_FindItem(sym, X) == 0) continue;
					}
					if (Y == RLANY_CAN_GET_X) rtrue;
					return BlkValueRead(rel, tmp + 1);
				}
			}
		}
		if (Y == RLANY_GET_X or RLANY_GET_Y)
			print "*** Lookup failed: value not found ***^";
		rfalse;
	} else if (task == RELS_LOOKUP_ALL_X) {
		if (BlkValueWeakKind(Y) ~= LIST_OF_TY) rfalse;
		LIST_OF_TY_SetLength(Y, 0);
		for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
			tmp = RRV_DATA_BASE + 3*at;
			fl = BlkValueRead(rel, tmp);
			if (fl & RRF_USED) {
				sym = BlkValueRead(rel, tmp + 2);
				if (fl & RRF_SINGLE) {
					if (KOVIsBlockValue(kx)) {	! MODIFIED per https://github.com/ganelson/inform/commit/7407443ae76d5f7690c047797b1dcbfc1e96e8b8#diff-0e165205db1b430fae941a6a30ccb212632ae4cbdbf056fefd4d5d788f3142b1
						if (BlkValueCompare(X, sym) ~= 0) continue;
					} else {
						if (X ~= sym) continue;
					}
				} else {
					if (LIST_OF_TY_FindItem(sym, X) == 0) continue;
				}
				LIST_OF_TY_InsertItem(Y, BlkValueRead(rel, tmp + 1));
			}
		}
		return Y;
	} else if (task == RELS_LOOKUP_ALL_Y) {
		if (BlkValueWeakKind(Y) ~= LIST_OF_TY) rfalse;
		LIST_OF_TY_SetLength(Y, 0);
		at = HashCoreLookUp(rel, kx, X);
		if (at >= 0) {
			tmp = RRV_DATA_BASE + 3*at;
			fl = BlkValueRead(rel, tmp);
			tmp = BlkValueRead(rel, tmp + 2);
			if (fl & RRF_SINGLE)
				LIST_OF_TY_InsertItem(Y, tmp);
			else
				LIST_OF_TY_AppendList(Y, tmp);
		}
		return Y;
	} else if (task == RELS_LIST) {
		if (BlkValueWeakKind(X) ~= LIST_OF_TY) rfalse;
		LIST_OF_TY_SetLength(X, 0);
		switch (Y) {
			RLIST_ALL_X:
				for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
					tmp = RRV_DATA_BASE + 3*at;
					fl = BlkValueRead(rel, tmp);
					if (fl & RRF_USED)
						LIST_OF_TY_InsertItem(X, BlkValueRead(rel, tmp + 1));
				}
				return X;
			RLIST_ALL_Y:
				for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
					tmp = RRV_DATA_BASE + 3*at;
					fl = BlkValueRead(rel, tmp);
					if (fl & RRF_USED) {
						tmp = BlkValueRead(rel, tmp + 2);
						if (fl & RRF_SINGLE)
							LIST_OF_TY_InsertItem(X, tmp, false, 0, true);
						else
							LIST_OF_TY_AppendList(X, tmp, false, 0, true);
					}
				}
				return X;
			RLIST_ALL_PAIRS:
				if (RELATION_TY_GetValency(rel) == RRVAL_O_TO_V) rev = 1;
				! LIST_OF_TY_InsertItem will make a deep copy of the item,
				! so we can reuse a single combination value here
				Y = BlkValueCreate(COMBINATION_TY, tmp);
				for ( at = BlkValueRead(rel, RRV_STORAGE): at >= 0: at-- ) {
					tmp = RRV_DATA_BASE + 3*at;
					fl = BlkValueRead(rel, tmp);
					if (fl & RRF_USED) {
						BlkValueWrite(Y, COMBINATION_ITEM_BASE + rev, BlkValueRead(rel, tmp + 1));
						tmp = BlkValueRead(rel, tmp + 2);
						if (fl & RRF_SINGLE) {
							BlkValueWrite(Y, COMBINATION_ITEM_BASE + 1 - rev, tmp);
							LIST_OF_TY_InsertItem(X, Y);
						} else {
							for ( mult = LIST_OF_TY_GetLength(tmp): mult > 0: mult-- ) {
								BlkValueWrite(Y, COMBINATION_ITEM_BASE + 1 - rev,
									LIST_OF_TY_GetItem(tmp, mult));
								LIST_OF_TY_InsertItem(X, Y);
							}
						}
					}
				}
				BlkValueWrite(Y, COMBINATION_ITEM_BASE, 0);
				BlkValueWrite(Y, COMBINATION_ITEM_BASE + 1, 0);
				BlkValueFree(Y);
				return X;
		}
		rfalse;
	}
	at = HashCoreLookUp(rel, kx, X);
	switch(task) {
		RELS_TEST:
			if (at < 0) rfalse;
			fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);
			tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2);
			if (fl & RRF_SINGLE) {
				if (KOVIsBlockValue(ky)) {
					if (BlkValueCompare(tmp, Y) == 0) rtrue;
				} else {
					if (tmp == Y) rtrue;
				}
				rfalse;
			} else {
				return LIST_OF_TY_FindItem(tmp, Y);
			}
		RELS_ASSERT_TRUE:
			if (at < 0) {
				! no entry exists for this key, just add one
				at = ~at;
				BlkValueWrite(rel, RRV_USED, BlkValueRead(rel, RRV_USED) + 1);
				if (BlkValueRead(rel, RRV_DATA_BASE + 3*at) == 0)
					BlkValueWrite(rel, RRV_FILLED, BlkValueRead(rel, RRV_FILLED) + 1);
				BlkValueWrite(rel, RRV_DATA_BASE + 3*at, RRF_USED+RRF_SINGLE);
				if (KOVIsBlockValue(kx)) { X = BlkValueCopy(BlkValueCreate(kx), X); }
				if (KOVIsBlockValue(ky)) { Y = BlkValueCopy(BlkValueCreate(ky), Y); }
				BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 1, X);
				BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, Y);
				HashCoreCheckResize(rel);
				break;
			}
			! an entry exists: could be a list or a single value
			fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);		! flags
			tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2);	! value or list
			if (fl & RRF_SINGLE) {
				! if Y is the same as the stored key, we have nothing to do
				if (KOVIsBlockValue(ky)) {
					if (BlkValueCompare(tmp, Y) == 0) rtrue;
				} else {
					if (tmp == Y) rtrue;
				}
				! it's different: either replace it or expand into a list,
				! depending on the value of mult
				if (mult) {
					fl = BlkValueCreate(LIST_OF_TY);	! new list
					BlkValueWrite(fl, LIST_ITEM_KOV_F, ky);
					LIST_OF_TY_SetLength(fl, 2);
					BlkValueWrite(fl, LIST_ITEM_BASE, tmp);	! do not copy
					LIST_OF_TY_PutItem(fl, 2, Y);		! copy if needed
					BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, fl);
					BlkValueWrite(rel, RRV_DATA_BASE + 3*at, RRF_USED);
				} else {
					if (KOVIsBlockValue(ky)) {
						BlkValueFree(tmp);
						Y = BlkValueCopy(BlkValueCreate(ky), Y);
					}
					BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, Y);
				}
			} else {
				! if Y is present already, do nothing. otherwise add it.
				LIST_OF_TY_InsertItem(tmp, Y, 0, 0, 1);
			}
			rtrue;
		RELS_ASSERT_FALSE:
			if (at < 0) rtrue;
			! an entry exists: could be a list or a single value
			fl = BlkValueRead(rel, RRV_DATA_BASE + 3*at);		! flags
			tmp = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2);	! value or list
			if (fl & RRF_SINGLE) {
				! if the stored key isn't Y, we have nothing to do
				if (KOVIsBlockValue(ky)) {
					if (BlkValueCompare(tmp, Y) ~= 0) rtrue;
				} else {
					if (tmp ~= Y) rtrue;
				}
				! delete the entry
				if (KOVIsBlockValue(ky))
					BlkValueFree(BlkValueRead(rel, RRV_DATA_BASE + 3*at + 2));
				.DeleteEntryIgnoringY;
				BlkValueWrite(rel, RRV_USED, BlkValueRead(rel, RRV_USED) - 1);
				if (KOVIsBlockValue(kx))
					BlkValueFree(BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1));
				BlkValueWrite(rel, RRV_DATA_BASE + 3*at, RRF_DELETED);
				BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 1, 0);
				BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, 0);
			} else {
				! remove Y from the list if present
				LIST_OF_TY_RemoveValue(tmp, Y, 1);
				! if the list is now empty, delete the whole entry
				if (LIST_OF_TY_GetLength(tmp) == 0) {
					BlkValueFree(tmp);
					jump DeleteEntryIgnoringY;
				}
			}
			rtrue;
	}
	rtrue;
];

[ HashCoreLookUp rel kx X  hashv i free mask perturb flags;
!print "[HCLU rel=", rel, " kx=", kx, " X=", X, ": ";
	! calculate a hash value for the key
	hashv = GetHashValue(kx, x);
	! look in the first expected slot
	mask = BlkValueRead(rel, RRV_STORAGE);
	i = hashv & mask;
!print "hv=", hashv, ", trying ", i;
	flags = BlkValueRead(rel, RRV_DATA_BASE + 3*i);
	if (flags == 0) {
!print " - not found]^";
		return ~i;
	}
	if (HashCoreEntryMatches(rel, i, kx, X)) {
!print " - found]^";
		return i;
	}
	! not here, keep looking in sequence
	free = -1;
	if (flags & RRF_DELETED) free = i;
	perturb = hashv;
	hashv = i;
	for (::) {
		hashv = hashv*5 + perturb + 1;
		i = hashv & mask;
!print ", ", i;
		flags = BlkValueRead(rel, RRV_DATA_BASE + 3*i);
		if (flags == 0) {
!print " - not found]^";
			if (free >= 0) return ~free;
			return ~i;
		}
		if (HashCoreEntryMatches(rel, i, kx, X)) {
!print " - found]^";
			return i;
		}
		if ((free < 0) && (flags & RRF_DELETED)) free = i;
		#ifdef TARGET_ZCODE;
		@log_shift perturb (-RRP_PERTURB_SHIFT) -> perturb;
		#ifnot;
		@ushiftr perturb RRP_PERTURB_SHIFT perturb;
		#endif;
	}
];

! MODIFIED
[ HashCoreCheckResize rel  filled ext newext temp i at kov kx F X Y;
	filled = BlkValueRead(rel, RRV_FILLED);
	ext = BlkValueRead(rel, RRV_STORAGE) + 1;
	if (filled >= (ext - filled) * RRP_CROWDED_IS) {
		! copy entries to temporary space
		temp = FlexAllocate(ext * (3*WORDSIZE), TEXT_TY, BLK_FLAG_WORD+BLK_FLAG_MULTIPLE);
		for (i=0: i<ext*3: i++)
			BlkValueWrite(temp, i, BlkValueRead(rel, RRV_DATA_BASE+i), true);
		! resize and clear our data
		if (ext >= RRP_LARGE_IS) newext = ext * RRP_RESIZE_LARGE;
		else newext = ext * RRP_RESIZE_SMALL;
		BlkValueSetLBCapacity(rel, RRV_DATA_BASE + newext*3);
		BlkValueWrite(rel, RRV_STORAGE, newext - 1);
		BlkValueWrite(rel, RRV_FILLED, BlkValueRead(rel, RRV_USED));
		for (i=0: i<newext*3: i++)
			BlkValueWrite(rel, RRV_DATA_BASE+i, 0);
		! copy entries back from temporary space
		kov = BlkValueRead(rel, RRV_KIND);
		kx = KindBaseTerm(kov, 1);	! MODIFIED per https://github.com/ganelson/inform/commit/9bffe2a889837879e8be317fe076b75ced746450
		for (i=0: i<ext: i++) {
			F = BlkValueRead(temp, 3*i, true);
			if (F == 0 || (F & RRF_DELETED)) continue;
			X = BlkValueRead(temp, 3*i + 1, true);
			Y = BlkValueRead(temp, 3*i + 2, true);
			at = HashCoreLookUp(rel, kx, X);
			if (at >= 0) { print "*** Duplicate entry while resizing ***^"; rfalse; }
			at = ~at;
			BlkValueWrite(rel, RRV_DATA_BASE + 3*at, F);
			BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 1, X);
			BlkValueWrite(rel, RRV_DATA_BASE + 3*at + 2, Y);
		}
		! done with temporary space
		FlexFree(temp);
	}
];

[ HashCoreEntryMatches rel at kx X  cx cy;
	cx = BlkValueRead(rel, RRV_DATA_BASE + 3*at + 1);
	if (KOVIsBlockValue(kx)) {
		if (BlkValueCompare(cx, X) ~= 0) rfalse;
	} else {
		if (cx ~= X) rfalse;
	}
	rtrue;
];

-) instead of "Hash Core Relation Handler" in "RelationKind.i6t".