Notes › Life Essays › It Took Three Presses to Reach Grandma: 36 Kinship Rules and 11,110 Chains Measured

It Took Three Presses to Reach Grandma: 36 Kinship Rules and 11,110 Chains Measured

Treating the term rule table as the subject under test: the 100 two-step chains yield only 28 terms with 34 fallbacks, three-step chains 384 and four-step chains 4032; age order is dropped at two places, and 5 of the 6 preset buttons throw.

Put the subject under test on the table

It started with a single relationship chain. I pressed 爸爸 (father) and then 爸爸 again, and the screen answered 爷爷, my father's father. I expected one more press to change the answer; it took three presses in a row before 奶奶 finally appeared. The page has no text field at all, 0 input elements and 0 textarea elements, so the only thing you can feed it is its 22 buttons, and what comes out is decided entirely by one relationship definition table plus one if-chain. I handled it the way I handle a subject under test: the template itself is 15,121 bytes and 13,713 UTF-16 code units spread over 418 line breaks, and I cut the function bodies out verbatim and ran them on a DOM stub. The instrumented build and the real page pipeline were then compared chain by chain over the 100 two-step chains and the 1,000 three-step chains, and the mismatch count was 0. Every figure below rests on that check.

36 rule sites, and the decision order is just the line order

The lookup function lookupTerm occupies lines 261 through 391, which is 131 lines and 3,569 code units. Inside that block the keyword return appears 38 times, and 2 of them sit in a chain.some callback and are therefore not rules, so the remaining 36 returns are 36 term sites; the same block also holds 47 if statements, 2 else branches and 14 ternaries. The order is exactly the source order: the single-tier branch on line 272 fires first and the unconditional fallback on line 388 fires last. Order is what matters here, because once a chain is caught by an earlier site, a later site written far more carefully is never reached at all. I replaced the matching line with an empty statement and watched where the chain landed instead, and here are the 5 pairs I picked.

ChainLine hitResultAfter deleting that line
father,mother386长辈(女性) elder generation, female388 晚辈(女性) younger generation, female
father,older_brother,wife290姑姑 father's sister318 伯母 father's brother's wife
mother,older_brother,wife294姨妈 mother's elder sister319 舅妈 mother's brother's wife
father,father,son287伯伯/叔叔 father's brother388 晚辈(男性) younger generation, male
father,son,son388晚辈(男性) younger generation, maleno return is left at the end of the function

Full enumeration: how many chains fall back and how many stay ambiguous

Take the Cartesian product of the 10 buttons grouped by chain length. With multi-dimensional combinations the row count is a product rather than a sum, which the older note listing every combination already works through. Forward direction, male perspective. The 100 two-step chains produce only 28 distinct terms, and the 34 fallback, 15 ambiguous and 51 named chains are spread over 19 words; the 1,000 three-step chains produce 30 words, where the fallback count climbs to 384 and the ambiguous and named counts are 165 and 451; the 10,000 four-step chains produce 34 words, 4,032 of them fallback and 1,368 ambiguous. Steps 1 through 4 together come to 11,110 chains. Fallback here means the 6 words 兄弟 (brother), 姐妹 (sister), 长辈(男性) (elder generation, male), 长辈(女性) (elder generation, female), 晚辈(男性) (younger generation, male) and 晚辈(女性) (younger generation, female), while ambiguous means the 6 strings that carry a slash.

Cartesian product of the 10 buttons: fallback, ambiguous, named 2-step: 100 fallback 34 / ambiguous 15 / named 51 3-step: 1000 fallback 384 / ambiguous 165 / named 451 4-step: 10000 fallback 4032 / ambiguous 1368; 11110 chains total

Figure 1: the fallback line grows faster than the word list does

isOlder and reverseRel: age order is thrown away in two places

isOlder lives on lines 406 to 412. It scans backward from the tail of the chain: a hit on older_ returns true, a hit on younger_ returns false, and when the chain contains no sibling tier at all it takes line 411 and returns false. That single default decides which word the peer generation gets. Among the 100 two-step chains the 8 peer words collapse to 表弟 4 and 表妹 4, younger male cousin and younger female cousin, and all 8 of those rows come from the default false: only 0 valid chains actually carry younger_, and the 4 words for an older brother or an older sister score 0 as well. The 1,000 three-step chains are the first ones where the comparison has anything to say, with 5 rows resolving to 堂弟, 5 to 堂哥, 1 to 堂妹 and 1 to 堂姐, and all 48 rows that come out as a younger brother or a younger sister belong to the group whose chain really does contain younger_. The default group accounts for 0 of those 12 rows. There are 216 chains that hold no sibling tier at all, and every one of them runs through line 411.

Age order: a default false and a reversal that flattens tiers 2-step: 100 74 differ reversed; 34 restored by a second flip 3-step: 1000 914 differ reversed; 242 restored by a second flip 8 peer words left: 4 younger male + 4 younger female cousins

Figure 2: reverse the chain once and age order is gone

The other place is reverseRel, whose table body runs from line 239 to line 249. Under M all 4 sibling tiers, 哥哥 (older brother), 姐姐 (older sister), 弟弟 (younger brother) and 妹妹 (younger sister), map to younger_brother, and under F they all map to younger_sister, so age order is smoothed away during the reversal. Passing male changes nothing either: that value never equals the capital F, so the code always takes the F side and the two tables end up identical across all 10 tiers. The collapse is easy to see, because [father,older_brother] and [father,younger_brother] both reverse into [younger_sister,son]. Among the 23 words that the reversed three-step enumeration produces, 伯伯, 嫂子, 姐夫, 堂哥, 表哥, 堂姐 and 表姐 each appear 0 times.

5 of the 6 shortcut examples throw and the chain area prints undefined

The page is the kinship calculator. Its shortcut row is 6 buttons on one line, labelled 爷爷的儿子, 姑姑的儿子, 舅舅的女儿, 叔叔的妻子, 外婆 and 姑姑, which is 4 chains of 3 tiers and 2 chains of 2 tiers. Apart from the 外婆 entry, whose source text is 'mother,mother', the other 5 token strings carry brother or sister, and neither of those two is among the 10 keys defined in relDefs. Click one and calculate() throws TypeError: Cannot read properties of undefined (reading 'side') while reading def.side on line 105 of the script.

After the throw the two outputs are not in the same state. The result area is never rewritten: if you computed something earlier, the term simply stays at its previous value. I pressed until 姑姑 showed up, then fed it a chain carrying brother, and the screen still read 姑姑. The chain area, by contrast, had already been rewritten, because addRel pushes before it renders: relLabels finds nothing for brother, so the tail of innerHTML receives the literal undefined, that is 9 code units, and kcChain keeps the unknown token exactly as typed, measured as ["father","father","brother"]. Repeating that same click with 6 unfamiliar tokens threw the identical error 6/6. Hand-editing the same entry into the legal chain 'father,father,older_brother' produces 爷爷. How you take a chain apart and count the people over dinner is the subject of the scenario note the meal where nobody could name anyone, and this article is only about that table.

FAQ

Q: if a fallback word comes back, did I build the chain wrong?

Not necessarily. 34 of the 100 two-step chains, 384 of the 1,000 three-step chains and 4,032 of the 10,000 four-step chains run through the fallback, and a perfectly legal chain lands there just the same, because gen only takes 3 values and the rules look at only 7 fields of state.

Q: how do I reproduce these counts?

Cut relDefs, calculate, lookupTerm, singleTerm, isOlder and reverseRel out of the <script> block of the template exactly as written, run them on a DOM stub that implements only getElementById, textContent and innerHTML, and then enumerate the 10 tiers length by length. Against the real pipeline the instrumented build showed 0 disagreements over the 100 two-step and 1,000 three-step chains, which is why this table of 36 sites was trusted as a sound input.

📎 Related articles

How Many Problems Fit on One A4 Worksheet Page: Font Size, Margins and the Info Bar Rewrite the Pagination 2026-11-03 The Same 23 Bytes at Four Error-Correction Levels: How Far This Page's Own QR Encoder Goes 2026-10-30 Type 137 ms as the frame delay and every frame holds 140 ms: tick steps, 4096-byte padding, canvas from the first image 2026-10-30 How a Perler Pattern Picks Its Bead Color: One Dither Pass Rewrites 150 of 1024 Cells 2026-10-30 Landscape Loses 25% And Portrait 43.75%: A Cell-By-Cell Geometry Audit Of 7 Collage Templates 2026-10-28