Skip to content

PML mixins carry across; the PML disclaimer becomes expandable - #133

Merged
roowus merged 1 commit into
mainfrom
feat/pml-mixins-and-expandable-caveat
Aug 28, 2026
Merged

PML mixins carry across; the PML disclaimer becomes expandable#133
roowus merged 1 commit into
mainfrom
feat/pml-mixins-and-expandable-caveat

Conversation

@roowus

@roowus roowus commented Aug 28, 2026

Copy link
Copy Markdown
Owner

What

Owner: "the pml disclainmer should be expandable, also can u make mixins carry over to". Two features — one UI, one that reverses the most-repeated claim in the PML compatibility docs.

1. Mixins carry across

The docs said PML mixins were not translatable in general — sound, as far as it went: you cannot rewrite a token-splice into an AST mixin, and there is no live function left to patch by the time a mod's init runs. The third option nobody had taken: run PML's patch language at TSPML's own patch time. The #62 plan already carries declared patches to the served bundle; a splice rides it.

  • Collect (lib/pml/shim.ts): object-spec registerClassMixin (the form real CDN mods ship) parsed per call; the four token-anchored types (INSERT/REPLACE/REPLACEBETWEEN/REMOVEBETWEEN) are collected. Method-extent types, wasm offsets, and the other seven families keep their named per-call refusals.
  • Persist (app/play/page.tsx): after a load summary, collected specs merge onto the record (JSON-key dedupe) and the plan re-parks — deliberately WITHOUT the mod-reload path; the mod is running, only the plan changed.
  • Carry (lib/user-patches.ts): pmlMixins join pasted mixins, one set per mod, func capped exactly as inject.
  • Apply (lib/pml/splice.ts + demo-transform.ts): token surgery on the RAW bundle before Babel (PML tokens are written in Kodub's own minified formatting and would not survive regeneration), under an exactly-once anchor rule — ambiguous matches refuse with the count. The engine's existing re-parse gate covers the spliced source; a broken splice boots vanilla (observed live: planStatus: base-failed, game fine).
  • Measured, not assumed: twin anchors (tokenStart === tokenEnd, ghosttoggle's real shape) occur once in the real bundle — so one occurrence is legal (empty span, insert after it), two splice between, three refuse. The first draft of the rule would have refused ghosttoggle's actual mixin.

Player-visible consequence, stated at install and in the Mods panel: mixins apply on the NEXT launch — first boot collects and says so, the reload applies. Same shape as physics plans, for the same reason (plans park before the frame's first fetch).

2. Expandable disclaimer + grouped filters

  • The PML install caveat is a native <details>: one line, full reasoning behind an expander.
  • The filter row split into labelled groups — loader / category / people (earlier owner note) — each keeping tag-row so existing smoke locators are untouched.

Proof

  • smoke:pml PASS end-to-end, both halves: refusals by name with the mod still loaded; a real splice (token = the game's own FPS-counter keybind registration, exactly-once in vanilla 0.6.2, executes at bundle eval) is collected-and-not-run on boot 1, then executed in the game frame after one reload, with the in-bundle report reading applied: 1.
  • The failed first run proved the fail-closed gate: a syntax-breaking func → base-failedvanilla boot, never corrupted.
  • Portal 722 tests (695 → 722; pml-splice.test.ts new, shim collection block, splice composition, plan carriage); repo-wide 1,335 green; tsc + smoke typecheck clean — the typecheck run AFTER the last script edit this time.

Docs (same merge)

docs/concepts/pml-compatibility.md rewritten ("Mixins: carried, with PML's semantics and TSPML's discipline" replaces "Why mixins cannot be translated"; the carries table, Files, and Proved-in-browser sections updated), docs/project/progress.md new section, README bullet + count.

…andable

Mixins were the documented-impossible half of PML compatibility. The
impossibility argument rules out TRANSLATING a PML splice into a TSPML AST
mixin, or faking the live function it patches — it does not rule out running
PML's patch language at TSPML's own patch time. The #62 request-carried plan
already goes from "a mod's declared patches" to "the served bundle"; a splice
is just another patch riding it.

Pipeline: shim COLLECTS object-spec registerClassMixin calls (the form real
CDN mods ship — ghosttoggle 1.0.8 is the reference) per call, refusing
everything it cannot faithfully apply (method-extent types, wasm offsets, the
other seven families keep their named refusals). The page persists collected
specs onto the record (JSON-key dedupe) and re-parks the plan WITHOUT
reloading the mod — the mod is already running; only the plan changed.
user-patches carries pmlMixins with the same caps as injects (func). At the
route, splices edit the RAW bundle text BEFORE Babel — PML tokens are written
in Kodub's own minified formatting and would not survive regeneration — and
the engine's existing re-parse gate covers the spliced source: a broken
splice boots vanilla, never corrupted (observed live).

The rule that makes it TSPML's: an anchor must match exactly once or the
patch refuses with the count — plus the one concession measurement forced:
twin anchors (tokenStart === tokenEnd, what ghosttoggle actually ships) occur
ONCE in the real bundle, so a single occurrence is legal (empty span, insert
after); two splice between; three refuse.

smoke:pml now proves both halves in a browser: OVERRIDE + registerGlobalMixin
refused by name while the mod stays loaded; a real splice whose token exists
exactly once in vanilla 0.6.2 and runs at bundle eval is collected-and-not-run
on the first boot, then EXECUTED in the game frame after one reload with the
in-bundle report reading applied:1 on main.bundle.js.

UI: the PML install caveat is a native <details> — one line, the paragraph
behind an expander. The filter row split into labelled groups (loader /
category / people) per owner note, each keeping the tag-row class so every
existing locator still sees the same control.

Portal 695 -> 722 tests (pml-splice.test.ts new at 18); repo-wide 1,335
green; smoke typecheck run AFTER the last script edit this time.
@vercel

vercel Bot commented Aug 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tspml Ready Ready Preview Aug 28, 2026 5:56pm

@roowus
roowus merged commit 52740a6 into main Aug 28, 2026
6 of 7 checks passed
@roowus
roowus deleted the feat/pml-mixins-and-expandable-caveat branch August 28, 2026 18:51
roowus added a commit that referenced this pull request Aug 28, 2026
* fix(portal): the PML Core / PML API rows answer 'do I need this?' on the card

Both rows now say outright what the adapter means for them: TSPML supplies
the PML runtime itself, so neither is required — install only if a mod asks
by name (nothing in the catalog does). The pmlapi summary also dropped a
stale clause claiming mixins 'cannot carry across'; token-anchored ones have
since #133.

* feat(portal): game versions become the fourth derived tag group

Owner: "also add version numbers to the tag". Fourth application of the
derivation rule (format, persons, no-build prose, versions): everything a card
states about itself is computed from the row's own fields.

The chip is the versions the row COVERS, not the gameVersions list: computed
with buildsForGameVersion against a universe (every release version in the
catalog + the launcher default, newest first). Chipping the literal range
string would have made the 0.6.2 filter falsely drop poly-to-track, the one
native mod — pinned by unit test and smoke:registry leg 2d (chip narrows to
exactly the eight covering rows, range entry among them; the inverse of the
thirteen-warned assertion, one predicate).

Prereleases are excluded from the universe — a beta chip answers nobody's
filter question; the detail facts row still shows the full list. The filter
and the chips agree by construction: searchRegistry derives its own universe
from the entries in scope, the grid passes the tab's, the detail page the
registry's. Monospace (.tag-version) is the only visual signature.

Portal 722 -> 728 tests (registry.test.ts 80 -> 86); repo-wide 1,341 green;
smoke typecheck after the last script edit; smoke:registry PASS with the new
leg; screenshot review done (four labelled rows: loader/category/version/people).
roowus added a commit that referenced this pull request Aug 29, 2026
Owner's diagnostic log: "4 mods 1 tspml works 3 pml dont" — three mods, three
refusals, three real call shapes the collector had never met. All three mods'
sources were fetched from the CDN and read; all three now carry.

- 3decspeed refused as "no type" because its spec's type IS a type — a NUMBER.
  PML's real MixinType (PolyTypes.js, read from the CDN) is a numeric enum
  (INSERT=3, REPLACEBETWEEN=5), and every mod importing PolyTypes.js by its
  absolute CDN URL — all of them — puts that number in the spec. The parser is
  now bilingual: names and numbers, mapped from the real enum.
- husplits' registerFuncMixin(name, spec) and noitalics'
  registerGlobalMixin(spec) were refused BY FAMILY. Family is PML's targeting
  hint — it decides where PML looks — and the exactly-once token rule subsumes
  looking. Every source-op family collects now; what still refuses, by name:
  method-extent values (0/1/2), class-wide values (4/7/8, they apply at every
  site at once), the two wasm families, and calls with no spec object.

The re-run exposed a trap in the fixture itself: in comma-expression context a
bare parenthesized func PARSES as a call of the preceding call's result — it
sets its marker, then throws, truncating the game's boot tail after the marker.
#133's "passing" splice was almost certainly doing exactly that while green.
The discipline (now in the fixture's comments): leading comma in comma-sequence
context, semicolon-statement in block context. And the numeric splice's first
token was lazy-path code (verifier catch) — applied but never ran; a marker
proves execution only where the token's site executes at bundle eval. The
fixture now pins: string enum, numeric enum, lone-object global twin-anchor —
3/3 applied in the browser, both markers firing in the game frame.

Three catalog summaries that said these mods' mixins "are refused and
reported" now say they carry after one restart.

Portal 728 -> 734 tests; repo-wide 1,347 green; smoke typecheck after the
last script edit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant