Skip to content

t1k:cocos:playable:parameter-scan

FieldValue
Moduleplayable
Version2.14.4
Effortmedium
Tools—

Keywords: canvas-scan, component-scan, config-scan, discover, find-nodes, parameter-scan, scan, scene-scan

/t1k:cocos:playable:parameter-scan
[--mode ui|config|all] [--deep] [--canvas <path>] [--path <folder>]

Two-layer scene and config discovery engine for the flat-primitive parameter architecture. The scanner identifies parameterizable Cocos nodes and emits a report; the implement step consumes that report and produces flat-primitive PlayableConfig entries (one per node, with prefixed primitives — never ComponentParameter wrappers).

Scanning and matching against the fixed rubric tables is mechanical; only novel or ambiguous compositions need judgment (routing SSOT: rules/orchestration-rules.md § “Task-Type → Agent Routing”):

StepAgent
Run batch-scan.cjs / parse scene JSON, then match each node’s component signature against the NodePreset shape tablet1k-list-classifier
Parse *Config.ts and classify each candidate by the stated keyword→primitive table (Health/Timing/Speed/Counts/Distance/Colors/Flags)t1k-list-classifier
--deep magic-number extraction: flag each numeric literal per the stated exclusion rules (loop/array indices, imports)t1k-list-classifier
Design a NEW NodePreset for a component combination the table doesn’t cover, or judge an ambiguous @property({ group }) groupingstays with the orchestrating skill/user — this skill applies presets, it does not invent them

The scan report is diagnostic input to a separate implement step, so nothing here writes config or code — t1k-list-classifier has no Edit/Write tool, which matches the skill’s own report-only shape. An unmatched node (no preset fits) is a judgment call outside the stated rubric and is reported as “skipped”, not guessed at by the cheap tier.

LayerFlagApprovalOutput
UI--mode uiNone (auto)Parameterizable nodes JSON
Config--mode configRequiredConfig candidates JSON
Both--mode allConfig onlyCombined report
IntentAction
”Parameterize all UI components”--mode ui → auto-generate per-node flat-primitive entries
”Scan nodes under a specific Canvas”--mode ui --canvas <path>
”Extract config values for params”--mode config → present candidates
”Find magic numbers in gameplay code”--mode config --deep
”Scan specific game folder”--mode config --path <folder>
”Full parameter pass”--mode all
ModeScopeMCP RequiredApproval
--mode uiCanvas descendants + component detectionYesNone
--mode config*Config.ts files in scripts/NoRequired
--mode config --deepAbove + magic numbers in gameplay codeNoRequired
--mode allUI (auto) then Config (review)YesConfig only
Old FlagMaps ToEmits Warning
--quick--mode ui (wired nodes only)Yes
--standard--mode uiYes
--deep (alone)--mode all --deepYes
CategoryKeywordsPrimitive
Healthhp, health, damage, healRangeParameter (0-1000)
Timingduration, delay, cooldown, intervalRangeParameter (0-60)
Speedspeed, velocity, rateRangeParameter (0-100)
Countscount, max, bullets, amountRangeParameter (0-99, step:1)
Distanceoffset, range, position, fovRangeParameter
Colorscolor, tint (hex literals)ColorParameter
Flagsenabled, active, visibleBooleanParameter

Finds all *Config.ts files in the target path and parses:

  • Exported const objects: export const GameConfig = { ... }
  • Individual exports: export const PLAYER_HP = 100

When --deep is specified, also scans .ts files for:

  • Numeric assignments: this.speed = 5;
  • Tween durations: .to(0.5, {...})
  • setTimeout values: setTimeout(fn, 3000)

Exclusions: Loop indices, array indices, import statements.

{
"mode": "config",
"configCandidates": [
{
"key": "PlayerMaxHP",
"value": 100,
"type": "number",
"category": "Gameplay-Combat",
"source": "GameConfig.ts:17",
"primitive": "RangeParameter"
}
],
"totalCandidates": 60
}

See references/config-detection.md for the full detection algorithm.

By default, scans nodes under Canvas (and any sibling BG canvas). Use --canvas to target a specific root:

Terminal window
--canvas "Canvas/SomeView/SafeArea"
--canvas "Canvas/BattleUI"
--canvas "Canvas/SomeView,Canvas/EndCard" # comma-separated

Path matching: exact path · suffix match · wildcard Canvas/*/SafeArea.

Step 1: Detect mode from user intent or flags
Step 2: Acquire scene data (MCP batch or scene JSON parse)
Step 3: For each node, detect components via manage_component get_all
Step 4: Build component signature per node
Step 5: Match signature against NodePreset (shape templates)
Step 6: Emit scan report JSON (flat-primitive shape per node)
PriorityComponentNotes
P0Node, UITransformAlways present on UI nodes
P0UIOpacityCommon, emits opacity (no prefix)
P0WidgetEmits widget-prefixed primitives
P1Sprite, Label, Button, CameraCore visual/interaction → prefixed primitives
P1RichText, ProgressBar, Slider, ToggleExtended UI → prefixed primitives
P2Layout, MaskLayout/clipping
SKIPParticleSystem*, Skeleton*, VideoPlayer, WebView, EditBox, ScrollView, PageViewExcluded
{
"scanRoot": "Canvas/SomeView/SafeArea",
"nodes": [
{
"uuid": "...",
"name": "Btn_CTA",
"path": "Canvas/SomeView/EndCard/Btn_CTA",
"components": ["cc.Node", "cc.UITransform", "cc.Sprite", "cc.Label", "cc.Button", "cc.UIOpacity"],
"matchedPreset": "Button",
"primitiveShape": {
"spriteColor": "#FFFFFF",
"spriteFrame": "",
"formattedText": {
"text": "PLAY", "fontColor": "#000000", "fontFamily": "Arial",
"fontSize": 48, "alignment": "center",
"strokeColor": "#000000", "strokeWidth": 0
},
"buttonTransition": "SCALE",
"buttonZoomScale": 1.1,
"buttonNormalColor": "#4CAF50",
"buttonPressedColor": "#388E3C",
"opacity": 255
},
"suggestedCategory": "UILayer-EndCard"
}
],
"codeDetectedGroups": [
{
"name": "Battle UI",
"properties": ["battleRoot", "scopeOverlay", "releaseLabel"],
"sourceFile": "BattleUIController.ts",
"suggestedCategory": "UILayer-Battle"
}
],
"presetsMatched": 3,
"groupsDetected": 1
}

Each node entry becomes ONE top-level ObjectParameter in PlayableConfig whose body is the primitiveShape map. The suggestedCategory becomes the entry’s category. No wrapper classes, no nested ObjectParameter.

When scanning code alongside scene nodes, detect @property({ group: 'X' }) decorators to identify logical UI groupings:

@property({ type: Node, group: 'Battle UI' })
battleRoot: Node = null;
@property({ type: Label, group: 'Battle UI' })
releaseLabel: Label = null;

Each grouped property is still its own top-level ObjectParameter. The group name becomes a shared category suggestion (UILayer-{Group}) so the dashboard sidebar collapses them together. Grouped properties do NOT produce a wrapper composite class — they produce N separate flat entries with one shared category.

  • Batch-check components. Do NOT pre-filter by name.
  • Use manage_component get_all per node when MCP is connected.
  • Read actual scene values for primitive defaults.
  • Never skip UIOpacity or Widget detection.
  • Report nodes with no parameterizable components as “skipped”.
  • Each scanned node → ONE ObjectParameter entry, never a wrapper class instance.
  • Component-prefixed primitives on multi-component nodes (spriteColor, buttonTransition). Single-field components skip the prefix (opacity).
  • A cc.Label reports as ONE formattedText object, not label atoms. Emit "formattedText": { text, fontColor, fontFamily, fontSize, alignment, strokeColor, strokeWidth } read from the scene. Do NOT report labelString/labelColor/labelFontSize/labelOutline*, and do NOT report the label fields with no formatted-text key (shadow, lineHeight, overflow, verticalAlign, bold/italic/underline, wrapText) — reporting them invites the implement step to emit dashboard controls that render as undefined. Parent t1k-cocos-playable-parameter/references/field-coverage.md § cc.Label.
  • references/config-detection.md — Config file parser + magic-number detector
  • references/property-group-detection.md — @property group detection algorithm
  • Parent skill t1k-cocos-playable-parameter/references/scan-strategy.md — Batch Canvas scanning approach
  • Parent skill t1k-cocos-playable-parameter/references/field-coverage.md — Component → prefixed-primitive tables
  • Parent skill t1k-cocos-playable-parameter/references/mcp-assignment.md — MCP health checks and batching

For large Canvas trees (100+ nodes), use the batch scanning script:

Terminal window
node scripts/batch-scan.cjs --canvas "Canvas/SomeView" --output scan-report.json --verbose

Options:

  • --canvas <path> — Root node path to scan
  • --output <file> — Output JSON file (default: stdout)
  • --delay <ms> — Delay between MCP calls (default: 50ms, 0 disables)
  • --mcp-url <url> — Cocos MCP endpoint (default: $COCOS_MCP_URL, else http://127.0.0.1:3000/mcp). The port is per project — resolve it per t1k-cocos-playable-parameter-mcp/references/mcp-endpoint.md and pass it explicitly rather than relying on the 3000 default
  • --verbose — Print progress to stderr

The script handles rate limiting automatically to avoid overwhelming the MCP server.

  • --auto disables review gates on every mode.
  • Scene values become primitive defaults; placeholder values pollute the parameter list.
  • RichText is a separate component from Label in Cocos 3.8+ — do not conflate.
  • Nodes with only Node + UITransform and no other components are non-parameterizable.
  • Batch script requires MCP server running. The script itself resolves --mcp-url → $COCOS_MCP_URL → http://127.0.0.1:3000/mcp — it does not read settings/mcp-server.json, so resolve the project’s real port yourself (t1k-cocos-playable-parameter-mcp/references/mcp-endpoint.md) and pass it in. A refused /health on 3000 means “wrong port” far more often than “editor closed” — never skip MCP verification on that signal alone.
  • --delay must be a non-negative number; an unparseable value exits 2 rather than silently running with no pacing.
  • NodePreset is a shape template, not a wrapper class. A “Button” preset means “this node has Sprite+Label+Button+UIOpacity components → emit these prefixed primitives”, not “wrap them in a ButtonParameter class”.