@polycode-projects/the-mechanical-code-talker 3.3.0 → 4.0.1
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 +176 -8
- 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 +52 -0
- package/corpus/worlds/index.json.gz +0 -0
- package/corpus/worlds/manifest.json +53 -13
- 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-garden.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-garden.jsonl +34 -1
- 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/p2p/webrtc-transport.mjs +146 -0
- package/src/domain/game-config.mjs +26 -5
- package/src/domain/grammar/ace.mjs +40 -4
- package/src/domain/grammar/lexicon-core.json +2 -1
- package/src/domain/grammar/lexicon.mjs +31 -0
- package/src/domain/memory/trust.mjs +6 -4
- package/src/domain/p2p/facts.mjs +81 -0
- package/src/domain/p2p/peer-id.mjs +32 -0
- package/src/domain/p2p/provenance-relabel.mjs +26 -0
- package/src/domain/p2p/sync-filter.mjs +31 -0
- package/src/domain/p2p/wire.mjs +123 -0
- package/src/domain/reference-pack.mjs +31 -7
- package/src/domain/spider-fly-world.mjs +1 -1
- package/src/domain/sprite-map.mjs +10 -2
- package/src/domain/sprite-templates.mjs +8 -6
- package/src/services/adventure-editor.mjs +10 -2
- package/src/services/adventure-viz.mjs +309 -73
- package/src/services/adventure.mjs +1034 -330
- package/src/services/chat-page-viz.mjs +1097 -16
- package/src/services/chat.mjs +99 -18
- package/src/services/code-explorer-viz.mjs +52 -14
- package/src/services/extract-facts.mjs +4 -7
- package/src/services/ingest-viz.mjs +54 -26
- package/src/services/ledger-viz.mjs +105 -56
- package/src/services/memory-panel-viz.mjs +24 -0
- package/src/services/mud-editor.mjs +313 -0
- package/src/services/mud-turn.mjs +264 -87
- package/src/services/mud-viz.mjs +2606 -384
- package/src/services/p2p-room.mjs +559 -0
- package/src/services/plan-viz.mjs +119 -61
- package/src/services/research-viz.mjs +151 -57
- package/src/services/spider-fly-turn.mjs +1 -1
- package/src/services/spider-fly-viz.mjs +67 -43
- package/src/services/sprite-catalog-viz.mjs +175 -51
- package/src/services/viz-theme.mjs +15 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +109 -109
- package/src/surfaces/web/mud-browser-entry.mjs +226 -18
- package/src/surfaces/web/p2p-browser-entry.mjs +39 -0
|
@@ -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": "
|
|
3
|
+
"version": "4.0.1",
|
|
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",
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// One WebRTC DataChannel between two browsers, and nothing else. The caller
|
|
2
|
+
// carries the offer and answer SDP strings between the two machines however it
|
|
3
|
+
// likes — a link, a paste, an already-open channel to a third peer — and this
|
|
4
|
+
// module never learns what travels over the channel once it is open: it takes
|
|
5
|
+
// plain JS values in and hands plain JS values out.
|
|
6
|
+
//
|
|
7
|
+
// `connectionState` is a live GETTER PROPERTY, not a method: read
|
|
8
|
+
// `transport.connectionState`, never `transport.connectionState()`. It reports
|
|
9
|
+
// RTCPeerConnection's own state ("new" | "connecting" | "connected" | "failed"
|
|
10
|
+
// | "closed"), so a caller can poll it without registering a handler.
|
|
11
|
+
//
|
|
12
|
+
// `iceServers` defaults to [] and this design keeps it empty: no STUN, no
|
|
13
|
+
// TURN, no third party in the loop at all. Peers that cannot already reach
|
|
14
|
+
// each other directly (same machine, same LAN, or a NAT that allows it) never
|
|
15
|
+
// finish the handshake, which is the stated boundary of staying serverless.
|
|
16
|
+
//
|
|
17
|
+
// Both `createOffer` and `createAnswerFor` resolve only once ICE gathering has
|
|
18
|
+
// completed, so the SDP string they return already carries every candidate.
|
|
19
|
+
// Nothing here trickles, because a pasted blob is a one-shot message rather
|
|
20
|
+
// than a live connection back to the other side.
|
|
21
|
+
//
|
|
22
|
+
// Late handler registration still fires: `onOpen` on an already-open channel
|
|
23
|
+
// and `onClose` on an already-closed one call back immediately, so a caller
|
|
24
|
+
// that registers after the transition never silently misses it. Each fires at
|
|
25
|
+
// most once.
|
|
26
|
+
|
|
27
|
+
const CHANNEL_LABEL = "tmct";
|
|
28
|
+
|
|
29
|
+
export function createTransport({ iceServers = [] } = {}) {
|
|
30
|
+
const PeerConnection = globalThis.RTCPeerConnection;
|
|
31
|
+
if (typeof PeerConnection !== "function") {
|
|
32
|
+
throw new Error("no RTCPeerConnection here: this transport runs in a browser, not in bare node");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const connection = new PeerConnection({ iceServers });
|
|
36
|
+
const messageHandlers = [];
|
|
37
|
+
const openHandlers = [];
|
|
38
|
+
const closeHandlers = [];
|
|
39
|
+
|
|
40
|
+
let channel = null;
|
|
41
|
+
let openAnnounced = false;
|
|
42
|
+
let closeAnnounced = false;
|
|
43
|
+
let tornDown = false;
|
|
44
|
+
|
|
45
|
+
function announceOpen() {
|
|
46
|
+
if (openAnnounced) return;
|
|
47
|
+
openAnnounced = true;
|
|
48
|
+
for (const handler of openHandlers) handler();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function announceClose() {
|
|
52
|
+
if (closeAnnounced) return;
|
|
53
|
+
closeAnnounced = true;
|
|
54
|
+
for (const handler of closeHandlers) handler();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function receive(event) {
|
|
58
|
+
let value;
|
|
59
|
+
try {
|
|
60
|
+
value = JSON.parse(event.data);
|
|
61
|
+
} catch {
|
|
62
|
+
// A peer sending something that isn't JSON must not be able to throw
|
|
63
|
+
// inside our receive path, so the frame is dropped rather than raised.
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
for (const handler of messageHandlers) handler(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function attachChannel(dataChannel) {
|
|
70
|
+
channel = dataChannel;
|
|
71
|
+
dataChannel.addEventListener("open", announceOpen);
|
|
72
|
+
dataChannel.addEventListener("close", announceClose);
|
|
73
|
+
dataChannel.addEventListener("message", receive);
|
|
74
|
+
if (dataChannel.readyState === "open") announceOpen();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
connection.addEventListener("datachannel", (event) => attachChannel(event.channel));
|
|
78
|
+
connection.addEventListener("connectionstatechange", () => {
|
|
79
|
+
const state = connection.connectionState;
|
|
80
|
+
if (state === "failed" || state === "closed") announceClose();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
async function whenIceGatheringCompletes() {
|
|
84
|
+
if (connection.iceGatheringState === "complete") return;
|
|
85
|
+
await new Promise((resolve) => {
|
|
86
|
+
const settle = () => {
|
|
87
|
+
if (connection.iceGatheringState !== "complete") return;
|
|
88
|
+
connection.removeEventListener("icegatheringstatechange", settle);
|
|
89
|
+
resolve();
|
|
90
|
+
};
|
|
91
|
+
connection.addEventListener("icegatheringstatechange", settle);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
async createOffer() {
|
|
97
|
+
attachChannel(connection.createDataChannel(CHANNEL_LABEL));
|
|
98
|
+
await connection.setLocalDescription(await connection.createOffer());
|
|
99
|
+
await whenIceGatheringCompletes();
|
|
100
|
+
return connection.localDescription.sdp;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
async createAnswerFor(offerSdp) {
|
|
104
|
+
await connection.setRemoteDescription({ type: "offer", sdp: offerSdp });
|
|
105
|
+
await connection.setLocalDescription(await connection.createAnswer());
|
|
106
|
+
await whenIceGatheringCompletes();
|
|
107
|
+
return connection.localDescription.sdp;
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
async completeWithAnswer(answerSdp) {
|
|
111
|
+
await connection.setRemoteDescription({ type: "answer", sdp: answerSdp });
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
send(data) {
|
|
115
|
+
const state = channel ? channel.readyState : "missing";
|
|
116
|
+
if (state !== "open") throw new Error(`cannot send over a data channel that is ${state}`);
|
|
117
|
+
channel.send(JSON.stringify(data));
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
onMessage(handler) {
|
|
121
|
+
messageHandlers.push(handler);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
onOpen(handler) {
|
|
125
|
+
openHandlers.push(handler);
|
|
126
|
+
if (openAnnounced) handler();
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
onClose(handler) {
|
|
130
|
+
closeHandlers.push(handler);
|
|
131
|
+
if (closeAnnounced) handler();
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
close() {
|
|
135
|
+
if (tornDown) return;
|
|
136
|
+
tornDown = true;
|
|
137
|
+
channel?.close();
|
|
138
|
+
connection.close();
|
|
139
|
+
announceClose();
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
get connectionState() {
|
|
143
|
+
return connection.connectionState;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
@@ -27,25 +27,32 @@ export const DEFAULT_GAME_CONFIG = Object.freeze({
|
|
|
27
27
|
minHatchlingMass: 3,
|
|
28
28
|
webDurationTurns: 10,
|
|
29
29
|
}),
|
|
30
|
+
// The drains are what make a mud character mortal: every scripted turn
|
|
31
|
+
// charges one, and a character whose mass reaches zero starves and takes no
|
|
32
|
+
// more turns. They are sized against the demo page's own default run (400
|
|
33
|
+
// shared turns, so roughly 200 each for two animals) — an animal that never
|
|
34
|
+
// eats dies about two thirds of the way through, and one that forages does
|
|
35
|
+
// not. A drain that emptied a starting mass in twenty turns would end every
|
|
36
|
+
// run before it had shown anything.
|
|
30
37
|
mud: Object.freeze({
|
|
31
38
|
moleInitialMass: 8,
|
|
32
|
-
moleMassDecrementPerTurn: 0.
|
|
39
|
+
moleMassDecrementPerTurn: 0.06,
|
|
33
40
|
moleSpeed: 1,
|
|
34
41
|
moleDigReach: 1,
|
|
35
42
|
voleInitialMass: 6,
|
|
36
|
-
voleMassDecrementPerTurn: 0.
|
|
43
|
+
voleMassDecrementPerTurn: 0.05,
|
|
37
44
|
voleSpeed: 1,
|
|
38
45
|
voleDigReach: 1,
|
|
39
46
|
badgerInitialMass: 20,
|
|
40
|
-
badgerMassDecrementPerTurn: 0.
|
|
47
|
+
badgerMassDecrementPerTurn: 0.08,
|
|
41
48
|
badgerSpeed: 2,
|
|
42
49
|
badgerDigReach: 2,
|
|
43
50
|
groundhogInitialMass: 12,
|
|
44
|
-
groundhogMassDecrementPerTurn: 0.
|
|
51
|
+
groundhogMassDecrementPerTurn: 0.06,
|
|
45
52
|
groundhogSpeed: 2,
|
|
46
53
|
groundhogDigReach: 2,
|
|
47
54
|
meerkatInitialMass: 9,
|
|
48
|
-
meerkatMassDecrementPerTurn: 0.
|
|
55
|
+
meerkatMassDecrementPerTurn: 0.05,
|
|
49
56
|
meerkatSpeed: 1,
|
|
50
57
|
meerkatDigReach: 1,
|
|
51
58
|
}),
|
|
@@ -122,6 +129,20 @@ function mergeSection(defaults, raw, keyMap) {
|
|
|
122
129
|
return out;
|
|
123
130
|
}
|
|
124
131
|
|
|
132
|
+
/** The species a mud character id names — "mole-1" -> "mole" — which is how
|
|
133
|
+
* every per-species knob above is keyed. Pure. */
|
|
134
|
+
export function mudSpeciesOf(characterId) {
|
|
135
|
+
return String(characterId).replace(/-\d+$/, "");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** What one turn costs `characterId` in mass, from the resolved `mud` section.
|
|
139
|
+
* Zero for a species the config carries no drain for: a knob nobody set is not
|
|
140
|
+
* a reason to invent a number and starve something with it. Pure. */
|
|
141
|
+
export function mudMassDrainPerTurn(mudConfig, characterId) {
|
|
142
|
+
const drain = mudConfig?.[`${mudSpeciesOf(characterId)}MassDecrementPerTurn`];
|
|
143
|
+
return Number.isFinite(drain) ? drain : 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
125
146
|
/**
|
|
126
147
|
* Fold a normalized tmct.toml's `games`/`planning` tables (the raw sparse
|
|
127
148
|
* pass-through src/adapters/toml-config.mjs's normalizeConfig produces —
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
import {
|
|
30
30
|
loadLexicon, lookupNoun, lookupVerb, lookupAdjective, lookupProperName,
|
|
31
|
-
predicateOf, numberOf, classify,
|
|
31
|
+
predicateOf, numberOf, classify, OF_CLASSIFIER_HEADS, OF_PARTITIVE_HEADS,
|
|
32
32
|
} from "./lexicon.mjs";
|
|
33
33
|
import { fuzzyMatchInSet } from "../interpret/fuzzy.mjs";
|
|
34
34
|
|
|
@@ -85,7 +85,15 @@ function local(lexicon, term) {
|
|
|
85
85
|
const stripDet = (tokens) =>
|
|
86
86
|
tokens.length > 1 && DET.has(tokens[0].toLowerCase()) ? tokens.slice(1) : tokens;
|
|
87
87
|
|
|
88
|
-
/**
|
|
88
|
+
/** A token a compound noun phrase may be built out of: a plain word (never a
|
|
89
|
+
* code-shaped ref, a number or a CURIE) that is not a DECLARED proper name —
|
|
90
|
+
* a name in a compound slot ("GitLab pipeline") is a structural miss, not a
|
|
91
|
+
* compound. Shared by both compound arms of resolveNP below. */
|
|
92
|
+
const compoundableWord = (lexicon, token) =>
|
|
93
|
+
/^[a-z][a-z'-]*$/i.test(token) && !lookupProperName(lexicon, token);
|
|
94
|
+
|
|
95
|
+
/** Resolve a noun phrase: PROPERNAME | code-ref | NOUN | ADJ NOUN | NOUN NOUN
|
|
96
|
+
* | NOUN of NOUN (the last two opt-in, see `allowCompound`).
|
|
89
97
|
* Returns { term, individual, extras, unknown } — `term` null on a miss with
|
|
90
98
|
* the undeclared tokens in `unknown` (empty `unknown` = structurally
|
|
91
99
|
* unparseable phrase → the caller returns a hard null). `extras` carries the
|
|
@@ -149,8 +157,7 @@ function resolveNP(lexicon, tokensIn, { allowCompound = false } = {}) {
|
|
|
149
157
|
// DECLARED proper name in either slot ("GitLab pipeline") keeps the
|
|
150
158
|
// structural miss below — a name in the wrong slot, not a compound.
|
|
151
159
|
if (allowCompound
|
|
152
|
-
&&
|
|
153
|
-
&& /^[a-z][a-z'-]*$/i.test(tokens[0]) && /^[a-z][a-z'-]*$/i.test(tokens[1])) {
|
|
160
|
+
&& compoundableWord(lexicon, tokens[0]) && compoundableWord(lexicon, tokens[1])) {
|
|
154
161
|
const n0 = lookupNoun(lexicon, tokens[0], { singularOnly: false });
|
|
155
162
|
const n1 = lookupNoun(lexicon, tokens[1], { singularOnly });
|
|
156
163
|
if (n0 && n1) {
|
|
@@ -162,6 +169,35 @@ function resolveNP(lexicon, tokensIn, { allowCompound = false } = {}) {
|
|
|
162
169
|
const unknown = tokens.filter((t) => !classify(t, lexicon));
|
|
163
170
|
return { term: null, individual: false, extras: [], unknown };
|
|
164
171
|
}
|
|
172
|
+
// "NOUN of NOUN" is ONE compound noun ("unit of work", "chain of command",
|
|
173
|
+
// "conflict of interest"), joined with its own "of" the same way two plain
|
|
174
|
+
// nouns are joined with a space, so the taught fact and the query side
|
|
175
|
+
// unify on one term. Opt-in through the same `allowCompound` gate and the
|
|
176
|
+
// same compoundableWord shape test as the two-noun arm above; the head noun
|
|
177
|
+
// leads, so an "a"/"an" agrees with IT.
|
|
178
|
+
//
|
|
179
|
+
// Which "of" this claims is decided by the head, against the two closed
|
|
180
|
+
// of-frame vocabularies in lexicon.mjs — the same ones the fact extractor
|
|
181
|
+
// reads. A CLASSIFIER head is a rewrite of the inner noun rather than a
|
|
182
|
+
// compound ("a kind of dog" is a dog), and a PARTITIVE head states quantity
|
|
183
|
+
// or composition rather than a class ("a piece of cake", "a lot of dogs");
|
|
184
|
+
// both decline here and land on the miss below. The relational reading is
|
|
185
|
+
// pattern 7's and is claimed before any of this: parseAce routes "the N of N
|
|
186
|
+
// is VALUE" to parseOfForm, so "the version of tmct is 0.2.0" never reaches
|
|
187
|
+
// a copula split at all. An inner determiner is likewise out of the shape —
|
|
188
|
+
// "a piece of the cake" is four tokens and no compound noun carries one.
|
|
189
|
+
if (tokens.length === 3 && allowCompound && tokens[1].toLowerCase() === "of"
|
|
190
|
+
&& compoundableWord(lexicon, tokens[0]) && compoundableWord(lexicon, tokens[2])) {
|
|
191
|
+
const head = tokens[0].toLowerCase();
|
|
192
|
+
const inner = tokens[2].toLowerCase();
|
|
193
|
+
if (!OF_CLASSIFIER_HEADS.has(head) && !OF_PARTITIVE_HEADS.has(head)) {
|
|
194
|
+
const headNoun = lookupNoun(lexicon, head, { singularOnly });
|
|
195
|
+
const innerNoun = lookupNoun(lexicon, inner, { singularOnly: false });
|
|
196
|
+
if (headNoun && innerNoun) {
|
|
197
|
+
return { term: `${ns}${head} of ${inner}`, individual: false, noun: headNoun, extras: [], unknown: [] };
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
165
201
|
// 0 or 3+ tokens: not a fragment NP. Name the undeclared words if any.
|
|
166
202
|
return { term: null, individual: false, extras: [], unknown: tokens.filter((t) => !classify(t, lexicon)) };
|
|
167
203
|
}
|
|
@@ -43,6 +43,24 @@ export const QUANTIFIERS = Object.freeze({
|
|
|
43
43
|
exactly: "owl:cardinality",
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
// The two closed head vocabularies that say what an "of" between two nouns is
|
|
47
|
+
// DOING, so every reader of the of-frame decides it the same way instead of
|
|
48
|
+
// each growing its own list. A CLASSIFIER head reads THROUGH to the inner noun
|
|
49
|
+
// — "a kind of dog" is a dog, "a type of mammal" is a mammal — so the phrase
|
|
50
|
+
// is a rewrite of the inner noun, never a term of its own. A PARTITIVE head
|
|
51
|
+
// states quantity or composition — "a piece of cake", "a lot of dogs", "a body
|
|
52
|
+
// of ice" — so the phrase names no class at all. Anything else ("unit of
|
|
53
|
+
// work", "chain of command") is a compound noun the of belongs to.
|
|
54
|
+
/** Of-frame heads that read through to the inner noun. */
|
|
55
|
+
export const OF_CLASSIFIER_HEADS = Object.freeze(new Set([
|
|
56
|
+
"type", "kind", "sort", "form", "class", "variety", "species", "breed", "genus",
|
|
57
|
+
]));
|
|
58
|
+
/** Of-frame heads that state quantity or composition, not a class. */
|
|
59
|
+
export const OF_PARTITIVE_HEADS = Object.freeze(new Set([
|
|
60
|
+
"body", "mass", "group", "collection", "set", "series", "number", "amount",
|
|
61
|
+
"piece", "part", "lot", "pair", "bunch", "pile",
|
|
62
|
+
]));
|
|
63
|
+
|
|
46
64
|
const NUMBER_WORDS = Object.freeze({
|
|
47
65
|
one: 1, two: 2, three: 3, four: 4, five: 5,
|
|
48
66
|
six: 6, seven: 7, eight: 8, nine: 9, ten: 10,
|
|
@@ -149,6 +167,19 @@ export function loadLexicon(extra, ns = DEFAULT_NS) {
|
|
|
149
167
|
return lex;
|
|
150
168
|
}
|
|
151
169
|
|
|
170
|
+
/** `lexicon` with `names` additionally declared as proper names. The noun,
|
|
171
|
+
* verb and adjective maps are SHARED with the base lexicon rather than
|
|
172
|
+
* re-ingested — a caller that re-declares on every turn (a live game world
|
|
173
|
+
* minting ids as it is played) would otherwise rebuild nine thousand core
|
|
174
|
+
* entries to add half a dozen. Proper names outrank every other category, so
|
|
175
|
+
* a name with no dictionary reading of its own ("groundhog-1", "carrot-2")
|
|
176
|
+
* resolves as itself instead of dying as an undeclared word. */
|
|
177
|
+
export function withProperNames(lexicon, names) {
|
|
178
|
+
const properNames = new Map(lexicon.properNames);
|
|
179
|
+
for (const name of names) properNames.set(String(name).toLowerCase(), String(name));
|
|
180
|
+
return { ...lexicon, properNames };
|
|
181
|
+
}
|
|
182
|
+
|
|
152
183
|
/** Noun lookup with plural folding; returns the entry ({lemma, property?}) or
|
|
153
184
|
* null. `opts.singularOnly` (an "a"/"an" determiner) prunes the irregular-
|
|
154
185
|
* plural fold in favor of a standalone-singular entry when both exist for
|
|
@@ -40,11 +40,13 @@ function parseChatTagRest(rest) {
|
|
|
40
40
|
* same tier the hand-written tier2 corpus already scores at; the :turnN
|
|
41
41
|
* segment a snapshot write carries records when, not who, and is not
|
|
42
42
|
* part of the Source identity)
|
|
43
|
-
* mud:<character>[:turnN]
|
|
43
|
+
* mud:<character>[:turnN][:gone] -> { kind:"corpus", name:"mud:<character>" }
|
|
44
44
|
* (one character's own testimony in a multi-character world — what it told
|
|
45
|
-
* someone, what it examined for itself
|
|
46
|
-
*
|
|
47
|
-
* apart from a world of the
|
|
45
|
+
* someone, what it examined for itself, and with `:gone` what it saw leave
|
|
46
|
+
* the world. Same tier as the world, but one Source per character; the
|
|
47
|
+
* `mud:` prefix in the name keeps that Source apart from a world of the
|
|
48
|
+
* same literal name. Both trailing segments say when and what, not who, so
|
|
49
|
+
* neither is part of the Source identity)
|
|
48
50
|
* ace:chat:<session>@<ts> -> { kind:"operator", createdAt:<ts>, sessionId:<session> }
|
|
49
51
|
* teach:chat:<session>@<ts> -> { kind:"teach", createdAt:<ts>, sessionId:<session> }
|
|
50
52
|
* web:<url> | url:<url> -> { kind:"web", url:<url> }
|