Skip to content

t1k:cocos:playable:submodule-to-package

FieldValue
Moduleplayable
Version2.14.4
Effortmedium
Tools—

Keywords: game-foundation, package, parameter-tool, submodule, vendored

/t1k:cocos:playable:submodule-to-package
[--verify-only]

Submodule → @playablelabs Package Migration

Section titled “Submodule → @playablelabs Package Migration”

Convert a Cocos 3.8.7 playable from the PLAGameFoundation / PlayableParamterTool git submodules to the vendored @playablelabs/game-foundation and @playablelabs/parameter-tool packages under assets/packages/.

IntentPath
Full migration (submodules still present)Run Migration steps 1→7
Packages already vendored, just check for bugs--verify-only → Step 7 only
A toast/tutorial/layout call throws ReferenceError: X is not definedGotcha 1 → run the verifier, patch the flagged file

The .ts / .core.ts / .core.impl.ts triplet in each vendored package is generated at publish time by the cocos-package-manager editor extension (split + obfuscate) — it is NOT how the source repo looks (upstream is a single monolithic .ts). That obfuscator has a defect (Gotcha 1) and several call-site APIs changed between the submodule and the package (Step 4). Both must be handled or the game silently fails to start / throws at runtime.

Drop both [submodule …] blocks from .gitmodules (keep ConfigPath, optimize-size), then git rm --cached the two submodule paths (assets/PLAGameFoundation, assets/PlayableParamterTool). Remove their VCS root mappings from .idea/vcs.xml if present.

Place the published packages under assets/packages/@playablelabs/game-foundation/ and .../parameter-tool/ (with their .meta files). These carry the generated .core.impl.ts split form.

In <project>/package.json, add both to playableSync[]:

"playableSync": ["@playablelabs/warning", "@playablelabs/parameter-tool", "@playablelabs/game-foundation"]

Step 4 — Repoint imports + apply API changes

Section titled “Step 4 — Repoint imports + apply API changes”

Rewrite every db://assets/PLAGameFoundation/… → db://assets/packages/@playablelabs/game-foundation/… and db://assets/PlayableParamterTool/… → db://assets/packages/@playablelabs/parameter-tool/…. Then apply the known signature changes — these are not path-only:

Call siteChange
ParameterUtilsParameterReplacedAssetsHandler moved to assetParameterUtils/
PlayableConfig TableParameternow takes category after minItems/maxItems
VirtualJoystick TouchEndSignalnow requires (worldPosition, uiPosition)
ParameterControllergetTextureFromBase64 now resolves null (was: never resolves). Guard the null — an unguarded null yields a textureless SpriteFrame that throws in SpriteFrame.getHash
ParameterManagerparameter-tool is now standalone and no longer imports game-foundation. You MUST register the three runtime seams — see Step 4a below. Without them it no-ops silently, ParametersReadySignal never reaches the bus, and the game flow never starts

Step 4a — Register the ParameterToolRuntime seams

Section titled “Step 4a — Register the ParameterToolRuntime seams”

There is no combined ParameterToolRuntime.register(...) method. The runtime exposes three separate registrars, one per seam, each taking an interface implementation:

export interface ISignalEmitter { fire(signal: object): void; }
export interface IVolumeSink { onUpdateVolume(volume: number): void; }
export interface IPresetPlayer { play(node: Node, config: AnimationPresetValues): unknown; }
export declare class ParameterToolRuntime {
static registerSignalEmitter(emitter: ISignalEmitter | null): void;
static registerVolumeSink(sink: IVolumeSink | null): void;
static registerPresetPlayer(player: IPresetPlayer | null): void;
static get signalEmitter(): ISignalEmitter;
static get volumeSink(): IVolumeSink;
static get presetPlayer(): IPresetPlayer;
}

Register all three during boot, before ParameterManager initialises:

ParameterToolRuntime.registerSignalEmitter(SignalBus);
ParameterToolRuntime.registerVolumeSink(AudioService);
ParameterToolRuntime.registerPresetPlayer(AnimationPresetService);

Each seam is registered independently because they are independent capabilities — a project with no preset animations registers the first two and leaves presetPlayer unset. This is the interface-and-provider seam that keeps parameter-tool free of a game-foundation dependency; the package names only its own interfaces, never a concrete game-foundation class.

Verify against the vendored declarations, not against memory or against the count above: assets/packages/@playablelabs/parameter-tool/ParameterToolRuntime.core.types.ts. A missing seam fails silently at runtime — there is no compile error and no warning. There have been unverified reports of more than three registrars in a newer package build; @playablelabs/parameter-tool is not vendored in this kit’s own repo, so that count cannot be confirmed from here — treat the declarations file on the target project as the source of truth, not the list above.

Step 5 — Remap scene/prefab script uuids

Section titled “Step 5 — Remap scene/prefab script uuids”

The submodule scripts and the package scripts have different meta uuids. In MainScene.scene (and any prefab referencing a foundation component, e.g. AudioEmitter.prefab), remap __type__ / _componentId / __uuid__ from the old submodule-script uuid to the package-script uuid. Missing this = component silently detaches in the editor. Do this in the Cocos editor (drag-reassign) or by matching each old uuid to the new .ts.meta uuid.

Delete any .meta whose prefab no longer exists after the swap.

Run the splitter-defect scanner (also the entry point for --verify-only):

Terminal window
node "$HOME/.claude/skills/t1k-cocos-playable-submodule-to-package/scripts/scan-missing-sibling-imports.cjs" \
<project>/assets/packages/@playablelabs/game-foundation \
<project>/assets/packages/@playablelabs/parameter-tool

For each DEFECT: line, add the printed import { … } from './<Sibling>'; to that .core.impl.ts. Exit 0 = clean. Then open the project in the Cocos editor and confirm LOADING→FTUE→GAMEPLAY runs (the three ParameterToolRuntime seams from Step 4a are what let the flow start). If the flow stalls at LOADING, an unregistered seam is the first thing to check — it produces no error, only silence.

  • Obfuscator drops enum sibling imports (the ReferenceError class). The publish-time obfuscator is the Cocos editor extension The1Studio/cocos-package-manager (ComponentExtractService), which extracts an @ccclass component’s method bodies into an obfuscated .core.impl sibling. Its topLevelLocalNames() gate — the set of module-scope declarations that keep a referencing method in the readable wrapper — enumerated const/let/var/function/class but omitted enum. So a method referencing a co-located enum got extracted anyway, leaving the enum a free identifier with no import → ReferenceError the instant that path fires (e.g. ToastPosition in ToastController on every toast; AspectFitMode, AspectRatioCategory, FingerAction on colder paths). // @ts-nocheck hides it from tsc. Root cause is in cocos-package-manager, NOT PLAGameFoundation (its source is a correct monolith) and NOT UPMAutoPublisher (that publishes to a different registry and does not obfuscate). Fixed in cocos-package-manager PR #10 (enums added to the gate). Until that ships and packages are republished, patch the vendored copy as a stopgap — a npm update @playablelabs/* reverts it — running the Step 7 scanner after any package update and re-patching with the same one-liner: append import{X}from'./<Sibling>'; after the existing from'cc'; import.
  • .core.impl.ts files trip the descriptive-name Edit/Write hook. Their PascalCase.core.impl.ts names are rejected by the naming hook on Edit/Write. Patch them with a Node fs one-liner via Bash (the hook only guards Edit/Write), or in the editor.
  • The bug is invisible to type-checking. Every .core.impl.ts carries // @ts-nocheck, so tsc / t1k-cocos-base-tsc-validate will NOT catch the missing import. Only runtime execution (or this scanner) does.
  • Do not confuse with t1k-cocos-base-migrate. That skill is the Cocos 2.4.15 JS→TS pipeline. This skill is Cocos 3.8.7 submodule→package and shares no code.