@polycode-projects/the-mechanical-code-talker 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -9
- package/corpus/reference/index.json.gz +0 -0
- package/corpus/reference/manifest.json +8 -8
- package/corpus/reference/shards/ref-00.jsonl.gz +0 -0
- package/corpus/sprites/src/sprite-facts.jsonl +401 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +49 -9
- package/corpus/worlds/shards/greyvale-museum.jsonl.gz +0 -0
- package/corpus/worlds/shards/lantern-cottage.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-hollow.jsonl.gz +0 -0
- package/corpus/worlds/shards/mud-warren.jsonl.gz +0 -0
- package/corpus/worlds/shards/spider-fly.jsonl.gz +0 -0
- package/corpus/worlds/src/greyvale-museum.jsonl +136 -0
- package/corpus/worlds/src/lantern-cottage.jsonl +60 -0
- package/corpus/worlds/src/mud-hollow.jsonl +82 -0
- package/corpus/worlds/src/mud-warren.jsonl +124 -0
- package/corpus/worlds/src/spider-fly.jsonl +1 -1
- package/data/sprites/animal-icon.toml +11 -5
- package/data/sprites/book-icon.toml +9 -5
- package/data/sprites/cabinet-icon.toml +10 -5
- package/data/sprites/cellar-icon.toml +16 -6
- package/data/sprites/container-icon.toml +7 -3
- package/data/sprites/desk-icon.toml +8 -5
- package/data/sprites/dog-icon.toml +8 -5
- package/data/sprites/dog-with-colour-icon.toml +13 -12
- package/data/sprites/drawing-room-icon.toml +14 -5
- package/data/sprites/egg-icon.toml +6 -3
- package/data/sprites/fly-icon.toml +10 -5
- package/data/sprites/furniture-icon.toml +5 -3
- package/data/sprites/garden-icon.toml +10 -3
- package/data/sprites/key-icon.toml +3 -1
- package/data/sprites/kitchen-icon.toml +15 -6
- package/data/sprites/lamp-icon.toml +9 -4
- package/data/sprites/letter-icon.toml +7 -4
- package/data/sprites/library-icon.toml +14 -3
- package/data/sprites/pan-icon.toml +7 -3
- package/data/sprites/person-icon.toml +6 -2
- package/data/sprites/poodle-icon.toml +1 -1
- package/data/sprites/portable-icon.toml +6 -4
- package/data/sprites/portrait-icon.toml +7 -4
- package/data/sprites/room-icon.toml +11 -2
- package/data/sprites/spider-icon.toml +9 -3
- package/data/sprites/study-icon.toml +10 -2
- package/package.json +5 -4
- package/src/adapters/memory/core.mjs +20 -0
- package/src/domain/ask-vocab.mjs +71 -0
- package/src/domain/ask.mjs +168 -0
- package/src/domain/game-config.mjs +11 -0
- package/src/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +18 -0
- package/src/domain/mud-facts.mjs +15 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/router/drive.mjs +35 -9
- package/src/domain/router/registry.mjs +24 -4
- package/src/domain/router/resolver.mjs +102 -40
- package/src/domain/scene-compose.mjs +117 -0
- package/src/domain/spider-fly-world.mjs +37 -1
- package/src/domain/sprite-facts.mjs +0 -0
- package/src/domain/sprite-request.mjs +156 -0
- package/src/domain/sprite-templates.mjs +169 -20
- package/src/services/adventure-editor.mjs +8 -14
- package/src/services/adventure-viz.mjs +209 -157
- package/src/services/adventure.mjs +526 -391
- package/src/services/chat-page-viz.mjs +69 -25
- package/src/services/chat.mjs +200 -51
- package/src/services/code-explorer-viz.mjs +102 -62
- package/src/services/extract-facts.mjs +4 -7
- package/src/services/ingest-viz.mjs +68 -82
- package/src/services/ledger-viz.mjs +136 -67
- package/src/services/memory-panel-viz.mjs +62 -0
- package/src/services/mud-editor.mjs +10 -15
- package/src/services/mud-turn.mjs +6 -6
- package/src/services/mud-viz.mjs +1016 -208
- package/src/services/p2p-room.mjs +90 -23
- package/src/services/plan-pddl.mjs +3 -1
- package/src/services/plan-viz.mjs +123 -64
- package/src/services/research-viz.mjs +160 -108
- package/src/services/spider-fly-turn.mjs +15 -23
- package/src/services/spider-fly-viz.mjs +146 -161
- package/src/services/spider-fly.mjs +69 -11
- package/src/services/sprite-catalog-viz.mjs +414 -240
- package/src/services/viz-boot.mjs +71 -0
- package/src/services/viz-room-graph.mjs +203 -0
- package/src/services/viz-theme.mjs +90 -1
- package/src/services/viz-ticker.mjs +22 -0
- package/src/surfaces/web/adventure-browser-entry.mjs +49 -33
- package/src/surfaces/web/chat-browser-entry.mjs +30 -105
- package/src/surfaces/web/code-explorer-browser-entry.mjs +168 -24
- package/src/surfaces/web/ingest-browser-entry.mjs +3 -13
- package/src/surfaces/web/ledger-browser-entry.mjs +7 -47
- package/src/surfaces/web/memory-ask-browser.bundle.js +127 -124
- package/src/surfaces/web/memory-stats.mjs +11 -0
- package/src/surfaces/web/mud-browser-entry.mjs +71 -29
- package/src/surfaces/web/plan-browser-entry.mjs +22 -40
- package/src/surfaces/web/research-browser-entry.mjs +26 -41
- package/src/surfaces/web/spider-fly-browser-entry.mjs +45 -28
- package/src/surfaces/web/sprites-browser-entry.mjs +14 -27
- package/src/surfaces/web/turn-session.mjs +120 -0
- package/src/tools/definitions.mjs +30 -0
- package/src/tools/handlers/index.mjs +6 -3
- package/src/tools/handlers/kit.mjs +19 -2
- package/src/tools/handlers/tmct-ask.mjs +11 -6
- package/src/tools/handlers/tmct-ingest.mjs +5 -1
- package/src/tools/handlers/tmct-related.mjs +4 -4
- package/src/tools/handlers/tmct-sprite.mjs +147 -0
- package/src/tools/memory-fallthrough.mjs +9 -2
- package/src/tools/server.mjs +25 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# letter — Ashcombe Hall's own objective (hidden in the cabinet): an
|
|
2
|
-
# envelope with a folded flap (a
|
|
3
|
-
#
|
|
2
|
+
# envelope with a folded flap (a darker overlay, the same carved-seam trick
|
|
3
|
+
# as the lid on container.toml) and a wax seal. The seal is a fixed red —
|
|
4
|
+
# a wax seal's colour comes from the wax pressed into it, not the paper
|
|
5
|
+
# underneath, the same reasoning as the flame on lamp.toml.
|
|
4
6
|
classes = ["letter"]
|
|
5
7
|
svg = """
|
|
6
8
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
7
9
|
<rect x="3" y="6" width="18" height="13" rx="1.2" fill="currentColor"/>
|
|
8
|
-
<path d="M3 6 L21 6 L21 6.8 L12 13.8 L3 6.8 Z" fill="currentColor
|
|
9
|
-
<
|
|
10
|
+
<path d="M3 6 L21 6 L21 6.8 L12 13.8 L3 6.8 Z" fill="color-mix(in srgb, currentColor 78%, black)"/>
|
|
11
|
+
<path d="M4 7.6 L11 12.9" stroke="color-mix(in srgb, currentColor 25%, white)" stroke-width="0.5" opacity="0.6"/>
|
|
12
|
+
<circle cx="12" cy="12.4" r="1.15" fill="#8a2e2e"/>
|
|
10
13
|
</svg>
|
|
11
14
|
"""
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
# library — a room floor plan marked by an open book, so the room-kind icon
|
|
2
2
|
# reads as a library rather than a plain room: the washed square is room.toml's
|
|
3
3
|
# own shape, the two-page V is the same open-book cue the large tier uses.
|
|
4
|
+
# One page is shaded a touch darker than the other so the spine reads as a
|
|
5
|
+
# fold rather than a seam drawn on a flat card. The wash is a fixed
|
|
6
|
+
# white/black tint rather than currentColor-mixed (see room-icon.toml's own
|
|
7
|
+
# header for why).
|
|
4
8
|
classes = ["library"]
|
|
5
9
|
svg = """
|
|
6
10
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
7
|
-
<
|
|
11
|
+
<defs>
|
|
12
|
+
<linearGradient id="library-wash" x1="0" y1="0" x2="0.4" y2="1">
|
|
13
|
+
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.1"/>
|
|
14
|
+
<stop offset="100%" stop-color="#000000" stop-opacity="0.14"/>
|
|
15
|
+
</linearGradient>
|
|
16
|
+
</defs>
|
|
17
|
+
<rect x="3" y="3" width="18" height="18" rx="1" fill="url(#library-wash)"/>
|
|
8
18
|
<rect x="3" y="3" width="18" height="18" rx="1" fill="none" stroke="currentColor" stroke-width="1.6"/>
|
|
9
|
-
<path d="M12 9 C10.4 7.9 8.2 7.6 6.4 8.1 V15.6 C8.2 15.1 10.4 15.4 12 16.5
|
|
10
|
-
<path d="M12 9
|
|
19
|
+
<path d="M12 9 C10.4 7.9 8.2 7.6 6.4 8.1 V15.6 C8.2 15.1 10.4 15.4 12 16.5 Z" fill="currentColor"/>
|
|
20
|
+
<path d="M12 9 C13.6 7.9 15.8 7.6 17.6 8.1 V15.6 C15.8 15.1 13.6 15.4 12 16.5 Z" fill="color-mix(in srgb, currentColor 78%, black)"/>
|
|
21
|
+
<path d="M12 9 V16.5" stroke="color-mix(in srgb, currentColor 30%, white)" stroke-width="0.4"/>
|
|
11
22
|
</svg>
|
|
12
23
|
"""
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# pan — the kitchen's default portable: a frying pan seen from above with a
|
|
2
|
-
# long handle
|
|
3
|
-
#
|
|
2
|
+
# long handle. The cooking surface reads darker than the rim (a pan's own
|
|
3
|
+
# interior is seasoned/scorched, its rim stays brighter metal) and the
|
|
4
|
+
# handle carries a rivet, so a taken pan reads as its own thing rather than
|
|
5
|
+
# the generic portable box.
|
|
4
6
|
classes = ["pan"]
|
|
5
7
|
svg = """
|
|
6
8
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
7
9
|
<circle cx="10" cy="14" r="6" fill="currentColor"/>
|
|
8
|
-
<circle cx="10" cy="14" r="
|
|
10
|
+
<circle cx="10" cy="14" r="4.8" fill="color-mix(in srgb, currentColor 70%, black)"/>
|
|
11
|
+
<path d="M6.6 11 A4.8 4.8 0 0 1 13 10" fill="none" stroke="color-mix(in srgb, currentColor 20%, white)" stroke-width="0.6" stroke-linecap="round" opacity="0.6"/>
|
|
9
12
|
<rect x="15" y="13" width="7" height="2" rx="1" fill="currentColor"/>
|
|
13
|
+
<circle cx="16.4" cy="14" r="0.5" fill="color-mix(in srgb, currentColor 55%, black)"/>
|
|
10
14
|
</svg>
|
|
11
15
|
"""
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# person — the generic fallback for anyone with no NPC sprite of their own
|
|
2
|
-
# (see butler/housekeeper/cook/gardener for the named Ashcombe Hall staff)
|
|
2
|
+
# (see butler/housekeeper/cook/gardener for the named Ashcombe Hall staff):
|
|
3
|
+
# two shoulder notches break the torso silhouette where sleeves would seam,
|
|
4
|
+
# so it reads as a clothed figure rather than a bare blob-with-a-head, while
|
|
5
|
+
# staying deliberately unadorned next to the named staff's own props.
|
|
3
6
|
classes = ["person"]
|
|
4
7
|
svg = """
|
|
5
8
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
6
9
|
<circle cx="12" cy="6.6" r="3.3" fill="currentColor"/>
|
|
7
10
|
<path d="M6 21 C6 15.3 8.7 12.8 12 12.8 C15.3 12.8 18 15.3 18 21 Z" fill="currentColor"/>
|
|
8
|
-
<path d="
|
|
11
|
+
<path d="M7.6 14.4 L9.4 13" stroke="color-mix(in srgb, currentColor 55%, black)" stroke-width="0.7" opacity="0.6" stroke-linecap="round"/>
|
|
12
|
+
<path d="M16.4 14.4 L14.6 13" stroke="color-mix(in srgb, currentColor 55%, black)" stroke-width="0.7" opacity="0.6" stroke-linecap="round"/>
|
|
9
13
|
</svg>
|
|
10
14
|
"""
|
|
@@ -5,7 +5,7 @@ classes = ["poodle"]
|
|
|
5
5
|
svg = """
|
|
6
6
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
7
7
|
<ellipse cx="13" cy="15" rx="6.4" ry="4.6" fill="currentColor"/>
|
|
8
|
-
<ellipse cx="10.8" cy="13.2" rx="1.8" ry="1.3" fill="currentColor
|
|
8
|
+
<ellipse cx="10.8" cy="13.2" rx="1.8" ry="1.3" fill="color-mix(in srgb, currentColor 70%, black)"/>
|
|
9
9
|
<circle cx="7" cy="8.5" r="4" fill="currentColor"/>
|
|
10
10
|
<g fill="currentColor">
|
|
11
11
|
<circle cx="4" cy="6" r="1.6"/>
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# portable — the generic fallback for any carryable object with no sprite
|
|
2
|
-
# of its own: a drawstring parcel silhouette
|
|
3
|
-
#
|
|
4
|
-
# rather than a gift.
|
|
2
|
+
# of its own: a drawstring parcel silhouette with a small tag hanging from
|
|
3
|
+
# the neck, so it reads as "an unspecified small thing someone packed"
|
|
4
|
+
# rather than a gift or a sack of grain.
|
|
5
5
|
classes = ["portable"]
|
|
6
6
|
svg = """
|
|
7
7
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
8
8
|
<path d="M12 6 C7.5 6 5 9.4 5 13.4 C5 17.7 8.1 20 12 20 C15.9 20 19 17.7 19 13.4 C19 9.4 16.5 6 12 6 Z" fill="currentColor"/>
|
|
9
9
|
<path d="M9.4 6.4 C9.9 4.6 10.8 3.4 12 3.4 C13.2 3.4 14.1 4.6 14.6 6.4" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
|
10
|
-
<ellipse cx="9.8" cy="11.6" rx="1.6" ry="2.1" fill="currentColor
|
|
10
|
+
<ellipse cx="9.8" cy="11.6" rx="1.6" ry="2.1" fill="color-mix(in srgb, currentColor 25%, white)"/>
|
|
11
|
+
<path d="M14.4 6.6 L17.4 8.8 L15.6 10.4 L14 8 Z" fill="color-mix(in srgb, currentColor 65%, black)"/>
|
|
12
|
+
<circle cx="16.1" cy="8.5" r="0.35" fill="color-mix(in srgb, currentColor 20%, white)"/>
|
|
11
13
|
</svg>
|
|
12
14
|
"""
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# portrait — Ashcombe Hall's own drawing-room portrait (a container, hiding
|
|
2
2
|
# the key): a gilt arched frame around a lighter canvas, with a bust
|
|
3
|
-
# silhouette so it reads as a picture of someone, not an empty frame.
|
|
3
|
+
# silhouette so it reads as a picture of someone, not an empty frame. Canvas
|
|
4
|
+
# and figure use fixed cream/sepia tones rather than currentColor opacity —
|
|
5
|
+
# a painting's own palette doesn't belong to the page theme, and a flat
|
|
6
|
+
# opacity wash would shift between a light and a dark backdrop anyway.
|
|
4
7
|
classes = ["portrait"]
|
|
5
8
|
svg = """
|
|
6
9
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
7
10
|
<path d="M4 21 V7 C4 4 6.8 2 12 2 C17.2 2 20 4 20 7 V21 Z" fill="currentColor"/>
|
|
8
|
-
<path d="M6.3 19.4 V8.3 C6.3 6.2 8.4 4.8 12 4.8 C15.6 4.8 17.7 6.2 17.7 8.3 V19.4 Z" fill="
|
|
9
|
-
<circle cx="12" cy="10.4" r="2.3" fill="
|
|
10
|
-
<path d="M8 18.4 C8 15.2 9.6 13.4 12 13.4 C14.4 13.4 16 15.2 16 18.4 Z" fill="
|
|
11
|
+
<path d="M6.3 19.4 V8.3 C6.3 6.2 8.4 4.8 12 4.8 C15.6 4.8 17.7 6.2 17.7 8.3 V19.4 Z" fill="#f4ecd8"/>
|
|
12
|
+
<circle cx="12" cy="10.4" r="2.3" fill="#8a6a52"/>
|
|
13
|
+
<path d="M8 18.4 C8 15.2 9.6 13.4 12 13.4 C14.4 13.4 16 15.2 16 18.4 Z" fill="#8a6a52"/>
|
|
11
14
|
</svg>
|
|
12
15
|
"""
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
# room — a schematic floor plan: a washed interior (a solid tint, not a
|
|
2
2
|
# hollow box) plus a doorway arch, kept abstract since a room is a space
|
|
3
|
-
# to draw, not an object to render literally.
|
|
3
|
+
# to draw, not an object to render literally. The wash is a fixed
|
|
4
|
+
# white/black tint rather than currentColor-mixed: a tint built from
|
|
5
|
+
# currentColor reverses which side looks lighter between a light and a
|
|
6
|
+
# dark theme, a plain white/black one never does.
|
|
4
7
|
classes = ["room"]
|
|
5
8
|
svg = """
|
|
6
9
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
7
|
-
<
|
|
10
|
+
<defs>
|
|
11
|
+
<linearGradient id="room-wash" x1="0" y1="0" x2="0.4" y2="1">
|
|
12
|
+
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.1"/>
|
|
13
|
+
<stop offset="100%" stop-color="#000000" stop-opacity="0.14"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
</defs>
|
|
16
|
+
<rect x="3" y="3" width="18" height="18" rx="1" fill="url(#room-wash)"/>
|
|
8
17
|
<rect x="3" y="3" width="18" height="18" rx="1" fill="none" stroke="currentColor" stroke-width="1.6"/>
|
|
9
18
|
<path d="M9 21 L9 13 A3 3 0 0 1 15 13 L15 21" fill="none" stroke="currentColor" stroke-width="1.6"/>
|
|
10
19
|
</svg>
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# spider — the spider-and-fly board's own agent. A confident two-lobe body
|
|
2
|
-
# (a
|
|
3
|
-
#
|
|
2
|
+
# (a darker carved patch on the abdomen for shading), two small fixed-dark
|
|
3
|
+
# eye dots (the same restrained fixed-accent technique the large tier's
|
|
4
|
+
# badger/vole use for an identity marking, not a coat colour) and eight
|
|
5
|
+
# thin outward legs.
|
|
4
6
|
classes = ["spider"]
|
|
5
7
|
svg = """
|
|
6
8
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
7
9
|
<ellipse cx="12" cy="16" rx="5.2" ry="4.8" fill="currentColor"/>
|
|
8
|
-
<ellipse cx="10.3" cy="14.3" rx="1.8" ry="1.4" fill="currentColor
|
|
10
|
+
<ellipse cx="10.3" cy="14.3" rx="1.8" ry="1.4" fill="color-mix(in srgb, currentColor 70%, black)"/>
|
|
9
11
|
<circle cx="12" cy="9.6" r="3.2" fill="currentColor"/>
|
|
12
|
+
<g fill="#1c1c1c">
|
|
13
|
+
<circle cx="10.7" cy="8.7" r="0.5"/>
|
|
14
|
+
<circle cx="13.3" cy="8.7" r="0.5"/>
|
|
15
|
+
</g>
|
|
10
16
|
<g stroke="currentColor" stroke-width="1.1" fill="none" stroke-linecap="round">
|
|
11
17
|
<path d="M9.4 8 L2.2 4.4"/>
|
|
12
18
|
<path d="M9 10.2 L1.4 9.4"/>
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# study — a room floor plan marked by a writing desk against one wall, so the
|
|
2
2
|
# room-kind icon reads as the study you start in rather than a plain room.
|
|
3
|
+
# The wash is a fixed white/black tint rather than currentColor-mixed (see
|
|
4
|
+
# room-icon.toml's own header for why).
|
|
3
5
|
classes = ["study"]
|
|
4
6
|
svg = """
|
|
5
7
|
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
6
|
-
<
|
|
8
|
+
<defs>
|
|
9
|
+
<linearGradient id="study-wash" x1="0" y1="0" x2="0.4" y2="1">
|
|
10
|
+
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.1"/>
|
|
11
|
+
<stop offset="100%" stop-color="#000000" stop-opacity="0.14"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
</defs>
|
|
14
|
+
<rect x="3" y="3" width="18" height="18" rx="1" fill="url(#study-wash)"/>
|
|
7
15
|
<rect x="3" y="3" width="18" height="18" rx="1" fill="none" stroke="currentColor" stroke-width="1.6"/>
|
|
8
|
-
<rect x="7" y="11" width="10" height="2.4" fill="currentColor"
|
|
16
|
+
<rect x="7" y="11" width="10" height="2.4" fill="currentColor"/>
|
|
9
17
|
<path d="M8 13.4 V17 M16 13.4 V17" stroke="currentColor" stroke-width="1.2"/>
|
|
10
18
|
<rect x="9.6" y="7.6" width="4.8" height="3" fill="none" stroke="currentColor" stroke-width="1"/>
|
|
11
19
|
</svg>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; indexes a repo on request (tmct index) or reads any producer's graph.",
|
|
@@ -111,11 +111,12 @@
|
|
|
111
111
|
"chat:repo": "node --disable-warning=ExperimentalWarning bin/tmct.mjs chat --repo",
|
|
112
112
|
"chat:plain": "node --disable-warning=ExperimentalWarning bin/tmct.mjs chat --plain",
|
|
113
113
|
"chat:narrate": "node --disable-warning=ExperimentalWarning bin/tmct.mjs chat --narrate",
|
|
114
|
-
"init": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init",
|
|
115
|
-
"init:
|
|
114
|
+
"init": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus seon && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus aws && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus python && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus java",
|
|
115
|
+
"init:small": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init",
|
|
116
|
+
"init:sqlite": "npm run init:small",
|
|
116
117
|
"init:persona:human": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --with-persona human",
|
|
117
118
|
"init:persona:empty": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --with-persona empty",
|
|
118
|
-
"init:large": "
|
|
119
|
+
"init:large": "npm run init",
|
|
119
120
|
"init:xl": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --persona-size large && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus seon && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus aws && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus python && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus java && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus wordnet-xl",
|
|
120
121
|
"init:xxl": "node --disable-warning=ExperimentalWarning bin/tmct.mjs init --persona-size large && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus seon && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus conceptnet && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus aws && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus python && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus java && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus wordnet-full && node --disable-warning=ExperimentalWarning bin/tmct.mjs import --corpus namenet",
|
|
121
122
|
"memory": "node --disable-warning=ExperimentalWarning bin/tmct.mjs memory",
|
|
@@ -172,6 +172,26 @@ export function createInMemoryStore() {
|
|
|
172
172
|
return { backend: BACKEND_MEMORY, payload: emptyMemory() };
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/** Assign `seedPayload` onto `memoryDir`'s own payload — spread over the
|
|
176
|
+
* store's own (possibly empty) payload rather than replacing it outright,
|
|
177
|
+
* so a partial seed (individuals and objectProperties only) still carries
|
|
178
|
+
* the classes/prefixes scaffolding the write path recounts, and a later
|
|
179
|
+
* teach turn works regardless of what the seed carries. A no-op when
|
|
180
|
+
* `seedPayload` is null/undefined — a browser session with nothing to seed
|
|
181
|
+
* keeps its own fresh empty payload untouched. */
|
|
182
|
+
export function applySeedPayload(memoryDir, seedPayload) {
|
|
183
|
+
if (seedPayload) memoryDir.payload = { ...memoryDir.payload, ...seedPayload };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** A structurally independent copy of a memory payload — `structuredClone`
|
|
187
|
+
* where available, falling back to a JSON round-trip for an environment
|
|
188
|
+
* without it. Returns null for a null/undefined `payload`, so a caller can
|
|
189
|
+
* seed a fresh session with "no seed yet" rather than an empty object. */
|
|
190
|
+
export function cloneMemoryPayload(payload) {
|
|
191
|
+
if (!payload) return null;
|
|
192
|
+
try { return structuredClone(payload); } catch { return JSON.parse(JSON.stringify(payload)); }
|
|
193
|
+
}
|
|
194
|
+
|
|
175
195
|
// ---- Backend C — SQLite: a live node:sqlite connection, per-row
|
|
176
196
|
// INSERT/REPLACE/DELETE diffed against what's already stored (write cost
|
|
177
197
|
// proportional to what changed, not total store size). Reads are cached on
|
package/src/domain/ask-vocab.mjs
CHANGED
|
@@ -189,6 +189,77 @@ export const RELATIONS = {
|
|
|
189
189
|
},
|
|
190
190
|
};
|
|
191
191
|
|
|
192
|
+
// ---- world relations: the predicates a WORLD graph stores about its own
|
|
193
|
+
// individuals, as opposed to RELATIONS above, which is a code vocabulary —
|
|
194
|
+
// a module imports a module, a commit touches a file, and none of its verbs
|
|
195
|
+
// name where a thing is or how it feels. A game board, a burrow and a manor
|
|
196
|
+
// all record those, in the same `mgx:` predicates, so the words for them are
|
|
197
|
+
// curated once here.
|
|
198
|
+
//
|
|
199
|
+
// Each entry is keyed by its stored predicate and carries the LISTING NOUNS a
|
|
200
|
+
// question reaches it by ("the locations of…", "the moods of…") plus `reads`,
|
|
201
|
+
// the third-person phrase one subject's answer is written in. No `verbs` list
|
|
202
|
+
// and no VERB_TO_KIND entry: these answer a listing over a named set, not the
|
|
203
|
+
// "does X <verb> Y" shapes RELATIONS' verbs compile to, and folding them in
|
|
204
|
+
// would put "is in" into the code grammar's own verb table. ----
|
|
205
|
+
|
|
206
|
+
export const WORLD_RELATIONS = Object.freeze({
|
|
207
|
+
placement: Object.freeze({
|
|
208
|
+
predicate: "mgx:currently-in",
|
|
209
|
+
comment: "individual -> place: where the subject is right now.",
|
|
210
|
+
nouns: Object.freeze(["location", "locations", "position", "positions", "place", "places", "whereabouts", "room", "rooms"]),
|
|
211
|
+
reads: "is in",
|
|
212
|
+
}),
|
|
213
|
+
mood: Object.freeze({
|
|
214
|
+
predicate: "mgx:feels",
|
|
215
|
+
comment: "individual -> mood word: how the subject feels right now.",
|
|
216
|
+
nouns: Object.freeze(["mood", "moods", "feeling", "feelings", "emotion", "emotions"]),
|
|
217
|
+
reads: "feels",
|
|
218
|
+
}),
|
|
219
|
+
mass: Object.freeze({
|
|
220
|
+
predicate: "mgx:mass",
|
|
221
|
+
comment: "individual -> number: the subject's current mass.",
|
|
222
|
+
nouns: Object.freeze(["mass", "masses", "weight", "weights"]),
|
|
223
|
+
reads: "has mass",
|
|
224
|
+
}),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/** listing noun -> WORLD_RELATIONS key ("locations" -> "placement"). */
|
|
228
|
+
export const WORLD_NOUN_TO_RELATION = Object.freeze(Object.fromEntries(
|
|
229
|
+
Object.entries(WORLD_RELATIONS).flatMap(([token, { nouns }]) => nouns.map((n) => [n, token])),
|
|
230
|
+
));
|
|
231
|
+
|
|
232
|
+
/** Every stored world predicate, for a caller projecting fact rows into a
|
|
233
|
+
* graph `ask` can traverse. */
|
|
234
|
+
export const WORLD_PREDICATES = Object.freeze(
|
|
235
|
+
Object.values(WORLD_RELATIONS).map((r) => r.predicate),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
// The regular English 3rd-person-singular suffix rule (the same regular
|
|
239
|
+
// -s/-es/-ies shape src/domain/inflect.mjs's own `pluralOf` applies to a
|
|
240
|
+
// noun) — not imported from there, since inflect.mjs sits downstream of
|
|
241
|
+
// interpret/normalize.mjs, which imports THIS module: importing it back
|
|
242
|
+
// here would be a real circular dependency, not just a style choice.
|
|
243
|
+
function thirdPersonSingular(w) {
|
|
244
|
+
if (/(?:[sxz]|ch|sh)$/i.test(w)) return `${w}es`;
|
|
245
|
+
if (/[^aeiou]y$/i.test(w)) return `${w.slice(0, -1)}ies`;
|
|
246
|
+
return `${w}s`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The third-person-singular sentence phrase for a stored relation `kind`
|
|
250
|
+
* ("inherits" -> "inherits from", "cochange" -> "co-changes with"), derived
|
|
251
|
+
* from RELATIONS' own `bare` infinitive rather than a second hand-curated
|
|
252
|
+
* verb table. A symbol-grain kind ("callsSymbol", "touchesSymbol") reads its
|
|
253
|
+
* coarse sibling's phrase; an unrecognized kind falls back to a plain
|
|
254
|
+
* camelCase split. */
|
|
255
|
+
export function phraseForRelation(kind) {
|
|
256
|
+
const key = String(kind || "");
|
|
257
|
+
const entry = RELATIONS[key] || (key.endsWith("Symbol") && RELATIONS[key.slice(0, -6)]) || null;
|
|
258
|
+
if (!entry) return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase();
|
|
259
|
+
const [head, ...rest] = entry.bare.split(" ");
|
|
260
|
+
return [thirdPersonSingular(head), ...rest].join(" ");
|
|
261
|
+
}
|
|
262
|
+
|
|
192
263
|
/** The closed set of reverse `inherits` verb phrasings a strategy checks to
|
|
193
264
|
* decide whether to swap subject/object. The three "the"-definite forms are
|
|
194
265
|
* named here but not reachable through VERB_TO_KIND (see above). */
|
package/src/domain/ask.mjs
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
PASSIVE_PARTICIPLE_TO_KIND, GENERIC_AGENT_WORDS, REDUCED_RELATIVE_CLAUSES,
|
|
27
27
|
AGGREGATE_TRIGGERS, LIST_TRIGGERS, SUPERLATIVE_EXTREMES, EDGE_NOUN_TO_METRIC, METRIC_IMPLIES_ENTITY, ANAPHORA_TRIGGERS,
|
|
28
28
|
MEMBERSHIP_KINDS, CASCADE_NOISE, CASCADE_SYNONYMS, HELP_TRIGGERS,
|
|
29
|
+
WORLD_RELATIONS, WORLD_NOUN_TO_RELATION, WORLD_PREDICATES,
|
|
29
30
|
stripTrailingScopeFiller,
|
|
30
31
|
} from "./ask-vocab.mjs";
|
|
31
32
|
import { expandContractions, normalizeQuery, applyNegationFrames, applyPhrasingFrames, matchNegationSet, STOPWORDS, splitWords, wordsOf, escapeRegex } from "./interpret/normalize.mjs";
|
|
@@ -2135,6 +2136,7 @@ function evalComposite(graph, ast, opts = {}) {
|
|
|
2135
2136
|
return { compositeKind: "count", count: evalSet(graph, ast.base, opts).length, entityType: ast.entityType, matches: [] };
|
|
2136
2137
|
}
|
|
2137
2138
|
if (ast.node === "list") return { compositeKind: "list", matches: evalSet(graph, ast.base, opts), entityType: ast.entityType, scoped: ast.scoped };
|
|
2139
|
+
if (ast.node === "worldRelation") return evalWorldRelation(graph, ast);
|
|
2138
2140
|
if (ast.node === "superlative") return evalSuperlative(graph, ast);
|
|
2139
2141
|
if (ast.node === "temporal") return evalTemporal(graph, ast, opts);
|
|
2140
2142
|
if (ast.node === "recentCommits") return evalRecentCommits(graph);
|
|
@@ -2292,6 +2294,20 @@ function renderComposite(parsed, result, graph) {
|
|
|
2292
2294
|
: "";
|
|
2293
2295
|
return { content: `${compositeList(result.matches)}${hint}.`, miss: false, ambiguous: false, matches: result.matches };
|
|
2294
2296
|
}
|
|
2297
|
+
// One sentence per subject, in id order, in the world's own words — the
|
|
2298
|
+
// subject's id, the relation's `reads` phrase, and the stored object
|
|
2299
|
+
// verbatim. Nothing is derived: an empty set is the honest miss, never a
|
|
2300
|
+
// sentence about a subject the world has no row for.
|
|
2301
|
+
if (result.compositeKind === "worldRelation") {
|
|
2302
|
+
const asked = listJoin(result.askedClasses);
|
|
2303
|
+
const noun = WORLD_RELATIONS[result.relation].nouns[1];
|
|
2304
|
+
if (!result.pairs.length) {
|
|
2305
|
+
return { content: `no ${noun} on record for ${asked} in this graph.`, miss: true, ambiguous: false, matches: [] };
|
|
2306
|
+
}
|
|
2307
|
+
const reads = WORLD_RELATIONS[result.relation].reads;
|
|
2308
|
+
const sentences = result.pairs.map((p) => `${p.subject.label || p.subject.id} ${reads} ${p.object}`);
|
|
2309
|
+
return { content: `${sentences.join("; ")}.`, miss: false, ambiguous: false, matches: result.matches };
|
|
2310
|
+
}
|
|
2295
2311
|
// A non-empty inherited result is disclosed out loud ("X has no own <kind>
|
|
2296
2312
|
// — inherited from <ancestor>: …"), never silently presented as the owner's own.
|
|
2297
2313
|
if (result.compositeKind === "membership") {
|
|
@@ -4403,6 +4419,148 @@ function dynamicClassQuery(graph, query) {
|
|
|
4403
4419
|
return listM ? { node: "list", entityType, base, scoped: false } : { node: "count", entityType, base };
|
|
4404
4420
|
}
|
|
4405
4421
|
|
|
4422
|
+
// ---- world-relation listing: "list the locations of flies and spiders", the
|
|
4423
|
+
// plural, multi-class form of a question a world graph can already answer. A
|
|
4424
|
+
// listing over ONE world predicate (ask-vocab.mjs's WORLD_RELATIONS) filtered
|
|
4425
|
+
// to one or more subject CLASSES, resolved dynamically against whatever
|
|
4426
|
+
// classes actually have an individual in this graph — so a world's own
|
|
4427
|
+
// taxonomy needs no entry in the closed code-graph noun table, exactly as the
|
|
4428
|
+
// count/list fallback above resolves its class noun. Fires only once the
|
|
4429
|
+
// normal cascade has already produced an honest miss, and misses honestly
|
|
4430
|
+
// itself when any asked class has no individual here. ----
|
|
4431
|
+
|
|
4432
|
+
// A class list: "flies and spiders", "spiders, flies and eggs". Each part must
|
|
4433
|
+
// be a single bare noun — a phrase with anything else in it is a restrictor
|
|
4434
|
+
// this shape can't honour, so it declines rather than dropping it.
|
|
4435
|
+
function parseWorldClassList(graph, text) {
|
|
4436
|
+
const parts = String(text || "").split(/\s*,\s*|\s+and\s+/i).map((s) => s.trim()).filter(Boolean);
|
|
4437
|
+
if (!parts.length) return null;
|
|
4438
|
+
const asked = [];
|
|
4439
|
+
const classes = [];
|
|
4440
|
+
for (const part of parts) {
|
|
4441
|
+
const word = part.replace(/^(?:all\s+)?(?:the\s+)?/i, "").trim();
|
|
4442
|
+
if (!/^[a-z][a-z'-]*$/i.test(word)) return null;
|
|
4443
|
+
const cls = resolveDynamicClass(graph, word);
|
|
4444
|
+
if (!cls) return null;
|
|
4445
|
+
asked.push(word.toLowerCase());
|
|
4446
|
+
classes.push(cls);
|
|
4447
|
+
}
|
|
4448
|
+
return { asked: [...new Set(asked)], classes: [...new Set(classes)] };
|
|
4449
|
+
}
|
|
4450
|
+
|
|
4451
|
+
const WORLD_LISTING_RE = new RegExp(
|
|
4452
|
+
`^(?:${LIST_TRIGGERS_SORTED.map(escapeRegex).join("|")})\\s+(?:all\\s+)?(?:the\\s+)?([a-z][a-z'-]*)\\s+(?:of|for)\\s+(.+?)[?.!\\s]*$`,
|
|
4453
|
+
"i",
|
|
4454
|
+
);
|
|
4455
|
+
// "where are the flies and spiders" — the same listing said the natural way.
|
|
4456
|
+
// Singular too ("where is the spider"): the class list resolves either way, and
|
|
4457
|
+
// with only the code-graph `where` lane to fall back on the answer would be
|
|
4458
|
+
// "no recorded code location" for a subject whose cell this graph plainly
|
|
4459
|
+
// holds. A phrase that isn't a bare class noun ("where is auth.mjs defined")
|
|
4460
|
+
// fails parseWorldClassList and keeps the definition-site reading.
|
|
4461
|
+
const WORLD_WHERE_RE = /^where(?:'s|\s+is|\s+are)\s+(?:all\s+)?(?:the\s+)?(.+?)[?.!\s]*$/i;
|
|
4462
|
+
|
|
4463
|
+
/** Compile "<list trigger> the <world-relation noun> of <class> and <class>"
|
|
4464
|
+
* (or "where are the <class> and <class>") into a world-relation listing AST,
|
|
4465
|
+
* or null when this isn't that shape. */
|
|
4466
|
+
function worldRelationQuery(graph, query) {
|
|
4467
|
+
const q = String(query || "").trim();
|
|
4468
|
+
const listM = q.match(WORLD_LISTING_RE);
|
|
4469
|
+
const relation = listM ? WORLD_NOUN_TO_RELATION[listM[1].toLowerCase()] : "placement";
|
|
4470
|
+
if (!relation) return null;
|
|
4471
|
+
const subjectText = listM ? listM[2] : q.match(WORLD_WHERE_RE)?.[1];
|
|
4472
|
+
if (!subjectText) return null;
|
|
4473
|
+
const resolved = parseWorldClassList(graph, subjectText);
|
|
4474
|
+
if (!resolved) return null;
|
|
4475
|
+
return {
|
|
4476
|
+
node: "worldRelation",
|
|
4477
|
+
relation,
|
|
4478
|
+
predicate: WORLD_RELATIONS[relation].predicate,
|
|
4479
|
+
classes: resolved.classes,
|
|
4480
|
+
askedClasses: resolved.asked,
|
|
4481
|
+
};
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4484
|
+
const normalizePredicate = (p) => String(p || "").toLowerCase();
|
|
4485
|
+
|
|
4486
|
+
function evalWorldRelation(graph, ast) {
|
|
4487
|
+
const wanted = new Set(ast.classes);
|
|
4488
|
+
const latest = new Map();
|
|
4489
|
+
for (const group of graph?.relations || []) {
|
|
4490
|
+
if (normalizePredicate(group.prop) !== ast.predicate && normalizePredicate(group.predicate) !== ast.predicate) continue;
|
|
4491
|
+
// A world appends a fresh row per turn rather than rewriting one, so the
|
|
4492
|
+
// LAST edge for a subject is its current value — the same last-wins fold
|
|
4493
|
+
// every world's own state reader applies to its rows.
|
|
4494
|
+
for (const edge of group.edges || []) {
|
|
4495
|
+
const subject = graph.byId?.get(edge.subject);
|
|
4496
|
+
if (subject && wanted.has(subject.class)) latest.set(edge.subject, { subject, object: edge.object });
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
const pairs = [...latest.values()].sort((a, b) => String(a.subject.id).localeCompare(String(b.subject.id)));
|
|
4500
|
+
return {
|
|
4501
|
+
compositeKind: "worldRelation",
|
|
4502
|
+
relation: ast.relation,
|
|
4503
|
+
askedClasses: ast.askedClasses,
|
|
4504
|
+
pairs,
|
|
4505
|
+
matches: pairs.map((p) => p.subject),
|
|
4506
|
+
};
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
// A world writes each turn's facts against a stamped subject ("spider-1@turn3",
|
|
4510
|
+
// "player@step7") and folds them back onto the base id, newest stamp winning —
|
|
4511
|
+
// spider-fly.mjs's foldSpiderFlyState and domain.mjs's foldWorldState both do
|
|
4512
|
+
// exactly this. An unstamped row is the world's own starting value, stamp 0.
|
|
4513
|
+
const WORLD_SNAPSHOT_RE = /^(.+)@(?:turn|step)(\d+)$/;
|
|
4514
|
+
|
|
4515
|
+
function splitWorldSnapshot(term) {
|
|
4516
|
+
const m = WORLD_SNAPSHOT_RE.exec(String(term ?? ""));
|
|
4517
|
+
return m ? { base: m[1], stamp: Number(m[2]) } : { base: String(term ?? ""), stamp: 0 };
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4520
|
+
/** Project plain world fact rows (`{subject, predicate, object}`, the shape
|
|
4521
|
+
* memory/core.mjs's readFactRows returns) into the `{individuals,
|
|
4522
|
+
* objectProperties}` payload parseEntities consumes, so a page holding a live
|
|
4523
|
+
* world can hand `ask` a graph of it instead of an empty one. Only the
|
|
4524
|
+
* WORLD_PREDICATES rows are carried: a board's own geometry (every cell and
|
|
4525
|
+
* every exit) is thousands of rows no natural-language listing asks for.
|
|
4526
|
+
* Turn-stamped rows are folded onto their base subject, so the graph carries
|
|
4527
|
+
* each subject's CURRENT value and never a stale one from an earlier turn.
|
|
4528
|
+
*
|
|
4529
|
+
* A subject's class comes from its own `rdf:type` row when the world states
|
|
4530
|
+
* one; `classOf(id)` is the fallback for a world that names its individuals
|
|
4531
|
+
* by convention instead (spider-fly's "spider-1" is a spider because of its
|
|
4532
|
+
* id). Returning null from `classOf` drops the subject — that is the seam a
|
|
4533
|
+
* caller filters through, so "which subjects still count as live" stays a
|
|
4534
|
+
* rule of the world module rather than of this projection. Pure. */
|
|
4535
|
+
export function worldRelationGraphPayload(rows, { classOf = () => null } = {}) {
|
|
4536
|
+
const declaredClass = new Map();
|
|
4537
|
+
for (const row of rows || []) {
|
|
4538
|
+
if (row?.predicate === "rdf:type" && row.subject && row.object) {
|
|
4539
|
+
declaredClass.set(splitWorldSnapshot(row.subject).base, String(row.object));
|
|
4540
|
+
}
|
|
4541
|
+
}
|
|
4542
|
+
const wanted = new Set(WORLD_PREDICATES);
|
|
4543
|
+
const individuals = new Map();
|
|
4544
|
+
const newest = new Map();
|
|
4545
|
+
for (const row of rows || []) {
|
|
4546
|
+
if (!row?.subject || !wanted.has(row.predicate)) continue;
|
|
4547
|
+
const { base, stamp } = splitWorldSnapshot(row.subject);
|
|
4548
|
+
const cls = declaredClass.get(base) || classOf(base);
|
|
4549
|
+
if (!cls) continue;
|
|
4550
|
+
const key = `${row.predicate}${base}`;
|
|
4551
|
+
const prior = newest.get(key);
|
|
4552
|
+
if (prior && prior.stamp > stamp) continue;
|
|
4553
|
+
individuals.set(base, { id: base, label: base, class: cls });
|
|
4554
|
+
newest.set(key, { stamp, predicate: row.predicate, subject: base, object: splitWorldSnapshot(row.object).base });
|
|
4555
|
+
}
|
|
4556
|
+
const groups = new Map();
|
|
4557
|
+
for (const edge of newest.values()) {
|
|
4558
|
+
if (!groups.has(edge.predicate)) groups.set(edge.predicate, { prop: edge.predicate, predicate: edge.predicate, examples: [] });
|
|
4559
|
+
groups.get(edge.predicate).examples.push({ subject: edge.subject, object: edge.object });
|
|
4560
|
+
}
|
|
4561
|
+
return { individuals: [...individuals.values()], objectProperties: [...groups.values()] };
|
|
4562
|
+
}
|
|
4563
|
+
|
|
4406
4564
|
// Matches T5's own bare-object capture (grammar.mjs meta-whatis), reused below to
|
|
4407
4565
|
// extract the term for the article-insertion fallback rather than duplicating it.
|
|
4408
4566
|
const BARE_META_WHATIS_RE = /^what\s+(?:is|are)\s+(?:an?\s+)?(.+?)[?.!\s]*$/i;
|
|
@@ -4447,6 +4605,16 @@ export function ask(graph, query, { contextId = null, nlp = undefined, prev = nu
|
|
|
4447
4605
|
if (!dynRendered.miss) { parsed = dyn; result = dynResult; rendered = dynRendered; relaxed = null; }
|
|
4448
4606
|
}
|
|
4449
4607
|
}
|
|
4608
|
+
// World-relation listing ("list the locations of flies and spiders"), on the
|
|
4609
|
+
// same terms: only after an honest miss, and only when it answers for real.
|
|
4610
|
+
if (rendered.miss && !rendered.ambiguous) {
|
|
4611
|
+
const world = worldRelationQuery(graph, query);
|
|
4612
|
+
if (world) {
|
|
4613
|
+
const worldResult = traverse(graph, world, { contextId, prev });
|
|
4614
|
+
const worldRendered = render(world, worldResult, graph);
|
|
4615
|
+
if (!worldRendered.miss) { parsed = world; result = worldResult; rendered = worldRendered; relaxed = null; }
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4450
4618
|
// Bare "what is X" (no article) meta fallback, narrow to ask() and never
|
|
4451
4619
|
// touching the outer `parsed` even on a miss — chat.mjs's own gates key off
|
|
4452
4620
|
// `!envelope.parsed`, so populating it here would steal turns from lanes
|
|
@@ -143,6 +143,17 @@ export function mudMassDrainPerTurn(mudConfig, characterId) {
|
|
|
143
143
|
return Number.isFinite(drain) ? drain : 0;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/** The mass denominator a spider-and-fly class's HUD bar and sprite
|
|
147
|
+
* expression both scale against — "how full" reads the same on either. `cls`
|
|
148
|
+
* is "spider" or "fly"; any other class (an egg, or a class this world adds
|
|
149
|
+
* later) has no denominator and gets null, never a guessed number. `config`
|
|
150
|
+
* is the page's own `{ maxSpiderMass, maxFlyMass }` pair. Pure. */
|
|
151
|
+
export function massScaleFor(cls, config) {
|
|
152
|
+
if (cls === "spider") return config?.maxSpiderMass ?? null;
|
|
153
|
+
if (cls === "fly") return config?.maxFlyMass ?? null;
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
146
157
|
/**
|
|
147
158
|
* Fold a normalized tmct.toml's `games`/`planning` tables (the raw sparse
|
|
148
159
|
* pass-through src/adapters/toml-config.mjs's normalizeConfig produces —
|