t1k:cocos:base:architecture-patterns
| Field | Value |
|---|---|
| Module | base |
| Version | 3.3.2 |
| Effort | medium |
| Tools | — |
Keywords: architecture, architecture-audit, code quality, component-split, dip, enabling-extraction, fsm pattern, god-class, patterns, pooling, refactor, signalbus, solid, srp
How to invoke
Section titled “How to invoke”/t1k:cocos:base:architecture-patterns[audit] [<project-root>] [--json <out>]Cocos Architecture Patterns
Section titled “Cocos Architecture Patterns”Structure decisions for Cocos Creator 3.x + TypeScript. Knowledge and grading only — this skill writes no project files.
Two SSOTs, neither of them this file.
- Doctrine — the SOLID table, module boundaries (
GameplayControlleris the only orchestrator; SignalBus is the only cross-module channel), mandatory class roles, codebase flow, the anti-patterns table — lives in the project’sdocs/system-design.md§ “Code Structure Rules (Mandatory)”. Read it there.- The artifact — that doc’s template, its module-table contract, and the
generate/verify/migratescripts — belongs tot1k-cocos-base-system-design.This skill owns only what neither of those does: when a pattern becomes mandatory, the split test, the enabling-extraction concept, and the audit that grades a codebase against its design doc. It reads the doc and the manifest; it never writes either. That separation is deliberate — a skill that both defines the target and grades against it can never report that the target itself is wrong.
Related: t1k-cocos-base-code-conventions = what things are called. Per-pattern skills
(t1k-cocos-playable-object-pool, -fsm, -signalbus) = how each is written.
Not the same thing as
t1k-cocos-base-clean-architecture. That skill is a different, opt-in 4-layer (Domain/Application/Presentation/Infrastructure) style for features whose rules/formulas/state transitions outlive one deliverable — this skill’s Service/Component/Controller doctrine and that skill’s 4-layer doctrine are alternatives, not layers of the same thing. Pick one per module.
Decision Tree
Section titled “Decision Tree”| Intent | Path |
|---|---|
| ”What are the architecture rules?” | Not here — docs/system-design.md § Code Structure Rules |
| ”Which pattern does this code need?” | Mandatory pattern triggers |
| ”Is this class too big / should I split it?” | The split test |
| ”Why can’t I swap in the library package?” | Enabling extraction |
| ”Does the code match the design doc?” | audit |
| ”I have no design doc / it is not on the template” | Not here — t1k-cocos-base-system-design |
| ”Generate or verify the manifest” | Not here — t1k-cocos-base-system-design |
Mandatory pattern triggers
Section titled “Mandatory pattern triggers”The design doc’s anti-patterns table says don’t do X. This table says when you are obliged to introduce Y — the gap the doc leaves. These are completion gates, not suggestions.
| Trigger | Required pattern | Reasoning |
|---|---|---|
instantiate() / new Node() per-spawn during gameplay | Object pooling (ObjectPoolManager) | Allocation + GC reclaim land mid-frame. On a playable’s budget this is the dominant framedrop source, and it only appears under load — so it survives casual testing. |
2+ booleans encoding current activity (_isDead, _isPaused) | FSM (StateManager) | N booleans encode 2^N states, most contradictory. An FSM makes the legal set explicit and illegal states unrepresentable. |
| A module reaching into another module directly | SignalBus | Already banned by the doc’s module-boundary rule; listed here because it is the trigger that most often blocks a package swap later. |
| A class with 3+ change reasons (see below) | Component split (SRP) | Blocks reuse; makes every later change a merge conflict. |
| Global mutable state read from 3+ sites | Service singleton | Ad-hoc module-level state has no defined init order and no teardown hook, so it leaks across scene reloads. |
Counter-trigger — never apply a pattern without its trigger. A pool for a boot-time spawn, an FSM
for one boolean, or an interface with one implementation is pure added complexity. The trigger is
the justification (YAGNI, per rules/coding-guidelines.md §2).
The split test
Section titled “The split test”Line count is a symptom, not the rule. Apply the change-reason test: list the distinct reasons this file would be edited over the next six months. Three or more unrelated reasons → split.
GameplayController (420 lines) reason 1: tuning spawn difficulty -> SpawnDirector reason 2: camera follow feel -> CameraController <- swappable for a package reason 3: score + combo rules -> ScoreService reason 4: win/lose transition -> stays (this IS the orchestrator's job)A 300-line class with one change reason is fine; a 90-line class with four is not. The conventions’
~200-line cap is a prompt to run this test, not the violation itself. Note the orchestrator legitimately
holds win/lose transition — the doc makes GameplayController the only class allowed to call
GameView/CTAService, so do not “clean” that out.
Enabling extraction
Section titled “Enabling extraction”The concept linking architecture work to package reuse. A subsystem that should become a published
@playablelabs/* package sometimes cannot be, because it is fused into a larger class — there is no
seam to cut.
GameplayController.updateCamera() <- camera logic fused into the orchestrator cannot swap @playablelabs/camera-controller | SRP split (the enabling extraction) vCameraController.update() <- seam now exists | reuse swap now possible vimport { CameraController } from '@playablelabs/camera-controller'This is why the refactor plan has two reuse passes:
- Reuse first where the seam already exists — never refactor code a swap is about to delete.
- Architecture second, including enabling extractions.
- Reuse again — the extractions expose seams the first scan could not see.
Flag every enabling extraction in the audit output so pass 3 knows where to look.
Audit mode
Section titled “Audit mode”Architecture conformance is a judgment call, not a regex match — so the audit is AI-driven (read the
code, reason against the design doc, emit findings) rather than a deterministic script, per
rules/ai-driven-design.md. Report-only: it never mutates project source. Its whole output is
the JSON below, persisted to reports/architecture-audit.json — see § “Persisting the audit”.
Inputs: the project’s docs/system-design.md + assets/scripts + the prior-art table from
t1k-cocos-base-knowledge-sweep (Cocos-scoped corpus sweep).
Grades against: the doc’s documented modules/signals/class roles and its § Code Structure
Rules and the trigger table above.
Run early, as a third plan input alongside structure-audit and reuse-scan.
Before you grade: check what you are grading against
Section titled “Before you grade: check what you are grading against”Architecture cannot be graded without a stated target, and a doc that is absent, stale, or unreadable
is not one. This is a blocking finding, not something to work around — and the repair belongs to
t1k-cocos-base-system-design, not here.
| Doc state | What to do |
|---|---|
| Absent, or not on the canonical template | Stop. Report designDocStatus and name t1k-cocos-base-system-design. Do not invent a target. |
| Present but stale | Stop. Grading against a doc known to be wrong produces findings nobody can act on. |
docs/design-manifest.json shows parse: "partial" / "unparseable" | The module map is incomplete — say so in the report rather than treating the modules you can see as the whole project. |
| Present and current | Grade. |
Worked example of the output shape (two findings, one with an enabling extraction):
references/audit-json-example.md.
requiredPattern vocabulary
Section titled “requiredPattern vocabulary”requiredPattern names the pattern the finding obliges, and it is read downstream as data, not
prose — t1k-cocos-playable-modularize-refactor keys a per-pattern editor-state override off it
(EDITOR_STATE_BY_PATTERN). Emit one of these exact spellings:
requiredPattern | Shape of the work | Downstream editor state |
|---|---|---|
object-pooling | in-file edit | open (tier default — verification is an in-editor compile) |
fsm | in-file edit | open |
signalbus | in-file edit | open |
component-split | in-file edit + new sibling .ts, scene re-point | open |
service-singleton | in-file edit | open |
module-split | bulk .ts + .meta move across directories | closed — a live editor is a second writer racing the importer |
The last row is the one that matters: it is the split decision-policy.md rule 7 step 3 consumes,
and it is the only pattern here whose work is a file move rather than an in-file edit. A move-shaped
finding emitted under any other spelling falls through to the tier default and gets planned with the
editor OPEN — the silent scene-corruption path rule 8 exists to avoid (#329). Adding a new
move-shaped pattern means adding it to BOTH this table and EDITOR_STATE_BY_PATTERN.
enablingExtraction: true + exposesSeamFor drive the Tier 2.75 second reuse pass; a finding without
them is ordinary quality work. source keeps provenance so a reader can trace any finding back to the
design doc or the trigger table.
exposesSeamFor must be evidence, not a guess. Naming a package the split would unlock is a
claim about what already exists upstream — so it comes from the t1k-cocos-base-knowledge-sweep
prior-art table, not from intuition about what @playablelabs probably ships. An unbacked
exposesSeamFor sends Tier 2.75 hunting for a package that does not exist, and the extraction that
justified itself on that promise becomes pure added surface.
“The sweep found nothing” and “the sweep never ran” are different facts — never collapse them.
Both leave exposesSeamFor unset, but they say opposite things about the project, so the audit
records which one happened via a top-level priorArt field:
| Sweep state | priorArt | What to emit |
|---|---|---|
| Ran, no hit for this seam | "searched" | Drop exposesSeamFor; the finding stands as ordinary SRP work. A real negative. |
| MCP unavailable / not invoked | "unavailable" | Drop exposesSeamFor and say so in the report. Every enabling-extraction claim in this run is unverified — not disproven. |
Reporting an unavailable sweep as a clean negative manufactures a false “no upstream package exists”,
which is the expensive direction of this error (rules/negative-result-scope.md): a false “already
exists” costs one check and self-corrects, a false “nothing upstream” gets built. When priorArt is
"unavailable", Tier 2.75 has no input it can trust — re-run the sweep before treating that pass as
complete rather than concluding there were no seams to fill.
Persisting the audit
Section titled “Persisting the audit”The audit is a path-addressable artifact, not just a returned summary: Phase 3 of the modularize
runbook gates on the file existing, and plan-refactor.cjs reads it as --arch <path>. Persist it the
way the two sibling plan inputs do (structure-audit step 4, reuse-scan step 6):
Report the findings, then persist the JSON to reports/architecture-audit.json (override with
--json <out>). Writing a report under reports/ is not a source mutation — “report-only” constrains
what the audit may change in assets/scripts, not whether it may leave its own output on disk.
The invoking skill body writes the file. Reasoning about the code may be delegated — the audit is
review-shaped work, and t1k-cocos-reviewer is a reasonable place to do it — but that agent is
deliverable: return with no write tool, so its findings come back as a returned summary and the body
that spawned it persists them.
Never brief a deliverable: return agent to produce a path. With no write tool it falls back to
Bash heredoc appends, which truncate silently at roughly 6 KB per call, and a mid-append read is
indistinguishable from a dead agent — so the briefer cannot tell a partial write from a lost one, and
a mid-task re-brief corrupts the run (#275). If you want an agent to be the writer, route to a
write-capable one and name it; do not widen the reviewer’s read-only grant (#232).
Feed the persisted file to plan-refactor.cjs --arch reports/architecture-audit.json so findings
become ordered Tier 2.5 plan steps rather than ad-hoc edits.
Gotchas
Section titled “Gotchas”- Do not restate architecture doctrine in this skill or in a plan.
docs/system-design.mdis the SSOT. Copying its SOLID/module-boundary/anti-pattern content anywhere else guarantees the two drift, and the copy is the one people read. - Do not author or repair the design doc from here. An absent or stale doc is a blocking finding
you report, and
t1k-cocos-base-system-designfixes. Fixing it inline makes the audit grade its own homework. - Line count is a symptom, not the rule. Splitting a 250-line class with one change reason yields two coupled classes and a worse codebase. Run the change-reason test first.
- Do not refactor code a reuse swap will delete. Check the reuse-scan report before any SOLID work — the most common wasted effort in a legacy pass.
- Applying a pattern without its trigger is also a violation. See the counter-trigger above.
- Splitting an attached
@ccclassComponent breaks scene references silently. Moving behaviour out leaves the old@propertyrefs dangling and the new component unattached.tsccannot catch it — the editor is authoritative. Re-wire and verify before deleting the old class. - A SignalBus conversion that forgets
onDestroyunsubscribe trades a coupling bug for a leak. Pair every subscribe with teardown using a named method — inline lambdas can never be unsubscribed (indexOfreference match). - Enabling extractions are worthless if the second reuse pass never runs. Tier 2.5 without Tier 2.75 leaves you with more files and the same duplicated logic — a net loss.
- The audit is judgment, so it is not byte-reproducible. Two runs may rank findings differently. Treat the JSON as a prioritized worklist, not a regression baseline; do not gate CI on its contents.
References
Section titled “References”docs/system-design.md(per project) — SSOT for architecture doctrine. This skill grades against it.t1k-cocos-base-system-design— owns that doc as an artifact: template, module-table contract, and thegenerate/verify/migratescripts. Everything this skill used to do with files now lives there.t1k-cocos-base-knowledge-sweep— Cocos-scoped prior-art sweep; supplies the audit’s third input and the evidence behindexposesSeamFor. Itst1k-cocos-base-knowledge-sweep/references/doc-search-contract.mdis the Cocos scoping profile for querying the corpus (general SSOT: core’st1k-knowledge-retrieval).rules/knowledge-sweep-cocos.md— auto-loaded rule binding brainstorm/plan/audit to that sweep in Cocos projects.rules/code-quality-cocos.md— the binding obligation this skill serves.t1k-cocos-playable-modularize-refactor— executes these findings as Tier 2.5 / 2.75.t1k-cocos-playable-object-pool,-fsm,-signalbus— how to write each pattern.