@polycode-projects/the-mechanical-code-talker 5.0.2 → 5.0.4
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/corpus/worlds/manifest.json +9 -9
- package/corpus/worlds/shards/town-square-chapel.jsonl.gz +0 -0
- package/corpus/worlds/shards/town-square-market.jsonl.gz +0 -0
- package/corpus/worlds/shards/town-square.jsonl.gz +0 -0
- package/corpus/worlds/src/town-square-chapel.jsonl +1 -1
- package/corpus/worlds/src/town-square-market.jsonl +1 -1
- package/corpus/worlds/src/town-square.jsonl +1 -1
- package/data/mudiii-assets.json +35 -5
- package/package.json +1 -1
- package/src/adapters/memory/core.mjs +236 -18
- package/src/domain/ask-vocab.mjs +1 -1
- package/src/domain/ask.mjs +20 -10
- package/src/domain/game-config.mjs +10 -1
- package/src/domain/interpret/normalize.mjs +4 -0
- package/src/domain/memory/retraction.mjs +232 -0
- package/src/domain/p2p/sync-filter.mjs +9 -1
- package/src/domain/spider-fly-world.mjs +80 -35
- package/src/domain/syllogise.mjs +128 -75
- package/src/services/adventure-autoplay.mjs +2 -2
- package/src/services/adventure-viz.mjs +12 -7
- package/src/services/chat.mjs +42 -14
- package/src/services/mud-turn.mjs +1 -1
- package/src/services/mud-viz.mjs +11 -2
- package/src/services/mudiii-scene.mjs +199 -55
- package/src/services/mudiii-turn.mjs +82 -1
- package/src/services/mudiii-viz.mjs +17 -7
- package/src/services/p2p-room.mjs +130 -9
- package/src/services/predator-prey.mjs +524 -69
- package/src/services/spider-fly-turn.mjs +128 -56
- package/src/services/spider-fly-viz.mjs +65 -71
- package/src/services/world-teach.mjs +3 -3
- package/src/surfaces/web/adventure-browser-entry.mjs +12 -3
- package/src/surfaces/web/memory-ask-browser.bundle.js +118 -118
- package/src/surfaces/web/mud-browser-entry.mjs +10 -3
- package/src/surfaces/web/mudiii-browser-entry.mjs +3 -3
- package/src/surfaces/web/spider-fly-browser-entry.mjs +26 -22
- package/src/services/spider-fly.mjs +0 -943
package/src/domain/syllogise.mjs
CHANGED
|
@@ -1527,14 +1527,35 @@ function bestEnvironmentTrustOpts(provenance, environments, trustOfId) {
|
|
|
1527
1527
|
* without it removal is still correct, the survivor's environments just stay
|
|
1528
1528
|
* stale until the next syllogise pass.
|
|
1529
1529
|
*
|
|
1530
|
+
* `sourceTags` narrows the target removal to the INVOKING PARTY's own
|
|
1531
|
+
* record(s) — a chat `/retract` names every provenance tag its own session
|
|
1532
|
+
* could have asserted the triple under here (the free-form teach lane and
|
|
1533
|
+
* the ACE-parsed assert lane both belong to one session, under two
|
|
1534
|
+
* different tags), so two sources who independently asserted the same
|
|
1535
|
+
* triple never let one's retraction erase the other's. Omitted (or empty),
|
|
1536
|
+
* retraction stays group-wide (every source's record for the triple), which
|
|
1537
|
+
* is what mud EDIT mode wants and what every pre-existing caller of this
|
|
1538
|
+
* function already gets.
|
|
1539
|
+
*
|
|
1530
1540
|
* Returns { retracted, count, budget, depth, truncated, found } — `found` is
|
|
1531
|
-
* false when `subject ⊑ object` was never a stored fact.
|
|
1541
|
+
* false when `subject ⊑ object` was never a stored fact. With `sourceTags`
|
|
1542
|
+
* set, two more fields describe what the SCOPED removal actually did:
|
|
1543
|
+
* `ownRecord` (false when the invoking party never asserted the triple
|
|
1544
|
+
* itself, so nothing of theirs existed to retract) and `stillStands` (true
|
|
1545
|
+
* when another source's record keeps the triple asserted after this one's
|
|
1546
|
+
* record is gone — in which case nothing entailed from it is cascaded,
|
|
1547
|
+
* because its premise never actually stopped holding).
|
|
1532
1548
|
*/
|
|
1533
1549
|
export async function retractSubClassOf(repoDir, subject, object, {
|
|
1534
|
-
budget = 50, depth = 32, maxEnvironments = DEFAULT_MAX_ENVIRONMENTS, store,
|
|
1550
|
+
budget = 50, depth = 32, maxEnvironments = DEFAULT_MAX_ENVIRONMENTS, store, sourceTags = [],
|
|
1535
1551
|
} = {}) {
|
|
1536
1552
|
const { loadMemory, readFactRows, removeFacts } = requireStore(store, ["loadMemory", "readFactRows", "removeFacts"], "retractSubClassOf");
|
|
1537
1553
|
const appendFactsFn = typeof store?.appendFacts === "function" ? store.appendFacts : null;
|
|
1554
|
+
const factRecordIdForTag = typeof store?.factRecordIdForTag === "function" ? store.factRecordIdForTag : null;
|
|
1555
|
+
const scoped = (sourceTags || []).filter(Boolean);
|
|
1556
|
+
if (scoped.length && !factRecordIdForTag) {
|
|
1557
|
+
throw new TypeError("retractSubClassOf needs a store option carrying factRecordIdForTag (memory/core.mjs's) to scope a retraction to sourceTags");
|
|
1558
|
+
}
|
|
1538
1559
|
const s = normFactTerm(subject);
|
|
1539
1560
|
const o = normFactTerm(object);
|
|
1540
1561
|
const targetId = factIdForTriple(s, SUBCLASS_PREDICATE, o);
|
|
@@ -1543,92 +1564,121 @@ export async function retractSubClassOf(repoDir, subject, object, {
|
|
|
1543
1564
|
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
1544
1565
|
if (!byId.has(targetId)) return { retracted: [], count: 0, budget, depth, truncated: false, found: false };
|
|
1545
1566
|
|
|
1546
|
-
//
|
|
1547
|
-
//
|
|
1548
|
-
|
|
1549
|
-
//
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
for (const premiseId of env) {
|
|
1557
|
-
if (!citedBy.has(premiseId)) citedBy.set(premiseId, new Set());
|
|
1558
|
-
citedBy.get(premiseId).add(r.id);
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1567
|
+
// The invoking party's own record(s) for the target — one candidate source
|
|
1568
|
+
// id per tag it could have asserted under — filtered down to whichever
|
|
1569
|
+
// ones the triple is ACTUALLY recorded under. Zero of them means it was
|
|
1570
|
+
// never this party's own assertion, whatever else stored it.
|
|
1571
|
+
const targetSourceIds = byId.get(targetId).sourceIds || [];
|
|
1572
|
+
const candidateSourceIds = scoped.map((tag) => factRecordIdForTag(targetId, tag).slice(targetId.length + 1));
|
|
1573
|
+
const ownSourceIds = scoped.length ? targetSourceIds.filter((sid) => candidateSourceIds.includes(sid)) : [];
|
|
1574
|
+
const ownsTarget = !scoped.length || ownSourceIds.length > 0;
|
|
1575
|
+
if (scoped.length && !ownsTarget) {
|
|
1576
|
+
return { retracted: [], count: 0, budget, depth, truncated: false, found: true, ownRecord: false, stillStands: true };
|
|
1561
1577
|
}
|
|
1578
|
+
// Another source's record keeps the triple standing even after this
|
|
1579
|
+
// party's own record(s) go — the premise never actually broke, so no
|
|
1580
|
+
// cascade runs.
|
|
1581
|
+
const stillStands = scoped.length > 0 && ownSourceIds.length < targetSourceIds.length;
|
|
1562
1582
|
|
|
1563
1583
|
const removed = new Set([targetId]);
|
|
1564
1584
|
const order = [targetId]; // deterministic report order: target first, then removal order
|
|
1565
1585
|
const reground = new Map(); // survivor fact id -> the environments to persist for it
|
|
1566
1586
|
let truncated = false;
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1587
|
+
|
|
1588
|
+
// A scoped retraction whose triple STILL STANDS through another source's
|
|
1589
|
+
// record never runs the cascade at all — its premise never actually broke,
|
|
1590
|
+
// so nothing entailed from it loses support.
|
|
1591
|
+
if (!stillStands) {
|
|
1592
|
+
// Only a purely-entailed fact ever carries a walkable justification —
|
|
1593
|
+
// a fact later independently taught is never a cascade candidate at all.
|
|
1594
|
+
const entailedRows = rows.filter((r) => environmentsOf(r).length && isPurelyEntailed(r.provenance));
|
|
1595
|
+
// premise id -> the entailed fact ids whose environments cite it. Built
|
|
1596
|
+
// ONCE; each round's candidate set reads it for the facts the newest
|
|
1597
|
+
// removals could actually touch — backward relevance from the same
|
|
1598
|
+
// structure a forward pass reads forward.
|
|
1599
|
+
const citedBy = new Map();
|
|
1600
|
+
for (const r of entailedRows) {
|
|
1601
|
+
for (const env of environmentsOf(r)) {
|
|
1602
|
+
for (const premiseId of env) {
|
|
1603
|
+
if (!citedBy.has(premiseId)) citedBy.set(premiseId, new Set());
|
|
1604
|
+
citedBy.get(premiseId).add(r.id);
|
|
1605
|
+
}
|
|
1574
1606
|
}
|
|
1575
1607
|
}
|
|
1576
|
-
const candidates = [...candidateIds].map((id) => byId.get(id))
|
|
1577
|
-
.sort((a, b) => a.subject.localeCompare(b.subject) || a.predicate.localeCompare(b.predicate) || a.object.localeCompare(b.object));
|
|
1578
|
-
if (!candidates.length) break; // fixpoint — nothing cites what just fell
|
|
1579
|
-
|
|
1580
|
-
// The surviving fact set for THIS round excludes every candidate's own
|
|
1581
|
-
// row too, not just `removed` — otherwise a candidate could trivially
|
|
1582
|
-
// "reach itself" through its own not-yet-deleted edge, or lean on a
|
|
1583
|
-
// sibling candidate standing on the same broken premise.
|
|
1584
|
-
const survivors = rows.filter((r) => !removed.has(r.id) && !candidateIds.has(r.id));
|
|
1585
|
-
const survivorIds = new Set(survivors.map((r) => r.id));
|
|
1586
|
-
const enumerateSupport = buildSupportEnumerator(survivors);
|
|
1587
|
-
const stillDerivable = buildSurvivorDerivabilityCheck(survivors);
|
|
1588
1608
|
|
|
1589
|
-
let
|
|
1590
|
-
let
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
// survivor (the stale-justification fix).
|
|
1598
|
-
const environments = environmentsOf(c);
|
|
1599
|
-
const intact = environments.filter((env) => env.every((id) => survivorIds.has(id)));
|
|
1600
|
-
if (intact.length) {
|
|
1601
|
-
if (intact.length !== environments.length) reground.set(c.id, intact);
|
|
1602
|
-
continue;
|
|
1609
|
+
let round = 0;
|
|
1610
|
+
let newlyRemoved = [targetId];
|
|
1611
|
+
for (; round < depth; round += 1) {
|
|
1612
|
+
const candidateIds = new Set();
|
|
1613
|
+
for (const id of newlyRemoved) {
|
|
1614
|
+
for (const cited of citedBy.get(id) || []) {
|
|
1615
|
+
if (!removed.has(cited)) candidateIds.add(cited);
|
|
1616
|
+
}
|
|
1603
1617
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1618
|
+
const candidates = [...candidateIds].map((id) => byId.get(id))
|
|
1619
|
+
.sort((a, b) => a.subject.localeCompare(b.subject) || a.predicate.localeCompare(b.predicate) || a.object.localeCompare(b.object));
|
|
1620
|
+
if (!candidates.length) break; // fixpoint — nothing cites what just fell
|
|
1621
|
+
|
|
1622
|
+
// The surviving fact set for THIS round excludes every candidate's own
|
|
1623
|
+
// row too, not just `removed` — otherwise a candidate could trivially
|
|
1624
|
+
// "reach itself" through its own not-yet-deleted edge, or lean on a
|
|
1625
|
+
// sibling candidate standing on the same broken premise.
|
|
1626
|
+
const survivors = rows.filter((r) => !removed.has(r.id) && !candidateIds.has(r.id));
|
|
1627
|
+
const survivorIds = new Set(survivors.map((r) => r.id));
|
|
1628
|
+
const enumerateSupport = buildSupportEnumerator(survivors);
|
|
1629
|
+
const stillDerivable = buildSurvivorDerivabilityCheck(survivors);
|
|
1630
|
+
|
|
1631
|
+
let progressed = false;
|
|
1632
|
+
let hitBudget = false;
|
|
1633
|
+
newlyRemoved = [];
|
|
1634
|
+
for (const c of candidates) {
|
|
1635
|
+
if (removed.size >= budget) { hitBudget = true; break; }
|
|
1636
|
+
// FAST PATH: an environment whose every premise still stands keeps the
|
|
1637
|
+
// fact — pure set membership, no re-derivation. When some environments
|
|
1638
|
+
// broke, queue the pruned set so the next retraction still sees the
|
|
1639
|
+
// survivor (the stale-justification fix).
|
|
1640
|
+
const environments = environmentsOf(c);
|
|
1641
|
+
const intact = environments.filter((env) => env.every((id) => survivorIds.has(id)));
|
|
1642
|
+
if (intact.length) {
|
|
1643
|
+
if (intact.length !== environments.length) reground.set(c.id, intact);
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
// ENUMERATE: a fresh premise environment among the survivors re-grounds
|
|
1647
|
+
// the fact under new citations.
|
|
1648
|
+
const fresh = enumerateSupport(c, { maxEnvironments });
|
|
1649
|
+
if (fresh.length) {
|
|
1650
|
+
reground.set(c.id, fresh);
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
// BOOLEAN BACKSTOP: the closure walk is the final authority — it sees
|
|
1654
|
+
// multi-hop support with no materialised direct edge to cite, so a
|
|
1655
|
+
// still-derivable fact is never removed on a stale citation alone (its
|
|
1656
|
+
// environments stay as they were).
|
|
1657
|
+
if (stillDerivable(c)) continue;
|
|
1658
|
+
removed.add(c.id);
|
|
1659
|
+
order.push(c.id);
|
|
1660
|
+
newlyRemoved.push(c.id);
|
|
1661
|
+
progressed = true;
|
|
1610
1662
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
if
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
progressed = true;
|
|
1663
|
+
if (hitBudget) { truncated = true; break; }
|
|
1664
|
+
if (!progressed) break; // every candidate this round survived — fixpoint
|
|
1665
|
+
}
|
|
1666
|
+
if (!truncated && round >= depth) {
|
|
1667
|
+
// depth exhausted, not a natural fixpoint — honestly flag it if a pending
|
|
1668
|
+
// candidate (any surviving fact whose environment union still cites a
|
|
1669
|
+
// removed id) would have been checked next round.
|
|
1670
|
+
truncated = entailedRows.some((r) => !removed.has(r.id) && r.justification.some((j) => removed.has(j)));
|
|
1620
1671
|
}
|
|
1621
|
-
if (hitBudget) { truncated = true; break; }
|
|
1622
|
-
if (!progressed) break; // every candidate this round survived — fixpoint
|
|
1623
|
-
}
|
|
1624
|
-
if (!truncated && round >= depth) {
|
|
1625
|
-
// depth exhausted, not a natural fixpoint — honestly flag it if a pending
|
|
1626
|
-
// candidate (any surviving fact whose environment union still cites a
|
|
1627
|
-
// removed id) would have been checked next round.
|
|
1628
|
-
truncated = entailedRows.some((r) => !removed.has(r.id) && r.justification.some((j) => removed.has(j)));
|
|
1629
1672
|
}
|
|
1630
1673
|
|
|
1631
|
-
|
|
1674
|
+
// The target's own position in `order` narrows to the invoking party's
|
|
1675
|
+
// record(s) when scoped; every cascade id stays group-wide — an entailed
|
|
1676
|
+
// fact is never itself "the invoking party's own", so there is no
|
|
1677
|
+
// per-source record to narrow it to.
|
|
1678
|
+
const removalIds = scoped.length
|
|
1679
|
+
? [...ownSourceIds.map((sid) => `${targetId}@${sid}`), ...order.slice(1)]
|
|
1680
|
+
: order;
|
|
1681
|
+
const { removed: actuallyRemoved } = await removeFacts(repoDir, removalIds);
|
|
1632
1682
|
if (appendFactsFn) {
|
|
1633
1683
|
const regroundWrites = [...reground.entries()]
|
|
1634
1684
|
.filter(([id]) => !removed.has(id))
|
|
@@ -1644,7 +1694,10 @@ export async function retractSubClassOf(repoDir, subject, object, {
|
|
|
1644
1694
|
});
|
|
1645
1695
|
if (regroundWrites.length) await appendFactsFn(repoDir, regroundWrites);
|
|
1646
1696
|
}
|
|
1647
|
-
return {
|
|
1697
|
+
return {
|
|
1698
|
+
retracted: actuallyRemoved, count: actuallyRemoved.length, budget, depth, truncated, found: true,
|
|
1699
|
+
...(scoped.length ? { ownRecord: true, stillStands } : {}),
|
|
1700
|
+
};
|
|
1648
1701
|
}
|
|
1649
1702
|
|
|
1650
1703
|
/**
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// actually seen. `exposedRoomIds` is the set of rooms this auto-play run has
|
|
12
12
|
// itself moved into (the opening room included from turn 0) — it is the sole
|
|
13
13
|
// caller making every move, so it always knows this, and threads the set
|
|
14
|
-
// forward turn to turn exactly like
|
|
14
|
+
// forward turn to turn exactly like predator-prey.mjs threads its own agents
|
|
15
15
|
// shape. `exposedFacts` turns that set into the actual filtered view: a fact
|
|
16
16
|
// is exposed when its subject's CURRENT placement resolves into an exposed
|
|
17
17
|
// room, when the subject is the player, or when the fact IS the world's
|
|
@@ -130,7 +130,7 @@ async function stepTowardThenAct({
|
|
|
130
130
|
* and execute exactly the one move that goal implies through `adventureTurn`
|
|
131
131
|
* — the same public entry point a real chat turn calls. Returns
|
|
132
132
|
* `{ turn, goal, plan, done, stalled, exposedRoomIds }`, mirroring
|
|
133
|
-
*
|
|
133
|
+
* predator-prey.mjs's own tick shape: `plan` is the remaining multi-step route a
|
|
134
134
|
* `findActionPath` search found this tick (or null when the move was a
|
|
135
135
|
* single, immediate step — an adjacent unexposed exit, or a take), `goal` is
|
|
136
136
|
* a short line describing what this tick did, `done` means the objective is
|
|
@@ -944,6 +944,8 @@ ${THEME_TOKENS_CSS}
|
|
|
944
944
|
onto further lines, staying right-anchored rather than dropping the
|
|
945
945
|
whole pill row under the heading. */
|
|
946
946
|
.command-head .pills { flex: 1 1 auto; min-width: 0; justify-content: flex-end; margin-bottom: 0; }
|
|
947
|
+
.teach-toggle { display: flex; align-items: center; gap: .35rem; font-family: ${MONO_STACK}; font-size: .72rem; color: var(--muted); margin: 0 0 .4rem; cursor: pointer; }
|
|
948
|
+
.teach-toggle input { accent-color: var(--gilt); }
|
|
947
949
|
.chatask { display: flex; align-items: center; gap: .5rem; border-top: 1px solid var(--line); padding-top: .5rem; }
|
|
948
950
|
.chatask .prompt { color: var(--taught); font-size: .78rem; font-family: ${MONO_STACK}; }
|
|
949
951
|
.chatask input { flex: 1; font-family: ${MONO_STACK}; font-size: .78rem; background: var(--bg); color: var(--ink); border: 1px solid var(--line); padding: .32rem .55rem; min-width: 0; }
|
|
@@ -1042,6 +1044,10 @@ ${scenarioList.map((s, i) => ` <option value="${i}"${i === 0 ? " select
|
|
|
1042
1044
|
<h2>What would you like to do</h2>
|
|
1043
1045
|
<div class="pills" id="pills"></div>
|
|
1044
1046
|
</div>
|
|
1047
|
+
<label class="teach-toggle" title="With this on, a sentence like "Candle is in the study." writes a fact into the world instead of running as a command.">
|
|
1048
|
+
<input type="checkbox" id="teachToggle">
|
|
1049
|
+
teach mode
|
|
1050
|
+
</label>
|
|
1045
1051
|
<form class="chatask" id="chatform">
|
|
1046
1052
|
<span class="prompt mono">tmct></span>
|
|
1047
1053
|
<input id="chatq" type="text" placeholder="go north" aria-label="Type a command, or ask a question" disabled>
|
|
@@ -1151,6 +1157,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
1151
1157
|
const pillsEl = el("pills");
|
|
1152
1158
|
const chatformEl = el("chatform");
|
|
1153
1159
|
const chatqEl = el("chatq");
|
|
1160
|
+
const teachToggleEl = el("teachToggle");
|
|
1154
1161
|
const carryListEl = el("carryList");
|
|
1155
1162
|
const mapWrapEl = el("mapWrap");
|
|
1156
1163
|
const mapViewportEl = el("mapViewport");
|
|
@@ -1525,7 +1532,7 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
1525
1532
|
}
|
|
1526
1533
|
function renderPills(rows, state, here) {
|
|
1527
1534
|
const actions = pillsFor(rows, state, here).filter((a) => a.indexOf("go ") !== 0);
|
|
1528
|
-
pillsEl.innerHTML = actions.map((a) => '<button type="button" class="pill">' + esc(a) + "</button>").join("");
|
|
1535
|
+
pillsEl.innerHTML = actions.map((a) => '<button type="button" class="pill" data-command="' + esc(a) + '">' + esc(a) + "</button>").join("");
|
|
1529
1536
|
// The empty input teaches the grounded noun form off the same affordance
|
|
1530
1537
|
// list the pills read — real props from THIS room, so a first-time player
|
|
1531
1538
|
// types "examine lamp", not a pronoun with nothing to bind to yet.
|
|
@@ -1822,12 +1829,10 @@ ${engineBundleJs ? `<script>\n${embedScriptText(engineBundleJs)}\n</script>` : `
|
|
|
1822
1829
|
|
|
1823
1830
|
async function boot({ fresh = false } = {}) {
|
|
1824
1831
|
const saved = !fresh && persist ? await persist.load() : null;
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
: {},
|
|
1830
|
-
);
|
|
1832
|
+
const restoreOptions = saved && saved.payload && saved.payload.memoryPayload
|
|
1833
|
+
? { restoredPayload: saved.payload.memoryPayload, restoredVisitedRoomIds: saved.payload.visitedRoomIds }
|
|
1834
|
+
: {};
|
|
1835
|
+
session = await tmct.open(world(), { ...restoreOptions, getTeachEnabled: () => teachToggleEl.checked });
|
|
1831
1836
|
lastTicks = 0;
|
|
1832
1837
|
const snap = await session.snapshot();
|
|
1833
1838
|
redraw(snap);
|
package/src/services/chat.mjs
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
stripTrailingScopeFiller, stripTrailingDiscourseTag, EDGE_NOUN_TO_METRIC, RELATIONS, LIST_TRIGGERS,
|
|
41
41
|
locativePreposition,
|
|
42
42
|
} from "../domain/ask-vocab.mjs";
|
|
43
|
-
import { COUNTERFACTUAL_RE, correctMisspellings, applyPreambleFrames, expandContractions, normalizeQuery, stripFillerWords, escapeRegex, kindNounAnaphoraHint, datedTeachSuffix } from "../domain/interpret/normalize.mjs";
|
|
43
|
+
import { COUNTERFACTUAL_RE, correctMisspellings, applyPreambleFrames, expandContractions, normalizeQuery, stripFillerWords, escapeRegex, kindNounAnaphoraHint, datedTeachSuffix, QUESTION_LEAD_RE } from "../domain/interpret/normalize.mjs";
|
|
44
44
|
import { setDefaultNlpAdapter } from "../domain/interpret/nlp-registry.mjs";
|
|
45
45
|
import { setConstructionBanks } from "../domain/interpret/strategies/constructions.mjs";
|
|
46
46
|
import { nlpAdapter } from "../adapters/ask-nlp.mjs";
|
|
@@ -2366,9 +2366,6 @@ const COMPARATIVE_ASK_RE = new RegExp(`^(?:is|are)\\s+(.+?)\\s+(${COMPARATIVE_SR
|
|
|
2366
2366
|
* preposition into a minted predicate (the general-verb teach/query lanes
|
|
2367
2367
|
* and the action-rule frames) — a single source so the set never forks. */
|
|
2368
2368
|
const PREP_SRC = "on|in|at|onto|upon|under|over|beside|near|behind|above|below|inside|outside";
|
|
2369
|
-
/** Interrogative / auxiliary leads that make an "X is a Y"-shaped line a QUESTION
|
|
2370
|
-
* ("what is a cache", "is a module a component"), never a teach declarative. */
|
|
2371
|
-
const QUESTION_LEAD_RE = /^(?:what|who|which|where|when|why|how|is|are|do|does|did|can|could|should|would|will|has|have)\b/i;
|
|
2372
2369
|
/** A plain declarative "X is a kind of Y" / "X is a Y" shape (subject-first, no
|
|
2373
2370
|
* question lead — paired with QUESTION_LEAD_RE at every call site), tolerating
|
|
2374
2371
|
* an infix "kind of"/"type of" (teachLane's own stripKindOf handles this same
|
|
@@ -4794,23 +4791,54 @@ async function teachLane(query, { memoryDir, sessionId = "", lexicon = null, cac
|
|
|
4794
4791
|
|
|
4795
4792
|
// RETRACTION — "forget that X is a Y": wires the data-layer retraction
|
|
4796
4793
|
// primitive (retractSubClassOf, src/domain/syllogise.mjs) up to chat-level
|
|
4797
|
-
// phrasing.
|
|
4794
|
+
// phrasing. Scoped to THIS session's own assertion (sourceTags below) —
|
|
4795
|
+
// the same tags teachFact/assertSentence write under — so retracting
|
|
4796
|
+
// never erases a fact another source taught, only this session's own copy.
|
|
4798
4797
|
//
|
|
4799
4798
|
// TRIGGER, never itself the authority: RETRACT_FORGET_RE only recognizes
|
|
4800
4799
|
// the SHAPE of a retraction sentence — it says nothing about whether
|
|
4801
|
-
// subject⊑object was ever actually taught
|
|
4802
|
-
// real and is the only thing that decides:
|
|
4803
|
-
// - found:
|
|
4804
|
-
//
|
|
4805
|
-
//
|
|
4806
|
-
//
|
|
4807
|
-
//
|
|
4800
|
+
// subject⊑object was ever actually taught, or taught by THIS session.
|
|
4801
|
+
// retractSubClassOf is asked for real and is the only thing that decides:
|
|
4802
|
+
// - found:false → subject⊑object was never a stored fact, and this
|
|
4803
|
+
// falls through to the rest of teachLane's ordinary cascade below
|
|
4804
|
+
// rather than claiming a specific, possibly-wrong reason.
|
|
4805
|
+
// - found:true, ownRecord:false → some OTHER source taught it; this
|
|
4806
|
+
// session has nothing of its own to withdraw.
|
|
4807
|
+
// - found:true, stillStands:true → this session's own record is gone,
|
|
4808
|
+
// but another source's record keeps the fact standing (no cascade —
|
|
4809
|
+
// its premise never actually broke).
|
|
4810
|
+
// - found:true, stillStands:false → this session held the only record;
|
|
4811
|
+
// the fact and its dependency-directed cascade are both gone.
|
|
4808
4812
|
if (retractForgetMatch) {
|
|
4809
4813
|
const { retractSubClassOf } = await import("../domain/syllogise.mjs");
|
|
4810
|
-
const {
|
|
4814
|
+
const { provenanceTag: aceProvenanceTag } = await import("../domain/grammar/assert.mjs");
|
|
4815
|
+
const {
|
|
4816
|
+
loadMemory: loadMemForRetract, readFactRows: readRowsForRetract, removeFacts,
|
|
4817
|
+
appendFacts: appendFactsForRetract, factRecordIdForTag,
|
|
4818
|
+
} = await import("../adapters/memory/core.mjs");
|
|
4811
4819
|
const result = await retractSubClassOf(memoryDir, retractSubject, retractObject, {
|
|
4812
|
-
|
|
4820
|
+
// A session's own positive assertion of subject⊑object can have
|
|
4821
|
+
// landed under either lane: the free-form teach lane (teachFact) or
|
|
4822
|
+
// the ACE-parsed assert lane (assertSentence) — both tags name here.
|
|
4823
|
+
sourceTags: [teachProvenanceTag(sessionId), aceProvenanceTag({ sessionId })],
|
|
4824
|
+
store: {
|
|
4825
|
+
loadMemory: loadMemForRetract, readFactRows: readRowsForRetract, removeFacts,
|
|
4826
|
+
appendFacts: appendFactsForRetract, factRecordIdForTag,
|
|
4827
|
+
},
|
|
4813
4828
|
});
|
|
4829
|
+
if (result.found && !result.ownRecord) {
|
|
4830
|
+
return {
|
|
4831
|
+
text: `"${retractSubject} is a kind of ${retractObject}" isn't something you taught me — it's on record from elsewhere, so there's nothing of yours to forget.`,
|
|
4832
|
+
via: "retract", miss: false,
|
|
4833
|
+
};
|
|
4834
|
+
}
|
|
4835
|
+
if (result.found && result.stillStands) {
|
|
4836
|
+
return {
|
|
4837
|
+
text: `noted — forgotten your own record of "${retractSubject} is a kind of ${retractObject}", `
|
|
4838
|
+
+ "but it's still stored, told to me by someone else.",
|
|
4839
|
+
via: "retract", miss: false,
|
|
4840
|
+
};
|
|
4841
|
+
}
|
|
4814
4842
|
if (result.found) {
|
|
4815
4843
|
const extra = result.count - 1; // beyond the target fact itself
|
|
4816
4844
|
return {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// mud-turn.mjs — one acting character's whole turn in a mud world: investigate
|
|
2
2
|
// the room it stands in, walk toward food it actually knows about, roll for
|
|
3
3
|
// digging out of the room's frontier, and (when none of that came to anything)
|
|
4
|
-
// set off for a room it has never stood in. The split mirrors
|
|
4
|
+
// set off for a room it has never stood in. The split mirrors predator-prey.mjs /
|
|
5
5
|
// spider-fly-turn.mjs: adventure.mjs owns the read/fold/write primitives, this
|
|
6
6
|
// file owns the per-tick decisions that drive them. Nothing here writes a fact
|
|
7
7
|
// of its own — every change goes out through runWorldCommand, recordTold,
|
package/src/services/mud-viz.mjs
CHANGED
|
@@ -411,6 +411,10 @@ ${scenarioList.length > 1 ? ` <select id="scenarioSelect" class="deck-sel
|
|
|
411
411
|
${scenarioList.map((s, i) => ` <option value="${i}"${i === 0 ? " selected" : ""}>${escapeHtml(s.label || scenarioLabel(s.worldPayload?.name))}</option>`).join("\n")}
|
|
412
412
|
</select>` : ""}
|
|
413
413
|
<button type="button" id="editModeBtn" aria-pressed="false">edit</button>
|
|
414
|
+
<label class="deck-teach" title="With this on, a sentence like "Candle is in the study." writes a fact into the world instead of running as a command.">
|
|
415
|
+
<input type="checkbox" id="teachToggle">
|
|
416
|
+
teach
|
|
417
|
+
</label>
|
|
414
418
|
<button type="button" class="deck-info-btn" id="deckInfoBtn" aria-expanded="false" aria-controls="deckInfoPopup" aria-label="about this demo">?</button>
|
|
415
419
|
<span class="mono deck-turns" id="globalTurnCount">turns: 0</span>
|
|
416
420
|
</div>
|
|
@@ -634,6 +638,8 @@ const MUD_STYLE = `
|
|
|
634
638
|
background: rgba(255,255,255,.5); color: var(--mud-ink);
|
|
635
639
|
}
|
|
636
640
|
.deck-select:hover { border-color: var(--burrow-glow); }
|
|
641
|
+
.deck-teach { display: flex; align-items: center; gap: .3rem; font-family: ${MONO_STACK}; font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--soil-mid); cursor: pointer; }
|
|
642
|
+
.deck-teach input[type="checkbox"] { accent-color: var(--burrow-glow); }
|
|
637
643
|
.deck-play { background: var(--mud-ink) !important; color: var(--parchment); border-color: var(--mud-ink) !important; padding: .38rem 1.1rem !important; }
|
|
638
644
|
.deck-play[aria-pressed="true"] { background: var(--burrow-glow) !important; border-color: var(--burrow-glow) !important; color: var(--mud-ink); }
|
|
639
645
|
.deck-turns { margin-left: auto; font-size: .74rem; color: var(--soil-mid); }
|
|
@@ -1006,7 +1012,7 @@ const MUD_STYLE = `
|
|
|
1006
1012
|
--lem-readout: var(--burrow-glow); --lem-readout-bg: var(--soil-deep);
|
|
1007
1013
|
--lem-chalk: var(--soil-mid);
|
|
1008
1014
|
}
|
|
1009
|
-
.deck-slider, .deck h3 { color: var(--lem-chalk); }
|
|
1015
|
+
.deck-slider, .deck h3, .deck-teach { color: var(--lem-chalk); }
|
|
1010
1016
|
.deck-slider input[type="range"] { accent-color: var(--burrow-glow); }
|
|
1011
1017
|
.deck button, .pane-controls button {
|
|
1012
1018
|
background: var(--lem-face); color: var(--mud-ink); border: 1px solid var(--soil-mid); border-radius: 3px;
|
|
@@ -3052,7 +3058,10 @@ function pageScript() {
|
|
|
3052
3058
|
// scenario's cast paired with the LAST scenario's pane ids, which is what
|
|
3053
3059
|
// a null pane-element lookup in renderAll means when it happens.
|
|
3054
3060
|
for (let i = 0; i < cast.length; i += 1) slotOf[cast[i]] = slots[i];
|
|
3055
|
-
const opened = await window.tmct.open(scenario().worldPayload, {
|
|
3061
|
+
const opened = await window.tmct.open(scenario().worldPayload, {
|
|
3062
|
+
characters: everyone(), epoch: nextEpoch,
|
|
3063
|
+
getTeachEnabled: function () { return el("teachToggle").checked; },
|
|
3064
|
+
});
|
|
3056
3065
|
if (seq !== bootSeq) return;
|
|
3057
3066
|
session = opened;
|
|
3058
3067
|
if (liveRoom) {
|