Back to skills
SkillHub ClubWrite Technical DocsFull StackTech Writer

theone-cocos-standards

Enforces TheOne Studio Cocos Creator development standards including TypeScript coding patterns, Cocos Creator 3.x architecture (Component system, EventDispatcher), and playable ads optimization guidelines. Triggers when writing, reviewing, or refactoring Cocos TypeScript code, implementing playable ads features, optimizing performance/bundle size, or reviewing code changes.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
54
Hot score
91
Updated
March 20, 2026
Overall rating
C4.5
Composite score
4.5
Best-practice grade
N/A

Install command

npx @skill-hub/cli install the1studio-theone-training-skills-theone-cocos-standards
codingtypescriptcocos-creatorgame-developmentoptimization

Repository

The1Studio/theone-training-skills

Skill path: .claude/skills/theone-cocos-standards

Enforces TheOne Studio Cocos Creator development standards including TypeScript coding patterns, Cocos Creator 3.x architecture (Component system, EventDispatcher), and playable ads optimization guidelines. Triggers when writing, reviewing, or refactoring Cocos TypeScript code, implementing playable ads features, optimizing performance/bundle size, or reviewing code changes.

Open repository

Best for

Primary workflow: Write Technical Docs.

Technical facets: Full Stack, Tech Writer.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: The1Studio.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install theone-cocos-standards into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/The1Studio/theone-training-skills before adding theone-cocos-standards to shared team environments
  • Use theone-cocos-standards for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: theone-cocos-standards
description: Enforces TheOne Studio Cocos Creator development standards including TypeScript coding patterns, Cocos Creator 3.x architecture (Component system, EventDispatcher), and playable ads optimization guidelines. Triggers when writing, reviewing, or refactoring Cocos TypeScript code, implementing playable ads features, optimizing performance/bundle size, or reviewing code changes.
---

# TheOne Studio Cocos Creator Development Standards

⚠️ **Cocos Creator 3.x (TypeScript 4.1+):** All patterns and examples are compatible with Cocos Creator 3.x playable ads development.

## Skill Purpose

This skill enforces TheOne Studio's comprehensive Cocos Creator development standards with **CODE QUALITY FIRST**:

**Priority 1: Code Quality & Hygiene** (MOST IMPORTANT)
- TypeScript strict mode, ESLint configuration, access modifiers (public/private/protected)
- Throw exceptions (never silent errors)
- console.log for development, remove in production builds
- readonly for immutable fields, const for constants
- No inline comments (use descriptive names)
- Proper error handling and type safety

**Priority 2: Modern TypeScript Patterns**
- Array methods (map/filter/reduce) over loops
- Arrow functions, destructuring, spread operators
- Optional chaining, nullish coalescing
- Type guards, utility types (Partial, Required, Readonly)
- Modern TypeScript features

**Priority 3: Cocos Creator Architecture**
- Component-based Entity-Component (EC) system
- Lifecycle methods: onLoad→start→onEnable→update→onDisable→onDestroy
- EventDispatcher pattern for custom events
- Node event system (EventTouch, keyboard events)
- Resource management and pooling for playables

**Priority 4: Playable Ads Performance**
- DrawCall batching (<10 DrawCalls target)
- Sprite atlas configuration (auto-atlas enabled)
- GPU skinning for skeletal animations
- Zero allocations in update() loop
- Bundle size <5MB (texture compression, code minification)

## When This Skill Triggers

- Writing or refactoring Cocos Creator TypeScript code
- Implementing playable ads features
- Working with component lifecycle and events
- Optimizing performance for playable ads
- Reviewing code changes or pull requests
- Setting up playable project architecture
- Reducing bundle size or DrawCall counts

## Quick Reference Guide

### What Do You Need Help With?

| Priority | Task | Reference |
|----------|------|-----------|
| **🔴 PRIORITY 1: Code Quality (Check FIRST)** | | |
| 1 | TypeScript strict mode, ESLint, access modifiers | [Quality & Hygiene](references/language/quality-hygiene.md) ⭐ |
| 1 | Throw exceptions, proper error handling | [Quality & Hygiene](references/language/quality-hygiene.md) ⭐ |
| 1 | console.log (development only), remove in production | [Quality & Hygiene](references/language/quality-hygiene.md) ⭐ |
| 1 | readonly/const, no inline comments, descriptive names | [Quality & Hygiene](references/language/quality-hygiene.md) ⭐ |
| **🟡 PRIORITY 2: Modern TypeScript Patterns** | | |
| 2 | Array methods, arrow functions, destructuring | [Modern TypeScript](references/language/modern-typescript.md) |
| 2 | Optional chaining, nullish coalescing | [Modern TypeScript](references/language/modern-typescript.md) |
| 2 | Type guards, utility types | [Modern TypeScript](references/language/modern-typescript.md) |
| **🟢 PRIORITY 3: Cocos Architecture** | | |
| 3 | Component system, @property decorator | [Component System](references/framework/component-system.md) |
| 3 | Lifecycle methods (onLoad→start→update→onDestroy) | [Component System](references/framework/component-system.md) |
| 3 | EventDispatcher, Node events, cleanup | [Event Patterns](references/framework/event-patterns.md) |
| 3 | Resource loading, pooling, memory management | [Playable Optimization](references/framework/playable-optimization.md) |
| **🔵 PRIORITY 4: Performance & Review** | | |
| 4 | DrawCall batching, sprite atlas, GPU skinning | [Playable Optimization](references/framework/playable-optimization.md) |
| 4 | Update loop optimization, zero allocations | [Performance](references/language/performance.md) |
| 4 | Bundle size reduction (<5MB target) | [Size Optimization](references/framework/size-optimization.md) |
| 4 | Architecture review (components, lifecycle, events) | [Architecture Review](references/review/architecture-review.md) |
| 4 | TypeScript quality review | [Quality Review](references/review/quality-review.md) |
| 4 | Performance review (DrawCalls, allocations) | [Performance Review](references/review/performance-review.md) |

## 🔴 CRITICAL: Code Quality Rules (CHECK FIRST!)

### ⚠️ MANDATORY QUALITY STANDARDS

**ALWAYS enforce these BEFORE writing any code:**

1. **Enable TypeScript strict mode** - "strict": true in tsconfig.json
2. **Use ESLint configuration** - @typescript-eslint rules enabled
3. **Use access modifiers** - public/private/protected on all members
4. **Throw exceptions for errors** - NEVER silent failures or undefined returns
5. **console.log for development only** - Remove all console statements in production builds
6. **Use readonly for immutable fields** - Mark fields that aren't reassigned
7. **Use const for constants** - Constants should be const, not let
8. **No inline comments** - Use descriptive names; code should be self-explanatory
9. **Proper null/undefined handling** - Use optional chaining and nullish coalescing
10. **Type safety** - Avoid `any` type, use proper types and interfaces

**Example: Enforce Quality First**

```typescript
// ✅ EXCELLENT: All quality rules enforced
import { _decorator, Component, Node, EventTouch } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('PlayerController')
export class PlayerController extends Component {
    // 3. Access modifier, 6. readonly for immutable
    @property(Node)
    private readonly targetNode: Node | null = null;

    // 7. const for constants
    private static readonly MAX_HEALTH: number = 100;
    private currentHealth: number = 100;

    // Lifecycle: onLoad → start → onEnable
    protected onLoad(): void {
        // 4. Throw exception for errors
        if (!this.targetNode) {
            throw new Error('PlayerController: targetNode is not assigned');
        }

        // 9. Proper event listener setup
        this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
    }

    protected onDestroy(): void {
        // 9. Always cleanup event listeners
        this.node.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
    }

    private onTouchStart(event: EventTouch): void {
        // 5. console.log only for development (remove in production)
        if (CC_DEBUG) {
            console.log('Touch detected');
        }

        this.takeDamage(10);
    }

    // 8. Descriptive method names (no inline comments needed)
    private takeDamage(amount: number): void {
        this.currentHealth -= amount;

        if (this.currentHealth <= 0) {
            this.handlePlayerDeath();
        }
    }

    private handlePlayerDeath(): void {
        // Death logic
    }
}
```

## ⚠️ Cocos Creator Architecture Rules (AFTER Quality)

### Component System Fundamentals

**Entity-Component (EC) System:**
- Components extend `Component` class
- Use `@ccclass` and `@property` decorators
- Lifecycle: onLoad → start → onEnable → update → lateUpdate → onDisable → onDestroy

**Execution Order:**
1. **onLoad()** - Component initialization, one-time setup
2. **start()** - After all components loaded, can reference other components
3. **onEnable()** - When component/node enabled (can be called multiple times)
4. **update(dt)** - Every frame (use sparingly for playables)
5. **lateUpdate(dt)** - After all update() calls
6. **onDisable()** - When component/node disabled
7. **onDestroy()** - Cleanup, remove listeners, release resources

**Universal Rules:**
- ✅ Initialize in onLoad(), reference other components in start()
- ✅ Register events in onEnable(), unregister in onDisable()
- ✅ Always cleanup listeners in onDestroy()
- ✅ Avoid heavy logic in update() (performance critical for playables)
- ✅ Use readonly for @property fields that shouldn't be reassigned
- ✅ Throw exceptions for missing required references

## Brief Examples

### 🔴 Code Quality First

```typescript
// ✅ EXCELLENT: Quality rules enforced
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('GameManager')
export class GameManager extends Component {
    @property(Node)
    private readonly playerNode: Node | null = null;

    private static readonly MAX_SCORE: number = 1000;
    private currentScore: number = 0;

    protected onLoad(): void {
        // Throw exception for missing required references
        if (!this.playerNode) {
            throw new Error('GameManager: playerNode is required');
        }

        if (CC_DEBUG) {
            console.log('GameManager initialized'); // Development only
        }
    }

    public addScore(points: number): void {
        if (points <= 0) {
            throw new Error('GameManager.addScore: points must be positive');
        }

        this.currentScore = Math.min(
            this.currentScore + points,
            GameManager.MAX_SCORE
        );
    }
}
```

### 🟡 Modern TypeScript Patterns

```typescript
// ✅ GOOD: Array methods instead of loops
const activeEnemies = allEnemies.filter(e => e.isActive);
const enemyPositions = activeEnemies.map(e => e.node.position);

// ✅ GOOD: Optional chaining and nullish coalescing
const playerName = player?.name ?? 'Unknown';

// ✅ GOOD: Destructuring
const { x, y } = this.node.position;

// ✅ GOOD: Arrow functions
this.enemies.forEach(enemy => enemy.takeDamage(10));

// ✅ GOOD: Type guards
function isPlayer(node: Node): node is PlayerNode {
    return node.getComponent(PlayerController) !== null;
}
```

### 🟢 Cocos Creator Component Pattern

```typescript
import { _decorator, Component, Node, EventTouch, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('TouchHandler')
export class TouchHandler extends Component {
    @property(Node)
    private readonly targetNode: Node | null = null;

    private readonly tempVec3: Vec3 = new Vec3(); // Reusable vector

    // 1. onLoad: Initialize component
    protected onLoad(): void {
        if (!this.targetNode) {
            throw new Error('TouchHandler: targetNode is required');
        }
    }

    // 2. start: Reference other components (if needed)
    protected start(): void {
        // Can safely access other components here
    }

    // 3. onEnable: Register event listeners
    protected onEnable(): void {
        this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
        this.node.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
    }

    // 4. onDisable: Unregister event listeners
    protected onDisable(): void {
        this.node.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
        this.node.off(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
    }

    // 5. onDestroy: Final cleanup
    protected onDestroy(): void {
        // Release any additional resources
    }

    private onTouchStart(event: EventTouch): void {
        // Handle touch
    }

    private onTouchMove(event: EventTouch): void {
        // Reuse vector to avoid allocations
        this.targetNode!.getPosition(this.tempVec3);
        this.tempVec3.y += 10;
        this.targetNode!.setPosition(this.tempVec3);
    }
}
```

### 🟢 Event Dispatcher Pattern

```typescript
import { _decorator, Component, EventTarget } from 'cc';
const { ccclass } = _decorator;

// Custom event types
export enum GameEvent {
    SCORE_CHANGED = 'score_changed',
    LEVEL_COMPLETE = 'level_complete',
    PLAYER_DIED = 'player_died',
}

export interface ScoreChangedEvent {
    oldScore: number;
    newScore: number;
}

@ccclass('EventManager')
export class EventManager extends Component {
    private static instance: EventManager | null = null;
    private readonly eventTarget: EventTarget = new EventTarget();

    protected onLoad(): void {
        if (EventManager.instance) {
            throw new Error('EventManager: instance already exists');
        }
        EventManager.instance = this;
    }

    public static emit(event: GameEvent, data?: any): void {
        if (!EventManager.instance) {
            throw new Error('EventManager: instance not initialized');
        }
        EventManager.instance.eventTarget.emit(event, data);
    }

    public static on(event: GameEvent, callback: Function, target?: any): void {
        if (!EventManager.instance) {
            throw new Error('EventManager: instance not initialized');
        }
        EventManager.instance.eventTarget.on(event, callback, target);
    }

    public static off(event: GameEvent, callback: Function, target?: any): void {
        if (!EventManager.instance) {
            throw new Error('EventManager: instance not initialized');
        }
        EventManager.instance.eventTarget.off(event, callback, target);
    }
}

// Usage in component
@ccclass('ScoreDisplay')
export class ScoreDisplay extends Component {
    protected onEnable(): void {
        EventManager.on(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
    }

    protected onDisable(): void {
        EventManager.off(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
    }

    private onScoreChanged(data: ScoreChangedEvent): void {
        console.log(`Score: ${data.oldScore} → ${data.newScore}`);
    }
}
```

### 🔵 Playable Performance Optimization

```typescript
import { _decorator, Component, Node, Sprite, SpriteAtlas } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('OptimizedSpriteManager')
export class OptimizedSpriteManager extends Component {
    // Use sprite atlas for DrawCall batching
    @property(SpriteAtlas)
    private readonly characterAtlas: SpriteAtlas | null = null;

    // Preallocate arrays to avoid allocations in update()
    private readonly tempNodes: Node[] = [];
    private frameCount: number = 0;

    protected onLoad(): void {
        if (!this.characterAtlas) {
            throw new Error('OptimizedSpriteManager: characterAtlas is required');
        }

        // Prewarm sprite frames from atlas
        this.prewarmSpriteFrames();
    }

    private prewarmSpriteFrames(): void {
        // Load all sprites from atlas (batched in single DrawCall)
        const spriteFrame = this.characterAtlas!.getSpriteFrame('character_idle');
        if (!spriteFrame) {
            throw new Error('Sprite frame not found in atlas');
        }
    }

    // Optimize update: avoid allocations, use object pooling
    protected update(dt: number): void {
        // Run expensive operations every N frames instead of every frame
        this.frameCount++;
        if (this.frameCount % 10 === 0) {
            this.updateExpensiveOperation();
        }
    }

    private updateExpensiveOperation(): void {
        // Reuse array instead of creating new one
        this.tempNodes.length = 0;

        // Batch operations to reduce DrawCalls
    }
}
```

## Code Review Checklist

### Quick Validation (before committing)

**🔴 Code Quality (CHECK FIRST):**
- [ ] TypeScript strict mode enabled in tsconfig.json
- [ ] ESLint rules passing (no errors)
- [ ] All access modifiers correct (public/private/protected)
- [ ] Exceptions thrown for errors (no silent failures)
- [ ] console.log removed or wrapped in CC_DEBUG
- [ ] readonly used for non-reassigned fields
- [ ] const used for constants
- [ ] No inline comments (self-explanatory code)
- [ ] Proper null/undefined handling
- [ ] No `any` types (use proper types)

**🟡 Modern TypeScript Patterns:**
- [ ] Array methods used instead of manual loops
- [ ] Arrow functions for callbacks
- [ ] Optional chaining (?.) for safe property access
- [ ] Nullish coalescing (??) for default values
- [ ] Destructuring for cleaner code
- [ ] Type guards for type narrowing

**🟢 Cocos Creator Architecture:**
- [ ] Component lifecycle methods in correct order
- [ ] onLoad() for initialization, start() for references
- [ ] Event listeners registered in onEnable()
- [ ] Event listeners unregistered in onDisable()
- [ ] Resources released in onDestroy()
- [ ] @property decorator used correctly
- [ ] Required references validated (throw if null)

**🔵 Playable Performance:**
- [ ] No allocations in update() loop
- [ ] Sprite atlas used for DrawCall batching
- [ ] GPU skinning enabled for skeletal animations
- [ ] Expensive operations throttled (not every frame)
- [ ] Object pooling for frequently created objects
- [ ] Texture compression enabled
- [ ] Bundle size <5MB target
- [ ] DrawCall count <10 target

## Common Mistakes to Avoid

### ❌ DON'T:
1. **Ignore TypeScript strict mode** → Enable "strict": true
2. **Silent error handling** → Throw exceptions for errors
3. **Leave console.log in production** → Remove or wrap in CC_DEBUG
4. **Skip access modifiers** → Use public/private/protected
5. **Use `any` type** → Define proper types and interfaces
6. **Add inline comments** → Use descriptive names instead
7. **Skip event cleanup** → Always unregister in onDisable/onDestroy
8. **Allocate in update()** → Preallocate and reuse objects
9. **Forget sprite atlas** → Use atlas for DrawCall batching
10. **Heavy logic in update()** → Throttle expensive operations
11. **Skip null checks** → Validate required references in onLoad
12. **Mutable @property fields** → Use readonly when appropriate
13. **Manual loops over arrays** → Use map/filter/reduce
14. **Ignore bundle size** → Monitor and optimize (<5MB target)

### ✅ DO:
1. **Enable TypeScript strict mode** ("strict": true)
2. **Throw exceptions for errors** (never silent failures)
3. **Use console.log for development only** (remove in production)
4. **Use access modifiers** (public/private/protected)
5. **Define proper types** (avoid `any`)
6. **Use descriptive names** (no inline comments)
7. **Always cleanup events** (onDisable/onDestroy)
8. **Preallocate objects** (reuse in update())
9. **Use sprite atlas** (DrawCall batching)
10. **Throttle expensive operations** (not every frame)
11. **Validate required references** (throw in onLoad if null)
12. **Use readonly for @property** (when appropriate)
13. **Use array methods** (map/filter/reduce)
14. **Monitor bundle size** (<5MB target for playables)

## Review Severity Levels

### 🔴 Critical (Must Fix)
- **TypeScript strict mode disabled** - Must enable "strict": true
- **Silent error handling** - Must throw exceptions for errors
- **console.log in production code** - Remove or wrap in CC_DEBUG
- **Missing access modifiers** - All members must have modifiers
- **Using `any` type without justification** - Define proper types
- **Inline comments instead of descriptive names** - Rename and remove comments
- **Event listeners not cleaned up** - Memory leak, must unregister
- **Missing required reference validation** - Must throw in onLoad if null
- **Allocations in update() loop** - Performance critical, must preallocate
- **No sprite atlas for multiple sprites** - DrawCall explosion, must use atlas
- **Bundle size >5MB** - Exceeds playable limit, must optimize

### 🟡 Important (Should Fix)
- **Missing readonly on @property fields** - Should be readonly when not reassigned
- **Missing const for constants** - Should use const instead of let
- **Manual loops instead of array methods** - Should use map/filter/reduce
- **Missing optional chaining** - Should use ?. for safe access
- **Missing nullish coalescing** - Should use ?? for default values
- **Heavy logic in update()** - Should throttle expensive operations
- **No object pooling for frequent allocations** - Should implement pooling
- **Texture compression not enabled** - Should enable for smaller bundle
- **DrawCall count >10** - Should optimize batching

### 🟢 Nice to Have (Suggestion)
- Could use arrow function for callback
- Could destructure for cleaner code
- Could use type guard for type safety
- Could improve naming for clarity
- Could add interface for better typing
- Could optimize algorithm for better performance

## Detailed References

### TypeScript Language Standards
- [Quality & Hygiene](references/language/quality-hygiene.md) - Strict mode, ESLint, access modifiers, error handling
- [Modern TypeScript](references/language/modern-typescript.md) - Array methods, optional chaining, type guards, utility types
- [Performance](references/language/performance.md) - Update loop optimization, zero allocations, caching

### Cocos Creator Framework
- [Component System](references/framework/component-system.md) - EC system, lifecycle methods, @property decorator
- [Event Patterns](references/framework/event-patterns.md) - EventDispatcher, Node events, subscription cleanup
- [Playable Optimization](references/framework/playable-optimization.md) - DrawCall batching, sprite atlas, GPU skinning, resource pooling
- [Size Optimization](references/framework/size-optimization.md) - Bundle size reduction, texture compression, build optimization

### Code Review
- [Architecture Review](references/review/architecture-review.md) - Component violations, lifecycle errors, event leaks
- [Quality Review](references/review/quality-review.md) - TypeScript quality issues, access modifiers, error handling
- [Performance Review](references/review/performance-review.md) - Playable-specific performance problems, DrawCalls, allocations

## Summary

This skill provides comprehensive Cocos Creator development standards for TheOne Studio's playable ads team:
- **TypeScript Excellence**: Strict mode, modern patterns, type safety
- **Cocos Architecture**: Component lifecycle, event patterns, resource management
- **Playable Performance**: DrawCall batching, GPU skinning, <5MB bundles
- **Code Quality**: Enforced quality, hygiene, and performance rules

Use the Quick Reference Guide above to navigate to the specific pattern you need.


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/language/quality-hygiene.md

```markdown
# TypeScript Quality & Code Hygiene

## Enable TypeScript Strict Mode

```typescript
// ✅ GOOD: Enable strict mode in tsconfig.json
{
    "compilerOptions": {
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictPropertyInitialization": true,
        "noImplicitThis": true,
        "alwaysStrict": true
    }
}

// Declare nullable explicitly
public playerName: string | null = null; // Can be null
public requiredName: string = ''; // Never null

// ❌ BAD: Ignoring nullability
public playerName: string; // Uninitialized, can be undefined
```

## Use Access Modifiers (public/private/protected)

```typescript
// ✅ GOOD: Explicit access modifiers
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('GameService')
export class GameService extends Component {
    // Private implementation details
    private readonly playerNodes: Node[] = [];
    private currentLevel: number = 1;

    // Protected for subclass access
    protected readonly maxHealth: number = 100;

    // Public API only when necessary
    public getCurrentLevel(): number {
        return this.currentLevel;
    }

    // Private helper methods
    private loadGameData(): void {
        // Implementation
    }
}

// ❌ BAD: No access modifiers (implicitly public)
@ccclass('GameService')
export class GameService extends Component {
    playerNodes: Node[] = []; // Implicitly public
    currentLevel: number = 1; // Implicitly public
}
```

## Enable ESLint with TypeScript Support

```json
// ✅ GOOD: .eslintrc.json configuration
{
    "parser": "@typescript-eslint/parser",
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended"
    ],
    "plugins": ["@typescript-eslint"],
    "rules": {
        "@typescript-eslint/explicit-function-return-type": "error",
        "@typescript-eslint/no-explicit-any": "error",
        "@typescript-eslint/no-unused-vars": "error",
        "@typescript-eslint/explicit-member-accessibility": "error"
    }
}
```

## Throw Exceptions for Errors

**Critical Rule**: Throw exceptions instead of silent failures or returning undefined.

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('PlayerService')
export class PlayerService extends Component {
    @property(Node)
    private readonly playerNode: Node | null = null;

    // ✅ EXCELLENT: Throw exception for errors
    protected onLoad(): void {
        if (!this.playerNode) {
            throw new Error('PlayerService: playerNode is required');
        }
    }

    public getPlayer(id: string): Player {
        const player = this.players.get(id);
        if (!player) {
            // Throw exception, don't return undefined
            throw new Error(`Player not found: ${id}`);
        }
        return player;
    }

    public loadLevel(levelId: number): void {
        if (levelId < 1 || levelId > 100) {
            throw new RangeError(`Invalid level ID: ${levelId}. Must be 1-100.`);
        }

        const levelData = this.loadLevelData(levelId);
        if (!levelData) {
            throw new Error(`Failed to load level data for level ${levelId}`);
        }

        this.initializeLevel(levelData);
    }
}

// ❌ WRONG: Silent failure
public getPlayer(id: string): Player | undefined {
    const player = this.players.get(id);
    // Returning undefined - caller doesn't know why it failed
    return player;
}

// ❌ WRONG: Logging error instead of throwing
public loadLevel(levelId: number): void {
    if (levelId < 1) {
        console.error('Invalid level ID'); // Don't just log
        return; // Silent failure
    }
}
```

## Logging: console.log for Development Only

**Logging Guidelines:**
- **console.log**: Use ONLY for development debugging
- **Remove in production**: Wrap in `CC_DEBUG` or remove entirely
- **Performance impact**: console.log can slow down playable ads
- **Bundle size**: Logging strings increase bundle size

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('GameManager')
export class GameManager extends Component {
    private currentScore: number = 0;

    // ✅ EXCELLENT: Conditional logging for development
    protected onLoad(): void {
        if (CC_DEBUG) {
            console.log('GameManager initialized');
        }
    }

    public addScore(points: number): void {
        this.currentScore += points;

        // ✅ GOOD: Development-only debug logging
        if (CC_DEBUG) {
            console.log(`Score updated: ${this.currentScore}`);
        }
    }

    private loadGameData(): void {
        try {
            const data = this.fetchData();
            this.processData(data);
        } catch (error) {
            // ✅ GOOD: Log errors in development
            if (CC_DEBUG) {
                console.error('Failed to load game data:', error);
            }
            // Always throw for caller to handle
            throw error;
        }
    }
}

// ❌ WRONG: Unconditional console.log in production
public addScore(points: number): void {
    console.log(`Adding ${points} points`); // Will be in production build
    this.currentScore += points;
}

// ❌ WRONG: Verbose logging everywhere
public update(dt: number): void {
    console.log('Update called'); // Called every frame!
    console.log(`Delta time: ${dt}`); // Performance impact
}

// ✅ BETTER: Remove logs in production or use build-time removal
// Configure build process to strip console.log in production builds
```

**Production Build Configuration:**

```javascript
// Build configuration to remove console.log in production
// rollup.config.js or webpack.config.js
export default {
    plugins: [
        // Remove console statements in production
        terser({
            compress: {
                drop_console: true, // Remove all console.* calls
                pure_funcs: ['console.log', 'console.debug'], // Remove specific calls
            }
        })
    ]
};
```

## Use readonly for Immutable Fields

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('PlayerController')
export class PlayerController extends Component {
    // ✅ GOOD: readonly for @property fields that aren't reassigned
    @property(Node)
    private readonly targetNode: Node | null = null;

    @property(Number)
    private readonly moveSpeed: number = 100;

    // ✅ GOOD: readonly for injected dependencies
    private readonly eventManager: EventManager;

    // Regular mutable field
    private currentHealth: number = 100;

    constructor(eventManager: EventManager) {
        super();
        this.eventManager = eventManager;
    }

    // ❌ WRONG: Can't reassign readonly field
    public setTarget(node: Node): void {
        // this.targetNode = node; // Error: Cannot assign to 'targetNode' because it is a read-only property
    }
}

// ❌ BAD: Mutable when shouldn't be
@ccclass('GameConfig')
export class GameConfig extends Component {
    @property(Number)
    private maxHealth: number = 100; // Should be readonly
}
```

## Use const for Constants

```typescript
// ✅ GOOD: const for constants
const MAX_PLAYERS = 4;
const DEFAULT_PLAYER_NAME = 'Player';
const GAME_VERSION = '1.0.0';

// ✅ GOOD: Static readonly for class constants
@ccclass('GameRules')
export class GameRules extends Component {
    private static readonly MAX_HEALTH: number = 100;
    private static readonly MIN_LEVEL: number = 1;
    private static readonly MAX_LEVEL: number = 50;

    public static isValidLevel(level: number): boolean {
        return level >= GameRules.MIN_LEVEL && level <= GameRules.MAX_LEVEL;
    }
}

// ✅ GOOD: Enum for related constants
export enum GameState {
    LOADING = 'loading',
    PLAYING = 'playing',
    PAUSED = 'paused',
    GAME_OVER = 'game_over',
}

// ❌ BAD: let for constants
let maxPlayers = 4; // Should be const
let defaultPlayerName = 'Player'; // Should be const

// ❌ BAD: Magic numbers without constants
public checkHealth(): boolean {
    return this.health > 0 && this.health <= 100; // What is 100?
}

// ✅ BETTER: Named constant
private static readonly MAX_HEALTH: number = 100;

public checkHealth(): boolean {
    return this.health > 0 && this.health <= GameRules.MAX_HEALTH;
}
```

## No Inline Comments (Use Descriptive Names)

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

// ✅ EXCELLENT: Self-explanatory code, no inline comments
@ccclass('PlayerController')
export class PlayerController extends Component {
    @property(Node)
    private readonly healthBarNode: Node | null = null;

    private currentHealth: number = 100;
    private static readonly MAX_HEALTH: number = 100;
    private static readonly CRITICAL_HEALTH_THRESHOLD: number = 20;

    public takeDamage(amount: number): void {
        this.currentHealth = Math.max(0, this.currentHealth - amount);
        this.updateHealthBar();

        if (this.isHealthCritical()) {
            this.triggerLowHealthWarning();
        }

        if (this.isDead()) {
            this.handlePlayerDeath();
        }
    }

    private isHealthCritical(): boolean {
        return this.currentHealth <= PlayerController.CRITICAL_HEALTH_THRESHOLD;
    }

    private isDead(): boolean {
        return this.currentHealth === 0;
    }

    private triggerLowHealthWarning(): void {
        // Implementation
    }

    private handlePlayerDeath(): void {
        // Implementation
    }

    private updateHealthBar(): void {
        if (!this.healthBarNode) return;

        const healthPercentage = this.currentHealth / PlayerController.MAX_HEALTH;
        this.healthBarNode.scale = new Vec3(healthPercentage, 1, 1);
    }
}

// ❌ BAD: Inline comments explaining unclear code
@ccclass('PlayerController')
export class PlayerController extends Component {
    private h: number = 100; // health

    public td(a: number): void { // take damage
        this.h = Math.max(0, this.h - a); // subtract damage but don't go below 0
        this.uh(); // update health bar

        if (this.h <= 20) { // if health is critical
            this.tlhw(); // trigger low health warning
        }

        if (this.h === 0) { // if dead
            this.hpd(); // handle player death
        }
    }
}

// ❌ BAD: Comments explaining what code does (should be obvious)
public addScore(points: number): void {
    // Add points to current score
    this.currentScore += points;

    // Check if score exceeds maximum
    if (this.currentScore > MAX_SCORE) {
        // Set score to maximum
        this.currentScore = MAX_SCORE;
    }
}

// ✅ BETTER: Descriptive names make comments unnecessary
public addScore(points: number): void {
    this.currentScore += points;
    this.clampScoreToMaximum();
}

private clampScoreToMaximum(): void {
    this.currentScore = Math.min(this.currentScore, MAX_SCORE);
}
```

**When Comments ARE Appropriate:**

```typescript
// ✅ GOOD: Documenting WHY, not WHAT
/**
 * Calculates damage using quadratic formula to create smooth damage curve.
 * Linear damage felt too harsh for new players during playtesting.
 */
private calculateDamage(baseAmount: number, level: number): number {
    return baseAmount * Math.pow(level, 0.8);
}

// ✅ GOOD: Documenting complex algorithms
/**
 * A* pathfinding algorithm implementation.
 * Uses Manhattan distance heuristic for grid-based movement.
 * @see https://en.wikipedia.org/wiki/A*_search_algorithm
 */
private findPath(start: Vec2, end: Vec2): Vec2[] {
    // Implementation
}

// ✅ GOOD: Documenting workarounds
/**
 * WORKAROUND: Cocos Creator 3.8.x has a bug where sprite atlas
 * frames aren't properly loaded on first access. Accessing once
 * in onLoad() ensures they're cached for later use.
 * @see https://github.com/cocos/cocos-engine/issues/12345
 */
protected onLoad(): void {
    this.atlas?.getSpriteFrame('dummy');
}
```

## Proper Null/Undefined Handling

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('PlayerManager')
export class PlayerManager extends Component {
    @property(Node)
    private readonly playerNode: Node | null = null;

    // ✅ EXCELLENT: Explicit validation and error handling
    protected onLoad(): void {
        if (!this.playerNode) {
            throw new Error('PlayerManager: playerNode is required');
        }
    }

    // ✅ GOOD: Optional chaining for safe access
    public getPlayerName(): string {
        return this.playerNode?.name ?? 'Unknown';
    }

    // ✅ GOOD: Nullish coalescing for default values
    public getPlayerHealth(): number {
        return this.playerNode?.getComponent(PlayerController)?.health ?? 0;
    }

    // ✅ GOOD: Type guard for type safety
    private isValidPlayer(node: Node | null): node is Node {
        return node !== null && node.getComponent(PlayerController) !== null;
    }

    public updatePlayer(): void {
        if (this.isValidPlayer(this.playerNode)) {
            // TypeScript knows playerNode is Node (not null)
            const controller = this.playerNode.getComponent(PlayerController)!;
            controller.update();
        }
    }
}

// ❌ BAD: No null checks
public updatePlayer(): void {
    this.playerNode.position = new Vec3(0, 0, 0); // Can crash if null
}

// ❌ BAD: Unsafe type assertions
public getController(): PlayerController {
    return this.playerNode!.getComponent(PlayerController)!; // Unsafe!
}
```

## Avoid `any` Type

```typescript
// ✅ GOOD: Proper types and interfaces
interface PlayerData {
    id: string;
    name: string;
    level: number;
    health: number;
}

@ccclass('PlayerService')
export class PlayerService extends Component {
    private readonly players: Map<string, PlayerData> = new Map();

    public addPlayer(data: PlayerData): void {
        this.players.set(data.id, data);
    }

    public getPlayer(id: string): PlayerData | undefined {
        return this.players.get(id);
    }
}

// ❌ BAD: Using any type
@ccclass('PlayerService')
export class PlayerService extends Component {
    private players: any = {}; // Type safety lost

    public addPlayer(data: any): void { // No type checking
        this.players[data.id] = data;
    }

    public getPlayer(id: string): any { // Caller doesn't know structure
        return this.players[id];
    }
}

// ✅ GOOD: Use generics instead of any
class DataStore<T> {
    private data: Map<string, T> = new Map();

    public set(key: string, value: T): void {
        this.data.set(key, value);
    }

    public get(key: string): T | undefined {
        return this.data.get(key);
    }
}

// ✅ GOOD: Use unknown for truly unknown types (safer than any)
function parseJSON(json: string): unknown {
    return JSON.parse(json);
}

// Then validate and narrow the type
const result = parseJSON('{"name": "Player"}');
if (isPlayerData(result)) {
    // result is now typed as PlayerData
    console.log(result.name);
}

function isPlayerData(obj: unknown): obj is PlayerData {
    return (
        typeof obj === 'object' &&
        obj !== null &&
        'id' in obj &&
        'name' in obj &&
        'level' in obj &&
        'health' in obj
    );
}
```

## Summary: Quality Checklist

**Before committing code, verify:**

- [ ] TypeScript strict mode enabled in tsconfig.json
- [ ] ESLint configuration active and passing
- [ ] All class members have access modifiers (public/private/protected)
- [ ] Exceptions thrown for errors (no silent failures)
- [ ] console.log removed or wrapped in CC_DEBUG
- [ ] readonly used for non-reassigned fields
- [ ] const used for constants (not let)
- [ ] No inline comments (code is self-explanatory)
- [ ] Optional chaining (?.) for safe property access
- [ ] Nullish coalescing (??) for default values
- [ ] No `any` types without justification
- [ ] Required references validated in onLoad()

**Quality is the foundation of all other patterns. Get this right first.**

```

### references/language/modern-typescript.md

```markdown
# Modern TypeScript Patterns

## Array Methods Over Loops

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass } = _decorator;

interface Enemy {
    node: Node;
    isActive: boolean;
    health: number;
    damage: number;
}

@ccclass('EnemyManager')
export class EnemyManager extends Component {
    private readonly enemies: Enemy[] = [];

    // ✅ EXCELLENT: Array methods for filtering
    public getActiveEnemies(): Enemy[] {
        return this.enemies.filter(enemy => enemy.isActive);
    }

    // ✅ EXCELLENT: Array methods for mapping
    public getEnemyPositions(): Vec3[] {
        return this.enemies.map(enemy => enemy.node.position.clone());
    }

    // ✅ EXCELLENT: Array methods for reduction
    public getTotalDamage(): number {
        return this.enemies.reduce((total, enemy) => total + enemy.damage, 0);
    }

    // ✅ EXCELLENT: Chaining array methods
    public getActiveEnemyDamage(): number {
        return this.enemies
            .filter(enemy => enemy.isActive)
            .reduce((total, enemy) => total + enemy.damage, 0);
    }

    // ✅ EXCELLENT: find instead of manual loop
    public findEnemyById(id: string): Enemy | undefined {
        return this.enemies.find(enemy => enemy.node.uuid === id);
    }

    // ✅ EXCELLENT: some/every for existence checks
    public hasActiveEnemies(): boolean {
        return this.enemies.some(enemy => enemy.isActive);
    }

    public areAllEnemiesDead(): boolean {
        return this.enemies.every(enemy => enemy.health <= 0);
    }
}

// ❌ BAD: Manual loops
public getActiveEnemies(): Enemy[] {
    const active: Enemy[] = [];
    for (let i = 0; i < this.enemies.length; i++) {
        if (this.enemies[i].isActive) {
            active.push(this.enemies[i]);
        }
    }
    return active;
}

// ❌ BAD: Manual accumulation
public getTotalDamage(): number {
    let total = 0;
    for (const enemy of this.enemies) {
        total += enemy.damage;
    }
    return total;
}
```

## Arrow Functions and Callbacks

```typescript
import { _decorator, Component, Node, EventTouch } from 'cc';
const { ccclass } = _decorator;

@ccclass('InputHandler')
export class InputHandler extends Component {
    private readonly buttons: Node[] = [];

    // ✅ EXCELLENT: Arrow functions for callbacks
    protected onEnable(): void {
        this.buttons.forEach(button => {
            button.on(Node.EventType.TOUCH_START, this.onButtonClick, this);
        });
    }

    protected onDisable(): void {
        this.buttons.forEach(button => {
            button.off(Node.EventType.TOUCH_START, this.onButtonClick, this);
        });
    }

    // ✅ GOOD: Arrow function preserves this context
    private readonly onButtonClick = (event: EventTouch): void => {
        const button = event.target as Node;
        this.handleButtonClick(button);
    };

    // ✅ GOOD: Arrow function for event handling
    private setupAsyncOperation(): void {
        setTimeout(() => {
            this.processData();
        }, 1000);
    }

    // ✅ GOOD: Arrow function in Promise chains
    private async loadData(): Promise<void> {
        fetch('data.json')
            .then(response => response.json())
            .then(data => this.processData(data))
            .catch(error => this.handleError(error));
    }
}

// ❌ BAD: Function expression loses this context
protected onEnable(): void {
    this.buttons.forEach(function(button) {
        // 'this' is undefined or wrong context
        button.on(Node.EventType.TOUCH_START, this.onButtonClick, this);
    });
}

// ❌ BAD: Verbose function syntax
private setupAsyncOperation(): void {
    const self = this;
    setTimeout(function() {
        self.processData();
    }, 1000);
}
```

## Destructuring

```typescript
import { _decorator, Component, Node, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

interface PlayerData {
    id: string;
    name: string;
    level: number;
    health: number;
    position: { x: number; y: number; z: number };
}

@ccclass('PlayerController')
export class PlayerController extends Component {
    // ✅ EXCELLENT: Destructuring in parameters
    public updatePlayer({ id, name, level, health, position }: PlayerData): void {
        console.log(`Updating ${name} (${id}) at level ${level}`);

        // ✅ EXCELLENT: Nested destructuring
        const { x, y, z } = position;
        this.node.setPosition(x, y, z);
    }

    // ✅ EXCELLENT: Destructuring with defaults
    public loadConfig({ speed = 100, jumpHeight = 50, maxHealth = 100 } = {}): void {
        this.speed = speed;
        this.jumpHeight = jumpHeight;
        this.maxHealth = maxHealth;
    }

    // ✅ EXCELLENT: Array destructuring
    public getPlayerPosition(): Vec3 {
        const [x, y, z] = [this.node.position.x, this.node.position.y, this.node.position.z];
        return new Vec3(x, y, z);
    }

    // ✅ EXCELLENT: Rest operator with destructuring
    public handleInput({ type, ...eventData }: InputEvent): void {
        switch (type) {
            case 'touch':
                this.handleTouch(eventData);
                break;
            case 'key':
                this.handleKey(eventData);
                break;
        }
    }
}

// ❌ BAD: No destructuring
public updatePlayer(playerData: PlayerData): void {
    console.log(`Updating ${playerData.name} (${playerData.id}) at level ${playerData.level}`);
    this.node.setPosition(playerData.position.x, playerData.position.y, playerData.position.z);
}

// ❌ BAD: Verbose property access
public loadConfig(config: Config): void {
    this.speed = config.speed !== undefined ? config.speed : 100;
    this.jumpHeight = config.jumpHeight !== undefined ? config.jumpHeight : 50;
    this.maxHealth = config.maxHealth !== undefined ? config.maxHealth : 100;
}
```

## Spread Operator

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

interface GameConfig {
    playerName: string;
    difficulty: string;
    soundEnabled: boolean;
}

@ccclass('GameManager')
export class GameManager extends Component {
    private readonly defaultConfig: GameConfig = {
        playerName: 'Player',
        difficulty: 'normal',
        soundEnabled: true,
    };

    // ✅ EXCELLENT: Spread for object merging
    public createConfig(overrides: Partial<GameConfig>): GameConfig {
        return { ...this.defaultConfig, ...overrides };
    }

    // ✅ EXCELLENT: Spread for array concatenation
    private readonly baseEnemies: string[] = ['goblin', 'orc'];
    private readonly bossEnemies: string[] = ['dragon', 'demon'];

    public getAllEnemies(): string[] {
        return [...this.baseEnemies, ...this.bossEnemies];
    }

    // ✅ EXCELLENT: Spread for array cloning
    public cloneEnemyList(): string[] {
        return [...this.baseEnemies];
    }

    // ✅ EXCELLENT: Spread in function calls
    public calculateMaxValue(...values: number[]): number {
        return Math.max(...values);
    }

    // ✅ EXCELLENT: Spread for immutable updates
    public addEnemy(enemy: string): void {
        this.baseEnemies = [...this.baseEnemies, enemy];
    }
}

// ❌ BAD: Manual merging
public createConfig(overrides: Partial<GameConfig>): GameConfig {
    const config: GameConfig = {
        playerName: overrides.playerName ?? this.defaultConfig.playerName,
        difficulty: overrides.difficulty ?? this.defaultConfig.difficulty,
        soundEnabled: overrides.soundEnabled ?? this.defaultConfig.soundEnabled,
    };
    return config;
}

// ❌ BAD: Manual concatenation
public getAllEnemies(): string[] {
    const enemies: string[] = [];
    for (const enemy of this.baseEnemies) {
        enemies.push(enemy);
    }
    for (const enemy of this.bossEnemies) {
        enemies.push(enemy);
    }
    return enemies;
}
```

## Optional Chaining (?.)

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

interface Player {
    name: string;
    stats?: {
        health?: number;
        level?: number;
    };
    inventory?: {
        items?: Item[];
    };
}

@ccclass('PlayerManager')
export class PlayerManager extends Component {
    @property(Node)
    private readonly playerNode: Node | null = null;

    // ✅ EXCELLENT: Optional chaining for safe access
    public getPlayerName(): string | undefined {
        return this.playerNode?.name;
    }

    // ✅ EXCELLENT: Deep optional chaining
    public getPlayerHealth(player: Player): number | undefined {
        return player?.stats?.health;
    }

    // ✅ EXCELLENT: Optional chaining with arrays
    public getFirstItem(player: Player): Item | undefined {
        return player?.inventory?.items?.[0];
    }

    // ✅ EXCELLENT: Optional chaining with methods
    public getComponentName(): string | undefined {
        return this.playerNode?.getComponent(PlayerController)?.getName?.();
    }

    // ✅ EXCELLENT: Combining with nullish coalescing
    public getDisplayName(): string {
        return this.playerNode?.name ?? 'Unknown Player';
    }
}

// ❌ BAD: Manual null checking
public getPlayerName(): string | undefined {
    if (this.playerNode !== null && this.playerNode !== undefined) {
        return this.playerNode.name;
    }
    return undefined;
}

// ❌ BAD: Nested null checks
public getPlayerHealth(player: Player): number | undefined {
    if (player) {
        if (player.stats) {
            if (player.stats.health !== undefined) {
                return player.stats.health;
            }
        }
    }
    return undefined;
}
```

## Nullish Coalescing (??)

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

interface GameConfig {
    playerName?: string;
    maxHealth?: number;
    soundVolume?: number;
    enableTutorial?: boolean;
}

@ccclass('ConfigManager')
export class ConfigManager extends Component {
    // ✅ EXCELLENT: Nullish coalescing for defaults
    public loadConfig(config: GameConfig): void {
        const playerName = config.playerName ?? 'Player';
        const maxHealth = config.maxHealth ?? 100;
        const soundVolume = config.soundVolume ?? 0.5;
        const enableTutorial = config.enableTutorial ?? true;

        console.log({ playerName, maxHealth, soundVolume, enableTutorial });
    }

    // ✅ EXCELLENT: Nullish coalescing preserves falsy values
    public getVolume(volume?: number): number {
        // Returns 0 if volume is 0 (not using || which would return 1)
        return volume ?? 1;
    }

    // ✅ EXCELLENT: Chaining nullish coalescing
    public getPlayerName(primaryName?: string, secondaryName?: string): string {
        return primaryName ?? secondaryName ?? 'Unknown';
    }

    // ✅ EXCELLENT: Nullish coalescing with optional chaining
    public getHealthDisplay(player?: Player): string {
        const health = player?.stats?.health ?? 0;
        return `Health: ${health}`;
    }
}

// ❌ BAD: Using || operator (treats 0, '', false as null)
public getVolume(volume?: number): number {
    return volume || 1; // Returns 1 even if volume is 0
}

// ❌ BAD: Manual null/undefined checks
public loadConfig(config: GameConfig): void {
    const playerName = config.playerName !== null && config.playerName !== undefined
        ? config.playerName
        : 'Player';
}

// ❌ BAD: Verbose ternary
public getPlayerName(name?: string): string {
    return name !== undefined && name !== null ? name : 'Unknown';
}
```

## Type Guards

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass } = _decorator;

// ✅ EXCELLENT: Type guard for interface
interface Player {
    type: 'player';
    health: number;
    level: number;
}

interface Enemy {
    type: 'enemy';
    health: number;
    damage: number;
}

type Entity = Player | Enemy;

function isPlayer(entity: Entity): entity is Player {
    return entity.type === 'player';
}

function isEnemy(entity: Entity): entity is Enemy {
    return entity.type === 'enemy';
}

@ccclass('CombatManager')
export class CombatManager extends Component {
    public handleEntity(entity: Entity): void {
        if (isPlayer(entity)) {
            // TypeScript knows entity is Player
            console.log(`Player level: ${entity.level}`);
        } else if (isEnemy(entity)) {
            // TypeScript knows entity is Enemy
            console.log(`Enemy damage: ${entity.damage}`);
        }
    }

    // ✅ EXCELLENT: Type guard for null/undefined
    private isValidNode(node: Node | null | undefined): node is Node {
        return node !== null && node !== undefined;
    }

    public processNode(node: Node | null): void {
        if (this.isValidNode(node)) {
            // TypeScript knows node is Node (not null)
            node.setPosition(0, 0, 0);
        }
    }

    // ✅ EXCELLENT: Type guard for component
    private hasPlayerController(node: Node): node is Node & { getComponent(PlayerController): PlayerController } {
        return node.getComponent(PlayerController) !== null;
    }

    public updatePlayer(node: Node): void {
        if (this.hasPlayerController(node)) {
            // TypeScript knows component exists
            const controller = node.getComponent(PlayerController)!;
            controller.update();
        }
    }
}

// ❌ BAD: No type guards, type assertions everywhere
public handleEntity(entity: Entity): void {
    if (entity.type === 'player') {
        console.log(`Player level: ${(entity as Player).level}`); // Type assertion
    } else {
        console.log(`Enemy damage: ${(entity as Enemy).damage}`); // Type assertion
    }
}
```

## Utility Types

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

interface GameConfig {
    playerName: string;
    maxHealth: number;
    difficulty: string;
    soundEnabled: boolean;
}

@ccclass('ConfigManager')
export class ConfigManager extends Component {
    // ✅ EXCELLENT: Partial for optional properties
    public updateConfig(updates: Partial<GameConfig>): void {
        // All properties are optional
    }

    // ✅ EXCELLENT: Required for mandatory properties
    public validateConfig(config: Required<GameConfig>): void {
        // All properties are required
    }

    // ✅ EXCELLENT: Readonly for immutable objects
    private readonly defaultConfig: Readonly<GameConfig> = {
        playerName: 'Player',
        maxHealth: 100,
        difficulty: 'normal',
        soundEnabled: true,
    };

    // ✅ EXCELLENT: Pick for selecting properties
    public getDisplayInfo(config: GameConfig): Pick<GameConfig, 'playerName' | 'difficulty'> {
        return {
            playerName: config.playerName,
            difficulty: config.difficulty,
        };
    }

    // ✅ EXCELLENT: Omit for excluding properties
    public getPublicConfig(config: GameConfig): Omit<GameConfig, 'soundEnabled'> {
        const { soundEnabled, ...publicConfig } = config;
        return publicConfig;
    }

    // ✅ EXCELLENT: Record for key-value mappings
    private readonly difficultyMultipliers: Record<string, number> = {
        easy: 0.5,
        normal: 1.0,
        hard: 1.5,
        expert: 2.0,
    };

    // ✅ EXCELLENT: ReturnType for function return types
    private createPlayer(): { name: string; level: number } {
        return { name: 'Player', level: 1 };
    }

    type PlayerType = ReturnType<typeof this.createPlayer>;
}
```

## Async/Await Patterns

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('DataManager')
export class DataManager extends Component {
    // ✅ EXCELLENT: Async/await for sequential operations
    public async loadGameData(): Promise<void> {
        try {
            const playerData = await this.fetchPlayerData();
            const levelData = await this.fetchLevelData(playerData.currentLevel);
            await this.initializeGame(playerData, levelData);
        } catch (error) {
            console.error('Failed to load game data:', error);
            throw error;
        }
    }

    // ✅ EXCELLENT: Promise.all for parallel operations
    public async loadAllData(): Promise<void> {
        try {
            const [playerData, configData, assetsData] = await Promise.all([
                this.fetchPlayerData(),
                this.fetchConfigData(),
                this.fetchAssetsData(),
            ]);

            this.initializeWithData(playerData, configData, assetsData);
        } catch (error) {
            console.error('Failed to load data:', error);
            throw error;
        }
    }

    // ✅ EXCELLENT: Promise.allSettled for partial failures
    public async loadDataWithFallback(): Promise<void> {
        const results = await Promise.allSettled([
            this.fetchPlayerData(),
            this.fetchConfigData(),
            this.fetchAssetsData(),
        ]);

        results.forEach((result, index) => {
            if (result.status === 'fulfilled') {
                console.log(`Data ${index} loaded:`, result.value);
            } else {
                console.error(`Data ${index} failed:`, result.reason);
            }
        });
    }

    // ✅ EXCELLENT: Error handling with async/await
    public async savePlayerData(data: PlayerData): Promise<boolean> {
        try {
            await this.validateData(data);
            await this.uploadData(data);
            return true;
        } catch (error) {
            if (error instanceof ValidationError) {
                console.error('Invalid data:', error.message);
            } else if (error instanceof NetworkError) {
                console.error('Network error:', error.message);
            } else {
                console.error('Unknown error:', error);
            }
            return false;
        }
    }

    private async fetchPlayerData(): Promise<PlayerData> {
        // Implementation
    }

    private async fetchLevelData(level: number): Promise<LevelData> {
        // Implementation
    }
}

// ❌ BAD: Promise chains (callback hell)
public loadGameData(): void {
    this.fetchPlayerData()
        .then(playerData => {
            return this.fetchLevelData(playerData.currentLevel);
        })
        .then(levelData => {
            return this.initializeGame(playerData, levelData); // playerData not in scope!
        })
        .catch(error => {
            console.error('Failed:', error);
        });
}
```

## Summary: Modern TypeScript Checklist

**Use these patterns for cleaner, more maintainable code:**

- [ ] Array methods (map/filter/reduce) instead of manual loops
- [ ] Arrow functions for callbacks and event handlers
- [ ] Destructuring for cleaner parameter handling
- [ ] Spread operator for object/array operations
- [ ] Optional chaining (?.) for safe property access
- [ ] Nullish coalescing (??) for default values
- [ ] Type guards for type-safe narrowing
- [ ] Utility types (Partial, Required, Readonly, Pick, Omit, Record)
- [ ] Async/await for asynchronous operations
- [ ] Promise.all/allSettled for parallel operations

**Modern TypeScript makes code more concise, readable, and type-safe.**

```

### references/framework/component-system.md

```markdown
# Cocos Creator Component System

## Entity-Component (EC) System Overview

Cocos Creator uses an Entity-Component (EC) architecture where:
- **Node** = Entity (game object container)
- **Component** = Behavior/functionality attached to Node
- **Scene** = Collection of Node hierarchies

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

// ✅ EXCELLENT: Complete component structure
@ccclass('PlayerController')
export class PlayerController extends Component {
    // @property decorator exposes fields to Inspector
    @property(Node)
    private readonly targetNode: Node | null = null;

    @property(Number)
    private readonly moveSpeed: number = 100;

    // Private fields not exposed
    private currentHealth: number = 100;
    private static readonly MAX_HEALTH: number = 100;

    // Lifecycle methods in execution order:
    // 1. onLoad() - Component initialization
    // 2. start() - After all components loaded
    // 3. onEnable() - When enabled (can be called multiple times)
    // 4. update(dt) - Every frame
    // 5. lateUpdate(dt) - After all update() calls
    // 6. onDisable() - When disabled
    // 7. onDestroy() - Cleanup
}
```

## @ccclass Decorator

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

// ✅ EXCELLENT: @ccclass with explicit name
@ccclass('GameManager')
export class GameManager extends Component {
    // Component implementation
}

// ✅ GOOD: @ccclass without name (uses class name)
@ccclass
export class PlayerController extends Component {
    // Component implementation
}

// ❌ WRONG: Missing @ccclass decorator
export class GameManager extends Component {
    // Won't work - Cocos can't serialize this component
}

// ❌ WRONG: Not extending Component
@ccclass('GameManager')
export class GameManager {
    // Won't work - must extend Component
}
```

## @property Decorator

```typescript
import { _decorator, Component, Node, Sprite, Label } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('PropertyExamples')
export class PropertyExamples extends Component {
    // ✅ EXCELLENT: Node reference
    @property(Node)
    private readonly playerNode: Node | null = null;

    // ✅ EXCELLENT: Component reference
    @property(Sprite)
    private readonly spriteComponent: Sprite | null = null;

    // ✅ EXCELLENT: Primitive types
    @property(Number)
    private readonly moveSpeed: number = 100;

    @property(String)
    private readonly playerName: string = 'Player';

    @property(Boolean)
    private readonly enableDebug: boolean = false;

    // ✅ EXCELLENT: Array of nodes
    @property([Node])
    private readonly enemyNodes: Node[] = [];

    // ✅ EXCELLENT: Array of numbers
    @property([Number])
    private readonly levelScores: number[] = [];

    // ✅ EXCELLENT: Enum property
    @property({ type: Enum(GameState) })
    private currentState: GameState = GameState.LOADING;

    // ✅ EXCELLENT: Property with custom display name and tooltip
    @property({
        type: Number,
        displayName: 'Movement Speed',
        tooltip: 'Player movement speed in units per second',
        min: 0,
        max: 500,
        step: 10,
    })
    private readonly speed: number = 100;

    // ✅ EXCELLENT: readonly for properties that shouldn't be reassigned
    @property(Node)
    private readonly targetNode: Node | null = null; // Can't reassign after initialization

    // Private field (not exposed to Inspector)
    private currentHealth: number = 100;
}

// ❌ WRONG: Property without type
@property
private playerNode: Node | null = null; // Won't serialize correctly

// ❌ WRONG: Mutable property that should be readonly
@property(Node)
private targetNode: Node | null = null; // Should be readonly if not reassigned
```

## Component Lifecycle Methods

### 1. onLoad() - Initialization

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('GameManager')
export class GameManager extends Component {
    @property(Node)
    private readonly playerNode: Node | null = null;

    @property(Node)
    private readonly uiRoot: Node | null = null;

    // ✅ EXCELLENT: onLoad for initialization and validation
    protected onLoad(): void {
        // Validate required references
        if (!this.playerNode) {
            throw new Error('GameManager: playerNode is required');
        }
        if (!this.uiRoot) {
            throw new Error('GameManager: uiRoot is required');
        }

        // Initialize component state
        this.initializeGameState();

        // Cache references (DO NOT reference other components yet)
        this.cacheNodeReferences();
    }

    private initializeGameState(): void {
        // Setup initial state
    }

    private cacheNodeReferences(): void {
        // Cache child nodes for faster access
    }
}

// ❌ WRONG: Accessing other components in onLoad
protected onLoad(): void {
    // Don't do this - other components may not be loaded yet
    const playerController = this.playerNode!.getComponent(PlayerController);
    playerController.initialize(); // May be undefined!
}

// ❌ WRONG: Heavy operations in onLoad
protected onLoad(): void {
    // Avoid expensive operations - onLoad should be fast
    this.loadAllLevelData(); // Should be async in start()
    this.generateProceduralContent(); // Too expensive for onLoad
}
```

### 2. start() - Post-Initialization

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('PlayerController')
export class PlayerController extends Component {
    @property(Node)
    private readonly enemyManagerNode: Node | null = null;

    private enemyManager!: EnemyManager;

    protected onLoad(): void {
        // Validate references
        if (!this.enemyManagerNode) {
            throw new Error('PlayerController: enemyManagerNode is required');
        }
    }

    // ✅ EXCELLENT: start() for referencing other components
    protected start(): void {
        // Safe to get components from other nodes now
        const enemyManager = this.enemyManagerNode!.getComponent(EnemyManager);
        if (!enemyManager) {
            throw new Error('EnemyManager component not found');
        }
        this.enemyManager = enemyManager;

        // Initialize based on other components
        this.setupPlayerBasedOnEnemies();

        // Start async operations
        this.loadPlayerDataAsync();
    }

    private setupPlayerBasedOnEnemies(): void {
        const enemyCount = this.enemyManager.getEnemyCount();
        this.adjustDifficultyBasedOnEnemies(enemyCount);
    }

    private async loadPlayerDataAsync(): Promise<void> {
        // Async loading is safe in start()
    }
}

// ❌ WRONG: Using start() instead of onLoad for validation
protected start(): void {
    // Too late - might be used before start() is called
    if (!this.playerNode) {
        throw new Error('playerNode is required');
    }
}
```

### 3. onEnable() - Activation

```typescript
import { _decorator, Component, Node, EventTouch } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('InputHandler')
export class InputHandler extends Component {
    @property(Node)
    private readonly buttonNode: Node | null = null;

    // ✅ EXCELLENT: onEnable() for registering listeners
    protected onEnable(): void {
        // Register event listeners
        if (this.buttonNode) {
            this.buttonNode.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
            this.buttonNode.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
        }

        // Subscribe to global events
        EventManager.on(GameEvent.LEVEL_COMPLETE, this.onLevelComplete, this);

        // Resume component operations
        this.resumeGameLogic();
    }

    protected onDisable(): void {
        // ✅ CRITICAL: Always unregister in onDisable
        if (this.buttonNode) {
            this.buttonNode.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
            this.buttonNode.off(Node.EventType.TOUCH_END, this.onTouchEnd, this);
        }

        EventManager.off(GameEvent.LEVEL_COMPLETE, this.onLevelComplete, this);

        // Pause component operations
        this.pauseGameLogic();
    }

    private onTouchStart(event: EventTouch): void {
        // Handle touch
    }

    private onTouchEnd(event: EventTouch): void {
        // Handle touch end
    }

    private onLevelComplete(): void {
        // Handle level complete
    }
}

// ❌ WRONG: Registering listeners in onLoad
protected onLoad(): void {
    // Don't register here - won't be unregistered properly when disabled
    this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
}

// ❌ WRONG: Not unregistering in onDisable
protected onEnable(): void {
    this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
}

protected onDisable(): void {
    // Missing unregistration - memory leak!
}
```

### 4. update(dt) - Per-Frame Logic

```typescript
import { _decorator, Component, Node, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('PlayerMovement')
export class PlayerMovement extends Component {
    @property(Number)
    private readonly moveSpeed: number = 100;

    private readonly tempVec3: Vec3 = new Vec3();
    private inputDirection: Vec3 = new Vec3(1, 0, 0);

    // ✅ EXCELLENT: Efficient update implementation
    protected update(dt: number): void {
        // Reuse preallocated vector
        this.node.getPosition(this.tempVec3);

        // Calculate movement
        this.tempVec3.x += this.inputDirection.x * this.moveSpeed * dt;
        this.tempVec3.y += this.inputDirection.y * this.moveSpeed * dt;

        // Apply new position
        this.node.setPosition(this.tempVec3);
    }
}

// Throttled expensive operations
@ccclass('AIController')
export class AIController extends Component {
    private frameCount: number = 0;
    private static readonly AI_UPDATE_INTERVAL: number = 10;

    // ✅ EXCELLENT: Throttle expensive operations
    protected update(dt: number): void {
        this.frameCount++;

        // Cheap operations every frame
        this.moveTowardsTarget(dt);

        // Expensive AI decisions every 10 frames
        if (this.frameCount % AIController.AI_UPDATE_INTERVAL === 0) {
            this.updateAIDecision();
        }
    }

    private moveTowardsTarget(dt: number): void {
        // Simple movement calculation
    }

    private updateAIDecision(): void {
        // Complex AI logic
    }
}

// ❌ WRONG: Allocations in update
protected update(dt: number): void {
    const currentPos = this.node.position.clone(); // Allocates every frame!
    currentPos.x += this.moveSpeed * dt;
    this.node.setPosition(currentPos);
}

// ❌ WRONG: Expensive operations every frame
protected update(dt: number): void {
    this.recalculatePathfinding(); // A* algorithm 60 times per second!
    this.updateComplexAI(); // Too expensive for every frame
}

// ❌ WRONG: Component lookups in update
protected update(dt: number): void {
    const sprite = this.node.getComponent(Sprite); // Cache this in onLoad!
    sprite?.doSomething();
}
```

### 5. lateUpdate(dt) - Post-Update Logic

```typescript
import { _decorator, Component, Node, Camera } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('CameraFollow')
export class CameraFollow extends Component {
    @property(Node)
    private readonly target: Node | null = null;

    @property(Camera)
    private readonly camera: Camera | null = null;

    // ✅ EXCELLENT: lateUpdate for camera following
    // Runs after all update() calls, ensuring target has moved
    protected lateUpdate(dt: number): void {
        if (!this.target || !this.camera) return;

        // Follow target position after target has been updated
        const targetPos = this.target.position;
        this.camera.node.setPosition(targetPos.x, targetPos.y, this.camera.node.position.z);
    }
}

// ✅ GOOD: lateUpdate for UI that depends on game state
@ccclass('HealthBarUpdater')
export class HealthBarUpdater extends Component {
    @property(Node)
    private readonly healthBar: Node | null = null;

    private playerHealth: number = 100;

    // Health is updated in PlayerController.update()
    // UI is updated in lateUpdate() to reflect final health value
    protected lateUpdate(dt: number): void {
        if (!this.healthBar) return;

        const healthPercentage = this.playerHealth / 100;
        this.healthBar.scale = new Vec3(healthPercentage, 1, 1);
    }
}

// ❌ WRONG: Using lateUpdate for regular logic
protected lateUpdate(dt: number): void {
    // This should be in update(), not lateUpdate()
    this.movePlayer(dt);
}
```

### 6. onDestroy() - Cleanup

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('ResourceManager')
export class ResourceManager extends Component {
    private readonly loadedAssets: Map<string, Asset> = new Map();
    private readonly eventListeners: Set<Function> = new Set();
    private readonly scheduledCallbacks: Set<Function> = new Set();

    // ✅ EXCELLENT: Complete cleanup in onDestroy
    protected onDestroy(): void {
        // Unregister all event listeners
        this.node.off(Node.EventType.TOUCH_START);
        EventManager.off(GameEvent.LEVEL_COMPLETE, this.onLevelComplete, this);

        // Clear collections
        this.eventListeners.clear();
        this.scheduledCallbacks.clear();

        // Release loaded assets
        for (const [id, asset] of this.loadedAssets) {
            asset.decRef();
        }
        this.loadedAssets.clear();

        // Unschedule all callbacks
        this.unscheduleAllCallbacks();

        // Clear any references to prevent memory leaks
        this.clearReferences();
    }

    private clearReferences(): void {
        // Clear any cached references
    }
}

// ❌ WRONG: Missing cleanup
protected onDestroy(): void {
    // Forgot to unregister events - memory leak!
    // Forgot to release assets - memory leak!
    // Forgot to unschedule callbacks - may cause errors!
}

// ❌ WRONG: Incomplete cleanup
protected onDestroy(): void {
    this.loadedAssets.clear(); // Cleared map but didn't decRef assets!
}
```

## Component Execution Order

```typescript
// Execution order when scene loads:
// 1. All components: onLoad() (in hierarchy order)
// 2. All components: start() (in hierarchy order)
// 3. All components: onEnable() (if not already enabled)
// 4. Begin frame loop:
//    - All components: update(dt)
//    - All components: lateUpdate(dt)
// 5. When component disabled:
//    - Component: onDisable()
// 6. When component destroyed:
//    - Component: onDestroy()

// ✅ EXCELLENT: Lifecycle method organization
@ccclass('CompleteLifecycle')
export class CompleteLifecycle extends Component {
    // 1. INITIALIZATION PHASE
    protected onLoad(): void {
        // Initialize component
        // Validate required references
        // Cache node references
        // DO NOT access other components yet
    }

    protected start(): void {
        // Access other components (safe now)
        // Start async operations
        // Initialize based on other components
    }

    // 2. ACTIVATION PHASE
    protected onEnable(): void {
        // Register event listeners
        // Subscribe to global events
        // Resume operations
    }

    // 3. UPDATE PHASE
    protected update(dt: number): void {
        // Per-frame game logic
        // Movement, input, AI
        // Keep allocations zero
    }

    protected lateUpdate(dt: number): void {
        // Logic that depends on update()
        // Camera follow, UI updates
    }

    // 4. DEACTIVATION PHASE
    protected onDisable(): void {
        // Unregister event listeners
        // Unsubscribe from events
        // Pause operations
    }

    // 5. CLEANUP PHASE
    protected onDestroy(): void {
        // Release resources
        // Clear collections
        // Unschedule callbacks
        // Final cleanup
    }
}
```

## Required Reference Validation

```typescript
import { _decorator, Component, Node, Sprite } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('RequiredReferences')
export class RequiredReferences extends Component {
    @property(Node)
    private readonly targetNode: Node | null = null;

    @property(Sprite)
    private readonly spriteComponent: Sprite | null = null;

    @property([Node])
    private readonly enemyNodes: Node[] = [];

    // ✅ EXCELLENT: Validate all required references in onLoad
    protected onLoad(): void {
        if (!this.targetNode) {
            throw new Error('RequiredReferences: targetNode is required');
        }

        if (!this.spriteComponent) {
            throw new Error('RequiredReferences: spriteComponent is required');
        }

        if (this.enemyNodes.length === 0) {
            throw new Error('RequiredReferences: at least one enemy node is required');
        }

        // All references validated - safe to use
        this.initialize();
    }

    private initialize(): void {
        // Can safely use all references here
        this.targetNode!.setPosition(0, 0, 0);
        this.spriteComponent!.sizeMode = Sprite.SizeMode.CUSTOM;
    }
}

// ❌ WRONG: No validation
protected onLoad(): void {
    // Assuming references exist - may crash at runtime
    this.targetNode!.setPosition(0, 0, 0);
}

// ❌ WRONG: Silent validation
protected onLoad(): void {
    if (!this.targetNode) {
        console.error('targetNode is missing'); // Don't just log
        return; // Silent failure
    }
}
```

## Summary: Component System Checklist

**Component Structure:**
- [ ] @ccclass decorator on class
- [ ] Extends Component base class
- [ ] @property decorator for Inspector-exposed fields
- [ ] readonly for properties that aren't reassigned
- [ ] Access modifiers (public/private/protected)

**Lifecycle Implementation:**
- [ ] onLoad() - Validate required references, initialize state
- [ ] start() - Access other components, start async operations
- [ ] onEnable() - Register event listeners
- [ ] update(dt) - Per-frame logic (zero allocations)
- [ ] lateUpdate(dt) - Post-update logic (camera, UI)
- [ ] onDisable() - Unregister event listeners
- [ ] onDestroy() - Release resources, clear references

**Best Practices:**
- [ ] Validate required @property references in onLoad()
- [ ] Throw exceptions for missing required references
- [ ] Cache component references (don't lookup in update)
- [ ] Zero allocations in update/lateUpdate
- [ ] Always unregister listeners in onDisable/onDestroy
- [ ] Use readonly for @property fields when appropriate

**The component lifecycle is the foundation of Cocos Creator architecture.**

```

### references/framework/event-patterns.md

```markdown
# Cocos Creator Event Patterns

## EventDispatcher Pattern (Custom Events)

```typescript
import { _decorator, Component, EventTarget } from 'cc';
const { ccclass } = _decorator;

// ✅ EXCELLENT: Centralized event system
export enum GameEvent {
    SCORE_CHANGED = 'score_changed',
    LEVEL_COMPLETE = 'level_complete',
    PLAYER_DIED = 'player_died',
    ENEMY_SPAWNED = 'enemy_spawned',
}

export interface ScoreChangedEvent {
    oldScore: number;
    newScore: number;
    combo: number;
}

export interface LevelCompleteEvent {
    level: number;
    stars: number;
    time: number;
}

@ccclass('EventManager')
export class EventManager extends Component {
    private static instance: EventManager | null = null;
    private readonly eventTarget: EventTarget = new EventTarget();

    protected onLoad(): void {
        if (EventManager.instance) {
            throw new Error('EventManager: instance already exists');
        }
        EventManager.instance = this;
    }

    protected onDestroy(): void {
        this.eventTarget.clear();
        EventManager.instance = null;
    }

    // ✅ EXCELLENT: Type-safe emit
    public static emit<T>(event: GameEvent, data?: T): void {
        if (!EventManager.instance) {
            throw new Error('EventManager: instance not initialized');
        }
        EventManager.instance.eventTarget.emit(event, data);
    }

    // ✅ EXCELLENT: Type-safe subscribe
    public static on<T>(event: GameEvent, callback: (data: T) => void, target?: any): void {
        if (!EventManager.instance) {
            throw new Error('EventManager: instance not initialized');
        }
        EventManager.instance.eventTarget.on(event, callback, target);
    }

    // ✅ EXCELLENT: Type-safe unsubscribe
    public static off<T>(event: GameEvent, callback: (data: T) => void, target?: any): void {
        if (!EventManager.instance) {
            throw new Error('EventManager: instance not initialized');
        }
        EventManager.instance.eventTarget.off(event, callback, target);
    }

    // ✅ EXCELLENT: Once (auto-unsubscribe after first call)
    public static once<T>(event: GameEvent, callback: (data: T) => void, target?: any): void {
        if (!EventManager.instance) {
            throw new Error('EventManager: instance not initialized');
        }
        EventManager.instance.eventTarget.once(event, callback, target);
    }
}

// Usage in component
@ccclass('ScoreManager')
export class ScoreManager extends Component {
    private currentScore: number = 0;

    public addScore(points: number): void {
        const oldScore = this.currentScore;
        this.currentScore += points;

        // ✅ EXCELLENT: Emit typed event
        EventManager.emit<ScoreChangedEvent>(GameEvent.SCORE_CHANGED, {
            oldScore,
            newScore: this.currentScore,
            combo: 1,
        });
    }
}

// Subscriber component
@ccclass('ScoreDisplay')
export class ScoreDisplay extends Component {
    protected onEnable(): void {
        // ✅ EXCELLENT: Subscribe in onEnable
        EventManager.on<ScoreChangedEvent>(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
    }

    protected onDisable(): void {
        // ✅ CRITICAL: Always unsubscribe in onDisable
        EventManager.off<ScoreChangedEvent>(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
    }

    private onScoreChanged(data: ScoreChangedEvent): void {
        console.log(`Score: ${data.oldScore} → ${data.newScore}`);
        this.updateDisplay(data.newScore);
    }

    private updateDisplay(score: number): void {
        // Update UI
    }
}

// ❌ WRONG: No unsubscription (memory leak)
protected onEnable(): void {
    EventManager.on<ScoreChangedEvent>(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
}

// Missing onDisable - memory leak!

// ❌ WRONG: String-based events (not type-safe)
EventManager.emit('score_changed', { score: 100 }); // Typo-prone
```

## Node Event System (Built-in Events)

```typescript
import { _decorator, Component, Node, EventTouch, EventKeyboard } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('TouchHandler')
export class TouchHandler extends Component {
    @property(Node)
    private readonly buttonNode: Node | null = null;

    // ✅ EXCELLENT: Touch event handling
    protected onEnable(): void {
        if (!this.buttonNode) return;

        this.buttonNode.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
        this.buttonNode.on(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
        this.buttonNode.on(Node.EventType.TOUCH_END, this.onTouchEnd, this);
        this.buttonNode.on(Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
    }

    protected onDisable(): void {
        if (!this.buttonNode) return;

        this.buttonNode.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
        this.buttonNode.off(Node.EventType.TOUCH_MOVE, this.onTouchMove, this);
        this.buttonNode.off(Node.EventType.TOUCH_END, this.onTouchEnd, this);
        this.buttonNode.off(Node.EventType.TOUCH_CANCEL, this.onTouchCancel, this);
    }

    private onTouchStart(event: EventTouch): void {
        const location = event.getUILocation();
        console.log(`Touch start at: ${location.x}, ${location.y}`);
    }

    private onTouchMove(event: EventTouch): void {
        const delta = event.getUIDelta();
        console.log(`Touch delta: ${delta.x}, ${delta.y}`);
    }

    private onTouchEnd(event: EventTouch): void {
        console.log('Touch ended');
    }

    private onTouchCancel(event: EventTouch): void {
        console.log('Touch cancelled');
    }
}

// ✅ EXCELLENT: Keyboard event handling
@ccclass('KeyboardHandler')
export class KeyboardHandler extends Component {
    protected onEnable(): void {
        this.node.on(Node.EventType.KEY_DOWN, this.onKeyDown, this);
        this.node.on(Node.EventType.KEY_UP, this.onKeyUp, this);
    }

    protected onDisable(): void {
        this.node.off(Node.EventType.KEY_DOWN, this.onKeyDown, this);
        this.node.off(Node.EventType.KEY_UP, this.onKeyUp, this);
    }

    private onKeyDown(event: EventKeyboard): void {
        switch (event.keyCode) {
            case macro.KEY.w:
            case macro.KEY.up:
                this.moveUp();
                break;
            case macro.KEY.s:
            case macro.KEY.down:
                this.moveDown();
                break;
        }
    }

    private onKeyUp(event: EventKeyboard): void {
        this.stopMovement();
    }
}
```

## Event Cleanup Patterns

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

// ✅ EXCELLENT: Comprehensive cleanup pattern
@ccclass('CompleteEventCleanup')
export class CompleteEventCleanup extends Component {
    @property(Node)
    private readonly targetNode: Node | null = null;

    // Track registered listeners for complete cleanup
    private readonly registeredListeners: Array<{
        node: Node;
        eventType: string;
        callback: Function;
    }> = [];

    protected onEnable(): void {
        if (!this.targetNode) return;

        // Register and track listeners
        this.registerListener(
            this.targetNode,
            Node.EventType.TOUCH_START,
            this.onTouchStart
        );
        this.registerListener(
            this.node,
            Node.EventType.CHILD_ADDED,
            this.onChildAdded
        );

        // Subscribe to global events
        EventManager.on(GameEvent.LEVEL_COMPLETE, this.onLevelComplete, this);
    }

    protected onDisable(): void {
        // Unregister all tracked listeners
        for (const { node, eventType, callback } of this.registeredListeners) {
            node.off(eventType, callback, this);
        }
        this.registeredListeners.length = 0;

        // Unsubscribe from global events
        EventManager.off(GameEvent.LEVEL_COMPLETE, this.onLevelComplete, this);
    }

    private registerListener(node: Node, eventType: string, callback: Function): void {
        node.on(eventType, callback, this);
        this.registeredListeners.push({ node, eventType, callback });
    }

    private onTouchStart(event: EventTouch): void {
        // Handle touch
    }

    private onChildAdded(child: Node): void {
        // Handle child added
    }

    private onLevelComplete(): void {
        // Handle level complete
    }
}

// ✅ EXCELLENT: Automatic cleanup with disposable pattern
interface IDisposable {
    dispose(): void;
}

class EventSubscription implements IDisposable {
    constructor(
        private readonly eventManager: EventManager,
        private readonly event: GameEvent,
        private readonly callback: Function,
        private readonly target: any
    ) {}

    public dispose(): void {
        EventManager.off(this.event, this.callback as any, this.target);
    }
}

@ccclass('DisposablePattern')
export class DisposablePattern extends Component {
    private readonly subscriptions: IDisposable[] = [];

    protected onEnable(): void {
        // ✅ EXCELLENT: Track subscriptions for auto-cleanup
        this.subscriptions.push(
            new EventSubscription(
                EventManager.instance!,
                GameEvent.SCORE_CHANGED,
                this.onScoreChanged,
                this
            )
        );
    }

    protected onDisable(): void {
        // ✅ EXCELLENT: Dispose all subscriptions
        for (const subscription of this.subscriptions) {
            subscription.dispose();
        }
        this.subscriptions.length = 0;
    }

    private onScoreChanged(data: ScoreChangedEvent): void {
        // Handle score change
    }
}
```

## Event Performance Best Practices

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('PerformanceOptimizedEvents')
export class PerformanceOptimizedEvents extends Component {
    // ✅ EXCELLENT: Throttle frequent events
    private lastEmitTime: number = 0;
    private static readonly EMIT_THROTTLE_MS: number = 100; // Max 10 events/second

    public emitThrottled(event: GameEvent, data: any): void {
        const now = Date.now();
        if (now - this.lastEmitTime >= PerformanceOptimizedEvents.EMIT_THROTTLE_MS) {
            EventManager.emit(event, data);
            this.lastEmitTime = now;
        }
    }

    // ✅ EXCELLENT: Batch events to reduce overhead
    private readonly pendingEvents: Array<{ event: GameEvent; data: any }> = [];
    private batchEmitScheduled: boolean = false;

    public emitBatched(event: GameEvent, data: any): void {
        this.pendingEvents.push({ event, data });

        if (!this.batchEmitScheduled) {
            this.batchEmitScheduled = true;
            this.scheduleOnce(() => {
                this.flushBatchedEvents();
            }, 0);
        }
    }

    private flushBatchedEvents(): void {
        for (const { event, data } of this.pendingEvents) {
            EventManager.emit(event, data);
        }
        this.pendingEvents.length = 0;
        this.batchEmitScheduled = false;
    }

    // ✅ EXCELLENT: Debounce events (emit only after quiet period)
    private debounceTimer: number | null = null;
    private static readonly DEBOUNCE_MS: number = 300;

    public emitDebounced(event: GameEvent, data: any): void {
        if (this.debounceTimer !== null) {
            clearTimeout(this.debounceTimer);
        }

        this.debounceTimer = setTimeout(() => {
            EventManager.emit(event, data);
            this.debounceTimer = null;
        }, PerformanceOptimizedEvents.DEBOUNCE_MS) as any;
    }
}

// ❌ WRONG: Emitting events in update loop
protected update(dt: number): void {
    // Emits 60 events per second!
    EventManager.emit(GameEvent.PLAYER_MOVED, this.node.position);
}

// ✅ BETTER: Throttle or emit only on significant changes
private lastPosition: Vec3 = new Vec3();
private static readonly MOVE_THRESHOLD: number = 1.0;

protected update(dt: number): void {
    const distance = Vec3.distance(this.node.position, this.lastPosition);

    if (distance >= PerformanceOptimizedEvents.MOVE_THRESHOLD) {
        EventManager.emit(GameEvent.PLAYER_MOVED, this.node.position.clone());
        this.lastPosition.set(this.node.position);
    }
}
```

## Summary: Event Pattern Checklist

**EventDispatcher (Custom Events):**
- [ ] Use centralized EventManager with EventTarget
- [ ] Define event names as enum (not strings)
- [ ] Use typed event data interfaces
- [ ] Subscribe in onEnable(), unsubscribe in onDisable()
- [ ] Always pass `this` as target parameter for proper cleanup

**Node Events (Built-in):**
- [ ] Use Node.EventType constants (TOUCH_START, KEY_DOWN, etc.)
- [ ] Register listeners in onEnable()
- [ ] Unregister listeners in onDisable() with same parameters
- [ ] Handle EventTouch and EventKeyboard properly

**Event Cleanup:**
- [ ] Track all registered listeners for complete cleanup
- [ ] Unregister in both onDisable() and onDestroy()
- [ ] Use disposable pattern for automatic cleanup
- [ ] Clear event collections in onDestroy()

**Performance:**
- [ ] Throttle frequent events (max 10/second)
- [ ] Batch events to reduce overhead
- [ ] Debounce events for user input
- [ ] Never emit events in update() without throttling

**Always unsubscribe from events to prevent memory leaks.**

```

### references/framework/playable-optimization.md

```markdown
# Playable Ads Performance Optimization

## DrawCall Batching (Critical for Playables)

**Target: <10 DrawCalls for smooth 60fps playables**

```typescript
import { _decorator, Component, Sprite, SpriteAtlas, SpriteFrame } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('SpriteAtlasManager')
export class SpriteAtlasManager extends Component {
    // ✅ EXCELLENT: Use sprite atlas for DrawCall batching
    @property(SpriteAtlas)
    private readonly characterAtlas: SpriteAtlas | null = null;

    @property(SpriteAtlas)
    private readonly uiAtlas: SpriteAtlas | null = null;

    private readonly spriteFrameCache: Map<string, SpriteFrame> = new Map();

    protected onLoad(): void {
        if (!this.characterAtlas || !this.uiAtlas) {
            throw new Error('SpriteAtlasManager: atlases are required');
        }

        // ✅ EXCELLENT: Prewarm sprite frames from atlas
        this.prewarmAtlas(this.characterAtlas, 'character');
        this.prewarmAtlas(this.uiAtlas, 'ui');
    }

    private prewarmAtlas(atlas: SpriteAtlas, prefix: string): void {
        const spriteFrames = atlas.getSpriteFrames();
        for (const frame of spriteFrames) {
            const key = `${prefix}_${frame.name}`;
            this.spriteFrameCache.set(key, frame);
        }
    }

    // ✅ EXCELLENT: Get sprite frame from cache (batched in same DrawCall)
    public getSpriteFrame(atlasName: string, frameName: string): SpriteFrame | null {
        const key = `${atlasName}_${frameName}`;
        return this.spriteFrameCache.get(key) ?? null;
    }
}

// Usage: All sprites from same atlas = single DrawCall
@ccclass('CharacterSprite')
export class CharacterSprite extends Component {
    @property(Sprite)
    private readonly sprite: Sprite | null = null;

    private atlasManager!: SpriteAtlasManager;

    protected start(): void {
        const manager = this.node.parent?.getComponent(SpriteAtlasManager);
        if (!manager) throw new Error('SpriteAtlasManager not found');
        this.atlasManager = manager;

        // ✅ GOOD: Set sprite frame from atlas (batched)
        const frame = this.atlasManager.getSpriteFrame('character', 'idle_01');
        if (frame && this.sprite) {
            this.sprite.spriteFrame = frame;
        }
    }
}

// ❌ WRONG: Individual textures (multiple DrawCalls)
@property(SpriteFrame)
private characterIdleFrame: SpriteFrame | null = null; // DrawCall 1

@property(SpriteFrame)
private characterWalkFrame: SpriteFrame | null = null; // DrawCall 2

@property(SpriteFrame)
private characterJumpFrame: SpriteFrame | null = null; // DrawCall 3
// Result: 3 DrawCalls for 3 sprites!

// ✅ BETTER: Single atlas
@property(SpriteAtlas)
private characterAtlas: SpriteAtlas | null = null; // DrawCall 1 for all frames
```

## GPU Skinning (Skeletal Animations)

```typescript
import { _decorator, Component, SkeletalAnimation } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('AnimationController')
export class AnimationController extends Component {
    @property(SkeletalAnimation)
    private readonly skeleton: SkeletalAnimation | null = null;

    protected onLoad(): void {
        if (!this.skeleton) {
            throw new Error('AnimationController: skeleton is required');
        }

        // ✅ EXCELLENT: Enable GPU skinning for better performance
        // GPU handles bone transformations instead of CPU
        this.skeleton.useBakedAnimation = true; // Baked animation data
    }

    public playAnimation(animName: string, loop: boolean = false): void {
        if (!this.skeleton) return;

        const state = this.skeleton.getState(animName);
        if (state) {
            state.wrapMode = loop ? SkeletalAnimation.WrapMode.Loop : SkeletalAnimation.WrapMode.Normal;
            this.skeleton.play(animName);
        }
    }
}

// ❌ WRONG: CPU skinning (default, slower)
// Don't set useBakedAnimation to false for playables
```

## Object Pooling for Playables

```typescript
import { _decorator, Component, Node, Prefab, instantiate, NodePool } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('PlayableObjectPool')
export class PlayableObjectPool extends Component {
    @property(Prefab)
    private readonly bulletPrefab: Prefab | null = null;

    @property(Prefab)
    private readonly effectPrefab: Prefab | null = null;

    private readonly bulletPool: NodePool = new NodePool();
    private readonly effectPool: NodePool = new NodePool();
    private static readonly PREWARM_COUNT: number = 20;

    // ✅ EXCELLENT: Prewarm pools to avoid allocations during gameplay
    protected onLoad(): void {
        if (!this.bulletPrefab || !this.effectPrefab) {
            throw new Error('PlayableObjectPool: prefabs are required');
        }

        // Prewarm bullet pool
        for (let i = 0; i < PlayableObjectPool.PREWARM_COUNT; i++) {
            const bullet = instantiate(this.bulletPrefab);
            this.bulletPool.put(bullet);
        }

        // Prewarm effect pool
        for (let i = 0; i < PlayableObjectPool.PREWARM_COUNT; i++) {
            const effect = instantiate(this.effectPrefab);
            this.effectPool.put(effect);
        }
    }

    // ✅ EXCELLENT: Get from pool (zero allocations in gameplay)
    public getBullet(): Node {
        if (this.bulletPool.size() > 0) {
            const bullet = this.bulletPool.get()!;
            bullet.active = true;
            return bullet;
        }

        // Fallback: create new (should be rare if prewarmed correctly)
        if (!this.bulletPrefab) {
            throw new Error('bulletPrefab is null');
        }
        return instantiate(this.bulletPrefab);
    }

    public returnBullet(bullet: Node): void {
        bullet.active = false;
        this.bulletPool.put(bullet);
    }

    protected onDestroy(): void {
        this.bulletPool.clear();
        this.effectPool.clear();
    }
}

// ❌ WRONG: Creating/destroying objects during gameplay
public shoot(): void {
    const bullet = instantiate(this.bulletPrefab!); // Allocates every time
    this.scheduleOnce(() => {
        bullet.destroy(); // Triggers GC
    }, 2.0);
}
```

## Update Loop Optimization for Playables

```typescript
import { _decorator, Component, Node, Vec3 } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('OptimizedUpdate')
export class OptimizedUpdate extends Component {
    @property([Node])
    private readonly enemies: Node[] = [];

    // ✅ EXCELLENT: Preallocate to avoid allocations in update
    private readonly tempVec3: Vec3 = new Vec3();
    private readonly activeEnemies: Node[] = [];
    private activeEnemiesDirty: boolean = true;
    private frameCount: number = 0;

    // ✅ EXCELLENT: Update expensive operations every N frames
    protected update(dt: number): void {
        this.frameCount++;

        // Cheap operations: every frame
        this.updateMovement(dt);

        // Expensive operations: every 10 frames (6 times/second at 60fps)
        if (this.frameCount % 10 === 0) {
            this.updateAI();
        }

        // Very expensive: every 60 frames (once per second at 60fps)
        if (this.frameCount % 60 === 0) {
            this.updatePathfinding();
        }
    }

    private updateMovement(dt: number): void {
        // Use cached active enemies list
        const activeEnemies = this.getActiveEnemies();

        for (const enemy of activeEnemies) {
            // Reuse preallocated vector
            enemy.getPosition(this.tempVec3);
            this.tempVec3.y += 10 * dt;
            enemy.setPosition(this.tempVec3);
        }
    }

    private getActiveEnemies(): Node[] {
        if (this.activeEnemiesDirty) {
            this.activeEnemies.length = 0;
            for (const enemy of this.enemies) {
                if (enemy.active) {
                    this.activeEnemies.push(enemy);
                }
            }
            this.activeEnemiesDirty = false;
        }
        return this.activeEnemies;
    }

    private updateAI(): void {
        // Expensive AI logic
    }

    private updatePathfinding(): void {
        // Very expensive pathfinding
    }
}

// ❌ WRONG: All logic in update, allocations everywhere
protected update(dt: number): void {
    // Allocates array every frame
    const activeEnemies = this.enemies.filter(e => e.active);

    for (const enemy of activeEnemies) {
        // Allocates vector every frame
        const pos = enemy.position.clone();
        pos.y += 10 * dt;
        enemy.setPosition(pos);
    }

    // Expensive operations every frame
    this.updatePathfinding(); // 60 times/second!
    this.updateAI(); // 60 times/second!
}
```

## Resource Loading and Preloading

```typescript
import { _decorator, Component, resources, SpriteFrame, AudioClip } from 'cc';
const { ccclass } = _decorator;

@ccclass('ResourcePreloader')
export class ResourcePreloader extends Component {
    private readonly loadedResources: Map<string, Asset> = new Map();

    // ✅ EXCELLENT: Preload all resources at game start
    protected async start(): Promise<void> {
        await this.preloadAllResources();
    }

    private async preloadAllResources(): Promise<void> {
        const resourcePaths = [
            'sprites/character',
            'sprites/enemies',
            'audio/bgm',
            'audio/sfx',
        ];

        const promises = resourcePaths.map(path => this.preloadResource(path));
        await Promise.all(promises);

        console.log('All resources preloaded');
    }

    private async preloadResource(path: string): Promise<void> {
        return new Promise((resolve, reject) => {
            resources.load(path, (err, asset) => {
                if (err) {
                    console.error(`Failed to load ${path}:`, err);
                    reject(err);
                    return;
                }

                this.loadedResources.set(path, asset);
                resolve();
            });
        });
    }

    public getResource<T extends Asset>(path: string): T | null {
        return (this.loadedResources.get(path) as T) ?? null;
    }

    protected onDestroy(): void {
        // ✅ EXCELLENT: Release all loaded resources
        for (const [path, asset] of this.loadedResources) {
            asset.decRef();
        }
        this.loadedResources.clear();
    }
}

// ❌ WRONG: Loading resources during gameplay
protected update(dt: number): void {
    if (this.shouldSpawnEnemy()) {
        // Loading during gameplay causes frame drops!
        resources.load('sprites/enemy', SpriteFrame, (err, sprite) => {
            this.spawnEnemy(sprite);
        });
    }
}

// ✅ BETTER: Preload and reuse
protected start(): void {
    resources.load('sprites/enemy', SpriteFrame, (err, sprite) => {
        this.enemySprite = sprite;
    });
}

protected update(dt: number): void {
    if (this.shouldSpawnEnemy() && this.enemySprite) {
        this.spawnEnemy(this.enemySprite); // Instant, no loading
    }
}
```

## Memory Management for Playables

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass } = _decorator;

@ccclass('MemoryOptimized')
export class MemoryOptimized extends Component {
    // ✅ EXCELLENT: Use typed arrays for large datasets
    private positions: Float32Array = new Float32Array(300); // 100 Vec3s
    private velocities: Float32Array = new Float32Array(300);

    // ✅ EXCELLENT: Reuse arrays instead of creating new ones
    private readonly tempArray: number[] = [];

    protected update(dt: number): void {
        // Reuse array, don't allocate
        this.tempArray.length = 0;

        for (let i = 0; i < 100; i++) {
            this.tempArray.push(i * dt);
        }
    }

    // ✅ EXCELLENT: WeakMap for caches (automatic cleanup)
    private readonly nodeCache: WeakMap<Node, CachedData> = new WeakMap();

    public getCachedData(node: Node): CachedData | undefined {
        return this.nodeCache.get(node);
    }

    protected onDestroy(): void {
        // ✅ EXCELLENT: Clear references
        this.tempArray.length = 0;
        // WeakMap entries are auto-cleared when nodes are destroyed
    }
}
```

## Summary: Playable Optimization Checklist

**DrawCall Batching (<10 target):**
- [ ] Use sprite atlases for all sprites (not individual textures)
- [ ] Prewarm sprite frame cache in onLoad()
- [ ] Group UI elements into single atlas
- [ ] Use same material for similar objects

**Animation Performance:**
- [ ] Enable GPU skinning (useBakedAnimation = true)
- [ ] Bake skeletal animations
- [ ] Limit simultaneous animations

**Object Pooling:**
- [ ] Pool bullets, effects, enemies (anything spawned frequently)
- [ ] Prewarm pools in onLoad() (at least 20 objects)
- [ ] Never instantiate/destroy during gameplay

**Update Loop:**
- [ ] Zero allocations in update()
- [ ] Throttle expensive operations (every 10-60 frames)
- [ ] Cache active object lists
- [ ] Reuse preallocated vectors/arrays

**Resource Management:**
- [ ] Preload all resources at game start
- [ ] Never load resources during gameplay
- [ ] Release resources in onDestroy()
- [ ] Use WeakMap for auto-cleanup caches

**Target: 60fps with <10 DrawCalls and <5MB bundle size for playable ads.**

```

### references/language/performance.md

```markdown
# TypeScript Performance Optimization

## Zero Allocations in update()

**Critical Rule**: Never allocate objects in `update()`, `lateUpdate()`, or any method called every frame.

```typescript
import { _decorator, Component, Node, Vec3, Quat } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('OptimizedController')
export class OptimizedController extends Component {
    @property(Node)
    private readonly targetNode: Node | null = null;

    // ✅ EXCELLENT: Preallocated reusable objects
    private readonly tempVec3: Vec3 = new Vec3();
    private readonly tempQuat: Quat = new Quat();
    private readonly tempVec3Array: Vec3[] = [];

    // ✅ EXCELLENT: No allocations in update
    protected update(dt: number): void {
        if (!this.targetNode) return;

        // Reuse preallocated vector
        this.targetNode.getPosition(this.tempVec3);
        this.tempVec3.y += 10 * dt;
        this.targetNode.setPosition(this.tempVec3);

        // Reuse preallocated quaternion
        this.targetNode.getRotation(this.tempQuat);
        Quat.rotateY(this.tempQuat, this.tempQuat, dt);
        this.targetNode.setRotation(this.tempQuat);
    }

    // ✅ EXCELLENT: Reuse array instead of creating new one
    public updateMultipleNodes(nodes: Node[]): void {
        this.tempVec3Array.length = 0; // Clear without allocating

        for (const node of nodes) {
            node.getPosition(this.tempVec3);
            this.tempVec3Array.push(this.tempVec3.clone());
        }
    }
}

// ❌ WRONG: Allocating in update
protected update(dt: number): void {
    if (!this.targetNode) return;

    // Creates new Vec3 every frame (60 allocations/second)
    const currentPos = this.targetNode.position.clone();
    currentPos.y += 10 * dt;
    this.targetNode.setPosition(currentPos);

    // Creates new array every frame
    const positions = this.nodes.map(n => n.position.clone());
}

// ❌ WRONG: String concatenation in update
protected update(dt: number): void {
    // Creates new string every frame
    const debugInfo = `Position: ${this.node.position.x}, ${this.node.position.y}`;
    console.log(debugInfo);
}
```

## Object Pooling Pattern

```typescript
import { _decorator, Component, Node, Prefab, instantiate, NodePool } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('BulletPool')
export class BulletPool extends Component {
    @property(Prefab)
    private readonly bulletPrefab: Prefab | null = null;

    private readonly pool: NodePool = new NodePool();
    private static readonly INITIAL_POOL_SIZE: number = 20;

    // ✅ EXCELLENT: Prewarm pool on initialization
    protected onLoad(): void {
        if (!this.bulletPrefab) {
            throw new Error('BulletPool: bulletPrefab is required');
        }

        for (let i = 0; i < BulletPool.INITIAL_POOL_SIZE; i++) {
            const bullet = instantiate(this.bulletPrefab);
            this.pool.put(bullet);
        }
    }

    // ✅ EXCELLENT: Get from pool (no allocation if available)
    public getBullet(): Node {
        let bullet: Node;

        if (this.pool.size() > 0) {
            bullet = this.pool.get()!;
        } else {
            // Only allocate if pool is empty
            if (!this.bulletPrefab) {
                throw new Error('BulletPool: bulletPrefab is required');
            }
            bullet = instantiate(this.bulletPrefab);
        }

        bullet.active = true;
        return bullet;
    }

    // ✅ EXCELLENT: Return to pool (no deallocation)
    public returnBullet(bullet: Node): void {
        bullet.active = false;
        this.pool.put(bullet);
    }

    // ✅ EXCELLENT: Clear pool on cleanup
    protected onDestroy(): void {
        this.pool.clear();
    }
}

// Usage in game
@ccclass('Gun')
export class Gun extends Component {
    private readonly bulletPool!: BulletPool;

    public shoot(): void {
        // ✅ GOOD: Get from pool instead of instantiate
        const bullet = this.bulletPool.getBullet();
        bullet.setPosition(this.node.position);

        // Set up bullet with timeout to return to pool
        this.scheduleOnce(() => {
            this.bulletPool.returnBullet(bullet);
        }, 3.0);
    }
}

// ❌ WRONG: Creating new instances every time
public shoot(): void {
    // Allocates and deallocates constantly
    const bullet = instantiate(this.bulletPrefab!);
    bullet.setPosition(this.node.position);

    this.scheduleOnce(() => {
        bullet.destroy(); // Triggers garbage collection
    }, 3.0);
}
```

## Caching Expensive Operations

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('EnemyManager')
export class EnemyManager extends Component {
    @property([Node])
    private readonly enemyNodes: Node[] = [];

    // ✅ EXCELLENT: Cache component references
    private readonly enemyControllers: EnemyController[] = [];
    private cachedActiveEnemies: EnemyController[] = [];
    private activeEnemiesDirty: boolean = true;

    protected onLoad(): void {
        // Cache component references on initialization
        for (const node of this.enemyNodes) {
            const controller = node.getComponent(EnemyController);
            if (controller) {
                this.enemyControllers.push(controller);
            }
        }
    }

    // ✅ EXCELLENT: Mark cache as dirty instead of recalculating
    public onEnemyStateChanged(): void {
        this.activeEnemiesDirty = true;
    }

    // ✅ EXCELLENT: Lazy recalculation only when needed
    public getActiveEnemies(): EnemyController[] {
        if (this.activeEnemiesDirty) {
            this.cachedActiveEnemies = this.enemyControllers.filter(e => e.isActive);
            this.activeEnemiesDirty = false;
        }
        return this.cachedActiveEnemies;
    }

    protected update(dt: number): void {
        // ✅ GOOD: Use cached active enemies
        const activeEnemies = this.getActiveEnemies();

        for (const enemy of activeEnemies) {
            enemy.update(dt);
        }
    }
}

// ❌ WRONG: Finding components every frame
protected update(dt: number): void {
    for (const node of this.enemyNodes) {
        const controller = node.getComponent(EnemyController); // Expensive lookup!
        if (controller?.isActive) {
            controller.update(dt);
        }
    }
}

// ❌ WRONG: Filtering every frame
protected update(dt: number): void {
    const activeEnemies = this.enemyControllers.filter(e => e.isActive); // Allocates array every frame!
    for (const enemy of activeEnemies) {
        enemy.update(dt);
    }
}
```

## Throttling Expensive Operations

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('AIController')
export class AIController extends Component {
    private frameCount: number = 0;
    private static readonly AI_UPDATE_INTERVAL: number = 10; // Every 10 frames
    private static readonly PATHFINDING_INTERVAL: number = 60; // Every 60 frames (1 second at 60fps)

    // ✅ EXCELLENT: Update AI every N frames, not every frame
    protected update(dt: number): void {
        this.frameCount++;

        // Run expensive AI logic every 10 frames instead of every frame
        if (this.frameCount % AIController.AI_UPDATE_INTERVAL === 0) {
            this.updateAIDecision();
        }

        // Run very expensive pathfinding every 60 frames (1 second)
        if (this.frameCount % AIController.PATHFINDING_INTERVAL === 0) {
            this.recalculatePath();
        }

        // Cheap operations can run every frame
        this.moveTowardsTarget(dt);
    }

    private updateAIDecision(): void {
        // Expensive: Check all enemies, evaluate threats, etc.
    }

    private recalculatePath(): void {
        // Very expensive: A* pathfinding
    }

    private moveTowardsTarget(dt: number): void {
        // Cheap: Simple movement
    }
}

// ❌ WRONG: Expensive operations every frame
protected update(dt: number): void {
    this.recalculatePath(); // A* pathfinding 60 times per second!
    this.updateAIDecision(); // Complex AI logic 60 times per second!
    this.moveTowardsTarget(dt);
}
```

## Time-Based Throttling

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('PerformanceMonitor')
export class PerformanceMonitor extends Component {
    private lastUpdateTime: number = 0;
    private static readonly UPDATE_INTERVAL: number = 1.0; // 1 second

    // ✅ EXCELLENT: Time-based throttling
    protected update(dt: number): void {
        const currentTime = Date.now() / 1000;

        if (currentTime - this.lastUpdateTime >= PerformanceMonitor.UPDATE_INTERVAL) {
            this.performExpensiveOperation();
            this.lastUpdateTime = currentTime;
        }
    }

    private performExpensiveOperation(): void {
        // Expensive operation that runs once per second
    }
}

// Alternative using scheduleOnce
@ccclass('TimerBased')
export class TimerBased extends Component {
    private static readonly CHECK_INTERVAL: number = 2.0; // 2 seconds

    protected start(): void {
        this.scheduleCheckRecurring();
    }

    private scheduleCheckRecurring(): void {
        this.performCheck();
        this.scheduleOnce(this.scheduleCheckRecurring, TimerBased.CHECK_INTERVAL);
    }

    private performCheck(): void {
        // Expensive check operation
    }
}
```

## Avoid Expensive Lookups

```typescript
import { _decorator, Component, Node, find } from 'cc';
const { ccclass } = _decorator;

@ccclass('GameManager')
export class GameManager extends Component {
    // ✅ EXCELLENT: Cache references in onLoad
    private uiRootNode!: Node;
    private playerNode!: Node;
    private enemyNodes: Node[] = [];

    protected onLoad(): void {
        // Cache node references once
        const uiRoot = find('Canvas/UI');
        if (!uiRoot) {
            throw new Error('GameManager: UI root not found');
        }
        this.uiRootNode = uiRoot;

        const player = find('Canvas/Player');
        if (!player) {
            throw new Error('GameManager: Player not found');
        }
        this.playerNode = player;

        // Cache array of enemy nodes
        const enemyParent = find('Canvas/Enemies');
        if (enemyParent) {
            this.enemyNodes = enemyParent.children.slice();
        }
    }

    protected update(dt: number): void {
        // ✅ GOOD: Use cached references
        this.updatePlayer(this.playerNode, dt);
        this.updateEnemies(this.enemyNodes, dt);
    }
}

// ❌ WRONG: Finding nodes every frame
protected update(dt: number): void {
    const player = find('Canvas/Player'); // Expensive search every frame!
    const enemies = find('Canvas/Enemies')?.children; // Expensive search every frame!

    if (player) {
        this.updatePlayer(player, dt);
    }
    if (enemies) {
        this.updateEnemies(enemies, dt);
    }
}

// ❌ WRONG: getComponent every frame
protected update(dt: number): void {
    const playerController = this.playerNode.getComponent(PlayerController); // Expensive lookup!
    playerController?.update(dt);
}

// ✅ BETTER: Cache component reference
private playerController!: PlayerController;

protected onLoad(): void {
    const controller = this.playerNode.getComponent(PlayerController);
    if (!controller) {
        throw new Error('PlayerController not found');
    }
    this.playerController = controller;
}

protected update(dt: number): void {
    this.playerController.update(dt);
}
```

## String Concatenation Performance

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('DebugDisplay')
export class DebugDisplay extends Component {
    // ✅ EXCELLENT: Use template literals for readability
    public getDebugInfo(player: Player): string {
        return `Player: ${player.name}, HP: ${player.health}/${player.maxHealth}, Level: ${player.level}`;
    }

    // ✅ EXCELLENT: Build strings efficiently with array join (for large strings)
    public generateReport(players: Player[]): string {
        const lines: string[] = [];
        lines.push('=== Player Report ===');

        for (const player of players) {
            lines.push(`${player.name}: Level ${player.level}, HP ${player.health}`);
        }

        lines.push('=== End Report ===');
        return lines.join('\n');
    }

    // ✅ EXCELLENT: Avoid string operations in update loop
    private debugText: string = '';
    private frameCount: number = 0;

    protected update(dt: number): void {
        this.frameCount++;

        // Only update debug text every 30 frames
        if (this.frameCount % 30 === 0) {
            this.debugText = this.generateDebugText();
        }
    }
}

// ❌ WRONG: String concatenation in loop
public generateReport(players: Player[]): string {
    let report = '=== Player Report ===\n';

    for (const player of players) {
        report += `${player.name}: Level ${player.level}\n`; // Allocates new string each iteration
    }

    report += '=== End Report ===';
    return report;
}

// ❌ WRONG: Building strings in update
protected update(dt: number): void {
    this.debugText = `FPS: ${1/dt}, Position: ${this.node.position}`; // Allocates every frame
}
```

## Number Operations Performance

```typescript
import { _decorator, Component } from 'cc';
const { ccclass } = _decorator;

@ccclass('MathOptimizations')
export class MathOptimizations extends Component {
    // ✅ EXCELLENT: Use multiplication instead of division
    private static readonly INV_FRAME_RATE: number = 1 / 60;

    public calculateTimedValue(value: number): number {
        return value * MathOptimizations.INV_FRAME_RATE; // Faster than value / 60
    }

    // ✅ EXCELLENT: Use bitwise operations for integer math
    public fastFloor(value: number): number {
        return value | 0; // Faster than Math.floor for positive numbers
    }

    public isPowerOfTwo(value: number): boolean {
        return (value & (value - 1)) === 0; // Faster than logarithm check
    }

    // ✅ EXCELLENT: Cache expensive math operations
    private readonly sinCache: Map<number, number> = new Map();

    public getCachedSin(angle: number): number {
        if (!this.sinCache.has(angle)) {
            this.sinCache.set(angle, Math.sin(angle));
        }
        return this.sinCache.get(angle)!;
    }

    // ✅ EXCELLENT: Use squared distance to avoid sqrt
    public isWithinRange(pos1: Vec3, pos2: Vec3, range: number): boolean {
        const dx = pos2.x - pos1.x;
        const dy = pos2.y - pos1.y;
        const dz = pos2.z - pos1.z;
        const distSquared = dx * dx + dy * dy + dz * dz;
        const rangeSquared = range * range;
        return distSquared <= rangeSquared; // No sqrt needed
    }
}

// ❌ WRONG: Using expensive operations
public isWithinRange(pos1: Vec3, pos2: Vec3, range: number): boolean {
    const distance = Vec3.distance(pos1, pos2); // Uses sqrt internally
    return distance <= range;
}

// ❌ WRONG: Division in hot path
protected update(dt: number): void {
    const value = this.baseValue / 60; // Division is slower than multiplication
}
```

## Memory Management Best Practices

```typescript
import { _decorator, Component, Node } from 'cc';
const { ccclass } = _decorator;

@ccclass('ResourceManager')
export class ResourceManager extends Component {
    private readonly loadedAssets: Map<string, Asset> = new Map();
    private readonly nodeReferences: Set<Node> = new Set();

    // ✅ EXCELLENT: Clear references on cleanup
    protected onDestroy(): void {
        // Clear maps and sets
        this.loadedAssets.clear();
        this.nodeReferences.clear();

        // Remove event listeners
        this.node.off(Node.EventType.TOUCH_START);
    }

    // ✅ EXCELLENT: Remove unused assets
    public unloadAsset(assetId: string): void {
        const asset = this.loadedAssets.get(assetId);
        if (asset) {
            asset.decRef(); // Release reference
            this.loadedAssets.delete(assetId);
        }
    }

    // ✅ EXCELLENT: Weak references for caches
    private readonly weakNodeCache: WeakMap<Node, CachedData> = new WeakMap();

    public getCachedData(node: Node): CachedData | undefined {
        return this.weakNodeCache.get(node);
    }

    public setCachedData(node: Node, data: CachedData): void {
        this.weakNodeCache.set(node, data);
        // Node is garbage collected → cache entry is automatically removed
    }
}

// ❌ WRONG: Memory leaks
protected onDestroy(): void {
    // Forgot to clear references - memory leak!
    // this.loadedAssets.clear();
    // this.nodeReferences.clear();
}

// ❌ WRONG: Strong references prevent garbage collection
private readonly nodeCache: Map<Node, CachedData> = new Map();
// Nodes are never garbage collected even when destroyed
```

## Summary: Performance Checklist

**Critical for playable ads (<5MB, <10 DrawCalls):**

- [ ] Zero allocations in update() (preallocate and reuse)
- [ ] Object pooling for frequently created/destroyed objects
- [ ] Cache component and node references (no getComponent in update)
- [ ] Throttle expensive operations (every N frames, not every frame)
- [ ] Avoid string operations in hot paths
- [ ] Use multiplication instead of division
- [ ] Use squared distance instead of distance (avoid sqrt)
- [ ] Clear references in onDestroy() to prevent memory leaks
- [ ] Use WeakMap for caches that should be garbage collected
- [ ] Array.length = 0 to clear arrays (don't create new arrays)

**Performance is critical for smooth 60fps playable ads.**

```

### references/framework/size-optimization.md

```markdown
# Bundle Size Optimization (<5MB Target)

## Texture Compression (Biggest Impact)

**Target: <5MB total bundle size for playable ads**

Texture compression is the single biggest factor in bundle size. Enable compression for all platforms.

### Build Settings Configuration

```json
// Project Settings → Build → Web Mobile
{
    "textureCompression": {
        "web-mobile": "auto",     // Auto-select best compression
        "web-desktop": "auto",
        "android": "etc1",        // ETC1 for Android
        "ios": "pvrtc"            // PVRTC for iOS
    },
    "packAutoAtlas": true,       // Auto-generate atlases
    "md5Cache": false,           // Disable for smaller output
    "inlineSpriteFrames": true   // Reduce file count
}
```

### Texture Size Guidelines

```typescript
// ✅ EXCELLENT: Optimal texture sizes for playables

// Character sprites: 512x512 max (often 256x256 is enough)
// UI elements: 256x256 max
// Backgrounds: 1024x1024 max (or use tiled smaller textures)
// Effects: 128x128 or 256x256
// Icons: 64x64 or 128x128

// ❌ WRONG: Oversized textures
// - 2048x2048 for small character sprites
// - High-res images that won't be seen at that scale
// Use appropriate sizes for display resolution
```

## Asset Optimization Priority

### 1. Textures (50-60% of bundle)

```typescript
// ✅ EXCELLENT: Sprite atlas configuration
// Combine multiple small textures into single atlas
// - Character animations: single atlas
// - UI elements: single atlas
// - Effects: single atlas

// Auto-atlas settings (Project Settings):
// - Max Width: 2048
// - Max Height: 2048
// - Padding: 2
// - Allow Rotation: true
// - Force Square: false

// ❌ WRONG: Individual texture files
// Each separate texture = separate HTTP request + worse compression
```

### 2. Audio (20-30% of bundle)

```typescript
// ✅ EXCELLENT: Audio optimization
// - Format: MP3 or OGG (not WAV)
// - Background music: 128kbps max, short loops (<30 seconds)
// - Sound effects: 64kbps, very short (<2 seconds)

// ❌ WRONG: Uncompressed audio
// - WAV files: 10-20x larger than compressed
// - Long music tracks: use short loops
// - High bitrate: 320kbps unnecessary for playables
```

### 3. Code (5-10% of bundle)

```typescript
// ✅ EXCELLENT: Code minification
// rollup.config.js or webpack.config.js
export default {
    mode: 'production',
    optimization: {
        minimize: true,
        minimizer: [
            new TerserPlugin({
                terserOptions: {
                    compress: {
                        drop_console: true,      // Remove console.log
                        drop_debugger: true,     // Remove debugger
                        dead_code: true,         // Remove unreachable code
                        unused: true             // Remove unused variables
                    },
                    mangle: { toplevel: true }   // Shorten variable names
                }
            })
        ]
    }
};

// ✅ EXCELLENT: Import only what you need
import { Vec3, Node } from 'cc'; // Specific imports

// ❌ WRONG: Import entire module
import * as cc from 'cc'; // Imports everything (larger bundle)
```

### 4. Fonts (5-10% of bundle)

```typescript
// ✅ EXCELLENT: Bitmap fonts for playables
// - Pre-render characters to texture
// - Include only needed characters: "0123456789,."
// - Much smaller than TTF fonts

// Create bitmap font:
// 1. Use BMFont tool or online generator
// 2. Include only needed characters
// 3. Export as .fnt + .png
// 4. Import to Cocos Creator as BitmapFont

// ❌ WRONG: TTF fonts
// - Large file size (hundreds of KB)
// - System fonts vary by platform
// - Use bitmap fonts for playables
```

## Build Configuration for Minimum Size

```json
// Project Settings → Build → Web Mobile

{
    // Bundle settings
    "inlineSpriteFrames": true,      // Reduce file count
    "md5Cache": false,               // Disable MD5 in filenames
    "mainBundleCompressionType": "default",
    "mainBundleIsRemote": false,

    // Code optimization
    "debug": false,                  // Disable debug mode
    "sourceMaps": false,             // Disable source maps
    "separateEngine": false,         // Include engine in bundle

    // Texture optimization
    "packAutoAtlas": true,           // Auto-generate atlases
    "textureCompression": "auto",    // Enable compression

    // Feature exclusions
    "excludeScenes": [],             // Remove unused scenes
    "useBuiltinServer": false        // Playables don't need server
}
```

## Removing Unused Assets

```typescript
// ✅ EXCELLENT: Regular asset cleanup

// 1. Use Cocos Creator's "Find References" feature
// - Right-click asset → Find References
// - Delete if no references found

// 2. Check build output
// - Review build folder size after each build
// - Identify largest files
// - Remove unused assets

// 3. Remove debug assets before build
// - Test levels
// - Debug sprites and textures
// - Development-only tools
// - Temporary assets

// ❌ WRONG: Keep all assets "just in case"
// - Unused textures add unnecessary size
// - Clean up regularly during development
```

## Real-World Example: Size Breakdown

```typescript
// Target: <5MB playable bundle
// Typical optimized breakdown:

// Textures: 2.5MB (50%)
// - Character sprites: 800KB (sprite atlas, ETC1 compressed)
// - UI elements: 600KB (sprite atlas, ETC1 compressed)
// - Background: 700KB (1024x1024, compressed, or tiled)
// - Effects: 400KB (sprite atlas, compressed)

// Code: 400KB (8%)
// - Cocos engine: 200KB (minified, tree-shaken)
// - Game logic: 200KB (minified, dead code removed)

// Audio: 1.5MB (30%)
// - Background music: 1MB (MP3, 128kbps, 60s loop)
// - Sound effects: 500KB (MP3, 64kbps, 10 short clips)

// Other: 600KB (12%)
// - Bitmap fonts: 200KB (only needed characters)
// - Config files: 100KB (JSON, minified)
// - Misc assets: 300KB

// Total: 5.0MB (within ad network limit)

// ❌ BAD EXAMPLE: Unoptimized (12MB+)
// - Textures: 8MB (no compression, individual files)
// - Audio: 3MB (WAV files, long tracks)
// - Code: 800KB (no minification, dev mode)
// - Fonts: 400KB (TTF fonts)
// Total: 12.2MB (rejected by ad networks!)
```

## Monitoring Bundle Size

```bash
# ✅ EXCELLENT: Monitor size regularly

# 1. Check build output size
du -sh build/web-mobile/

# 2. Break down by asset type
du -sh build/web-mobile/assets/
du -sh build/web-mobile/src/

# 3. Find largest files
find build/web-mobile -type f -exec du -h {} \; | sort -rh | head -20

# 4. Set size budget in CI/CD
# Fail build if bundle >5MB
# Alert if bundle >4.5MB (warning threshold)
```

## Lazy Loading Pattern (Optional)

```typescript
import { _decorator, Component, resources, Prefab } from 'cc';
const { ccclass } = _decorator;

@ccclass('LazyLoader')
export class LazyLoader extends Component {
    // ✅ EXCELLENT: Load levels on demand
    // For playables with multiple levels, load only current level

    private levelPrefabs: Map<number, Prefab> = new Map();

    public async loadLevel(levelId: number): Promise<void> {
        if (this.levelPrefabs.has(levelId)) {
            return; // Already loaded
        }

        const path = `levels/level_${levelId}`;
        return new Promise((resolve, reject) => {
            resources.load(path, Prefab, (err, prefab) => {
                if (err) {
                    reject(err);
                    return;
                }
                this.levelPrefabs.set(levelId, prefab);
                resolve();
            });
        });
    }

    // ✅ GOOD: Unload previous level
    public async switchLevel(fromLevel: number, toLevel: number): Promise<void> {
        const prevPrefab = this.levelPrefabs.get(fromLevel);
        if (prevPrefab) {
            prevPrefab.decRef();
            this.levelPrefabs.delete(fromLevel);
        }
        await this.loadLevel(toLevel);
    }
}

// ❌ WRONG: Loading all levels at start
// - Increases initial bundle size
// - Longer load time
// - Only load what's needed for first level
```

## Size Optimization Checklist

**🔴 Critical (Biggest Impact):**
- [ ] Enable texture compression (auto or platform-specific)
- [ ] Use sprite atlases (combine textures)
- [ ] Reduce texture dimensions (512x512 max for characters)
- [ ] Compress audio (MP3/OGG, 64-128kbps)
- [ ] Remove unused assets

**🟡 Important:**
- [ ] Enable code minification (drop_console, dead_code removal)
- [ ] Use bitmap fonts (not TTF)
- [ ] Disable source maps in production
- [ ] Import specific modules (tree shaking)
- [ ] Remove debug/test assets

**🟢 Nice to Have:**
- [ ] Lazy load levels (if multiple levels)
- [ ] Monitor bundle size in CI/CD
- [ ] Set size budget alerts (<5MB hard limit)
- [ ] Track size trends over time

**Target: <5MB total bundle size for playable ad approval.**

```

### references/review/architecture-review.md

```markdown
# Cocos Creator Architecture Review

This review focuses on Cocos Creator-specific architectural issues including component lifecycle violations, event management problems, and performance issues specific to playable ads.

## Component Lifecycle Violations

### Accessing Components in onLoad

```typescript
// ❌ CRITICAL: Accessing other components in onLoad
@ccclass('BadLifecycle')
export class BadLifecycle extends Component {
    @property(Node)
    private playerNode: Node | null = null;

    protected onLoad(): void {
        // WRONG: Other components may not be loaded yet
        const controller = this.playerNode!.getComponent(PlayerController);
        controller.initialize(); // May be undefined!
    }
}

// ✅ CORRECT: Access components in start()
@ccclass('GoodLifecycle')
export class GoodLifecycle extends Component {
    @property(Node)
    private readonly playerNode: Node | null = null;

    private playerController!: PlayerController;

    protected onLoad(): void {
        if (!this.playerNode) {
            throw new Error('GoodLifecycle: playerNode is required');
        }
    }

    protected start(): void {
        const controller = this.playerNode!.getComponent(PlayerController);
        if (!controller) {
            throw new Error('PlayerController not found');
        }
        this.playerController = controller;
        this.playerController.initialize();
    }
}

// Severity: 🔴 Critical
// Impact: Undefined behavior, crashes
// Fix: Move component access from onLoad() to start()
```

### Event Listener Memory Leaks

```typescript
// ❌ CRITICAL: Not unregistering event listeners
@ccclass('EventLeakBad')
export class EventLeakBad extends Component {
    protected onEnable(): void {
        this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
        EventManager.on(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
    }

    // MISSING: onDisable() - memory leak!
}

// ✅ CORRECT: Always unregister in onDisable
@ccclass('EventLeakGood')
export class EventLeakGood extends Component {
    protected onEnable(): void {
        this.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
        EventManager.on(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
    }

    protected onDisable(): void {
        this.node.off(Node.EventType.TOUCH_START, this.onTouchStart, this);
        EventManager.off(GameEvent.SCORE_CHANGED, this.onScoreChanged, this);
    }

    private onTouchStart(event: EventTouch): void {}
    private onScoreChanged(data: ScoreChangedEvent): void {}
}

// Severity: 🔴 Critical
// Impact: Memory leaks, performance degradation
// Fix: Always implement onDisable() to unregister listeners
```

### Missing Required Reference Validation

```typescript
// ❌ CRITICAL: No validation of required references
@ccclass('NoValidation')
export class NoValidation extends Component {
    @property(Node)
    private targetNode: Node | null = null;

    protected onLoad(): void {
        this.targetNode!.setPosition(0, 0, 0); // Will crash if null
    }
}

// ✅ CORRECT: Validate in onLoad
@ccclass('WithValidation')
export class WithValidation extends Component {
    @property(Node)
    private readonly targetNode: Node | null = null;

    protected onLoad(): void {
        if (!this.targetNode) {
            throw new Error('WithValidation: targetNode is required');
        }
        this.targetNode.setPosition(0, 0, 0);
    }
}

// Severity: 🔴 Critical
// Impact: Runtime crashes with unhelpful errors
// Fix: Validate all required @property references in onLoad()
```

### Resource Cleanup Violations

```typescript
// ❌ CRITICAL: Not releasing resources
@ccclass('ResourceLeakBad')
export class ResourceLeakBad extends Component {
    private readonly loadedAssets: Map<string, Asset> = new Map();

    protected onDestroy(): void {
        // MISSING: decRef() and clear()
    }
}

// ✅ CORRECT: Complete cleanup
@ccclass('ResourceLeakGood')
export class ResourceLeakGood extends Component {
    private readonly loadedAssets: Map<string, Asset> = new Map();

    protected onDestroy(): void {
        for (const [id, asset] of this.loadedAssets) {
            asset.decRef();
        }
        this.loadedAssets.clear();
        this.unscheduleAllCallbacks();
    }
}

// Severity: 🔴 Critical
// Impact: Memory leaks
// Fix: Release resources and clear collections in onDestroy()
```

## Performance Violations (Playable-Specific)

### Allocations in Update Loop

```typescript
// ❌ CRITICAL: Allocating every frame
@ccclass('UpdateAllocationsBad')
export class UpdateAllocationsBad extends Component {
    protected update(dt: number): void {
        const pos = this.node.position.clone(); // 60 allocations/second
        pos.y += 10 * dt;
        this.node.setPosition(pos);
    }
}

// ✅ CORRECT: Preallocate and reuse
@ccclass('UpdateAllocationsGood')
export class UpdateAllocationsGood extends Component {
    private readonly tempVec3: Vec3 = new Vec3();

    protected update(dt: number): void {
        this.node.getPosition(this.tempVec3);
        this.tempVec3.y += 10 * dt;
        this.node.setPosition(this.tempVec3);
    }
}

// Severity: 🔴 Critical
// Impact: Frame drops, GC pauses
// Fix: Preallocate objects, reuse in update
```

### Component Lookup in Update

```typescript
// ❌ IMPORTANT: getComponent in update
@ccclass('ComponentLookupBad')
export class ComponentLookupBad extends Component {
    @property(Node)
    private playerNode: Node | null = null;

    protected update(dt: number): void {
        const controller = this.playerNode!.getComponent(PlayerController); // Expensive!
        controller?.update(dt);
    }
}

// ✅ CORRECT: Cache component reference
@ccclass('ComponentLookupGood')
export class ComponentLookupGood extends Component {
    @property(Node)
    private readonly playerNode: Node | null = null;

    private playerController!: PlayerController;

    protected start(): void {
        if (!this.playerNode) {
            throw new Error('playerNode is required');
        }
        const controller = this.playerNode.getComponent(PlayerController);
        if (!controller) {
            throw new Error('PlayerController not found');
        }
        this.playerController = controller;
    }

    protected update(dt: number): void {
        this.playerController.update(dt);
    }
}

// Severity: 🟡 Important
// Impact: Significant performance overhead
// Fix: Cache component references in start()
```

## Summary: Architecture Review Checklist

**🔴 Critical (Must Fix):**
- [ ] No component access in onLoad() (use start())
- [ ] All event listeners unregistered in onDisable()
- [ ] Required @property references validated in onLoad()
- [ ] Resources released in onDestroy()
- [ ] Zero allocations in update() loop
- [ ] readonly used for @property fields not reassigned

**🟡 Important (Should Fix):**
- [ ] Component references cached (not getComponent in update)
- [ ] Expensive operations throttled (every N frames)
- [ ] Node references cached (not find() in update)
- [ ] Arrays cleared with .length = 0 (not new array)

**🟢 Nice to Have:**
- [ ] Object pooling for frequent spawn/despawn
- [ ] WeakMap for auto-cleanup caches
- [ ] Disposable pattern for subscription management

**Always fix lifecycle and event cleanup issues - they cause crashes and memory leaks.**

```

### references/review/quality-review.md

```markdown
# TypeScript Quality Review

This review focuses on TypeScript code quality issues including access modifiers, strict mode compliance, error handling, and code hygiene.

## TypeScript Strict Mode Violations

```typescript
// ❌ CRITICAL: Strict mode disabled
// tsconfig.json
{
    "compilerOptions": {
        "strict": false // Bad!
    }
}

// ✅ CORRECT: Enable strict mode
{
    "compilerOptions": {
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictPropertyInitialization": true
    }
}

// Severity: 🔴 Critical
// Fix: Enable strict mode in tsconfig.json
```

## Access Modifier Violations

```typescript
// ❌ CRITICAL: Missing access modifiers
@ccclass('NoModifiers')
export class NoModifiers extends Component {
    playerNode: Node | null = null; // Implicitly public!
    currentHealth: number = 100;    // Implicitly public!

    updateHealth(value: number) {   // Implicitly public!
        this.currentHealth = value;
    }
}

// ✅ CORRECT: Explicit modifiers
@ccclass('WithModifiers')
export class WithModifiers extends Component {
    @property(Node)
    private readonly playerNode: Node | null = null;

    private currentHealth: number = 100;

    public updateHealth(value: number): void {
        this.currentHealth = value;
    }
}

// Severity: 🔴 Critical
// Fix: Add access modifiers (public/private/protected) to all members
```

## Silent Error Handling

```typescript
// ❌ CRITICAL: Silent failures
@ccclass('SilentErrors')
export class SilentErrors extends Component {
    public getPlayer(id: string): Player | undefined {
        const player = this.players.get(id);
        return player; // Caller doesn't know why it failed
    }
}

// ✅ CORRECT: Throw exceptions
@ccclass('ThrowExceptions')
export class ThrowExceptions extends Component {
    public getPlayer(id: string): Player {
        const player = this.players.get(id);
        if (!player) {
            throw new Error(`Player not found: ${id}`);
        }
        return player;
    }
}

// Severity: 🔴 Critical
// Fix: Throw exceptions for errors, not silent failures
```

## console.log in Production

```typescript
// ❌ CRITICAL: Unconditional console.log
@ccclass('ConsoleLogBad')
export class ConsoleLogBad extends Component {
    protected update(dt: number): void {
        console.log('Update'); // In production build!
    }
}

// ✅ CORRECT: Conditional or removed
@ccclass('ConsoleLogGood')
export class ConsoleLogGood extends Component {
    protected update(dt: number): void {
        if (CC_DEBUG) {
            console.log('Update');
        }
    }
}

// Severity: 🔴 Critical (for playables)
// Impact: Bundle size increase, performance
// Fix: Wrap in CC_DEBUG or remove entirely
```

## Inline Comments Instead of Descriptive Names

```typescript
// ❌ IMPORTANT: Comments explaining unclear code
@ccclass('InlineCommentsBad')
export class InlineCommentsBad extends Component {
    private h: number = 100; // health

    public td(a: number): void { // take damage
        this.h = this.h - a; // subtract
        if (this.h <= 0) { // dead
            this.hd(); // handle death
        }
    }
}

// ✅ CORRECT: Self-explanatory names
@ccclass('InlineCommentsGood')
export class InlineCommentsGood extends Component {
    private currentHealth: number = 100;

    public takeDamage(amount: number): void {
        this.currentHealth -= amount;
        if (this.isDead()) {
            this.handleDeath();
        }
    }

    private isDead(): boolean {
        return this.currentHealth <= 0;
    }

    private handleDeath(): void {
        // Implementation
    }
}

// Severity: 🟡 Important
// Fix: Use descriptive names, remove inline comments
```

## Missing readonly/const

```typescript
// ❌ IMPORTANT: Mutable when should be immutable
@ccclass('MissingReadonly')
export class MissingReadonly extends Component {
    @property(Node)
    private targetNode: Node | null = null; // Should be readonly

    private maxHealth: number = 100; // Should be static readonly
}

// ✅ CORRECT: Use readonly/const
@ccclass('WithReadonly')
export class WithReadonly extends Component {
    @property(Node)
    private readonly targetNode: Node | null = null;

    private static readonly MAX_HEALTH: number = 100;
}

// Severity: 🟡 Important
// Fix: Add readonly to fields not reassigned, use static readonly for constants
```

## Using `any` Type

```typescript
// ❌ IMPORTANT: Using any without justification
@ccclass('UsingAny')
export class UsingAny extends Component {
    private data: any = {}; // Type safety lost

    public processData(input: any): any {
        return input; // No type checking
    }
}

// ✅ CORRECT: Use proper types
interface PlayerData {
    id: string;
    name: string;
    level: number;
}

@ccclass('WithTypes')
export class WithTypes extends Component {
    private data: Map<string, PlayerData> = new Map();

    public processData(input: PlayerData): PlayerData {
        return input;
    }
}

// Severity: 🟡 Important
// Fix: Define proper types and interfaces, avoid `any`
```

## Summary: Quality Review Checklist

**🔴 Critical (Must Fix):**
- [ ] TypeScript strict mode enabled in tsconfig.json
- [ ] All members have access modifiers (public/private/protected)
- [ ] Exceptions thrown for errors (no silent failures)
- [ ] console.log removed or wrapped in CC_DEBUG
- [ ] No nullable warnings (proper null handling)

**🟡 Important (Should Fix):**
- [ ] readonly used for non-reassigned fields
- [ ] const used for constants (not let)
- [ ] No inline comments (self-explanatory code)
- [ ] Optional chaining (?.) for safe access
- [ ] Nullish coalescing (??) for defaults
- [ ] No `any` types without justification

**🟢 Nice to Have:**
- [ ] Arrow functions for callbacks
- [ ] Destructuring for cleaner code
- [ ] Type guards for type safety
- [ ] Utility types (Partial, Required, etc.)

**Code quality is the foundation - fix these issues before performance optimization.**

```

### references/review/performance-review.md

```markdown
# Playable Ads Performance Review

This review focuses on performance issues specific to playable ads including DrawCall optimization, bundle size, update loop performance, and resource management.

## DrawCall Explosion (Critical for Playables)

**Target: <10 DrawCalls for 60fps playables**

```typescript
// ❌ CRITICAL: Individual textures (multiple DrawCalls)
@ccclass('DrawCallBad')
export class DrawCallBad extends Component {
    @property(SpriteFrame)
    private sprite1: SpriteFrame | null = null; // DrawCall 1

    @property(SpriteFrame)
    private sprite2: SpriteFrame | null = null; // DrawCall 2

    @property(SpriteFrame)
    private sprite3: SpriteFrame | null = null; // DrawCall 3

    // 10 sprites = 10 DrawCalls! (BAD)
}

// ✅ CORRECT: Sprite atlas (single DrawCall)
@ccclass('DrawCallGood')
export class DrawCallGood extends Component {
    @property(SpriteAtlas)
    private readonly characterAtlas: SpriteAtlas | null = null; // 1 DrawCall for all
}

// Severity: 🔴 Critical
// Impact: Frame drops, poor performance
// Target: <10 DrawCalls total
// Fix: Use sprite atlases for all sprites
```

## Update Loop Allocations

```typescript
// ❌ CRITICAL: Allocating in update
@ccclass('UpdateAllocationsBad')
export class UpdateAllocationsBad extends Component {
    protected update(dt: number): void {
        // Creates new Vec3 every frame
        const pos = this.node.position.clone(); // 60 allocations/second!
        pos.y += 10 * dt;
        this.node.setPosition(pos);

        // Creates array every frame
        const enemies = this.getAllEnemies().filter(e => e.active); // 60 arrays/second!
    }
}

// ✅ CORRECT: Zero allocations
@ccclass('UpdateAllocationsGood')
export class UpdateAllocationsGood extends Component {
    private readonly tempVec3: Vec3 = new Vec3();
    private readonly activeEnemies: Enemy[] = [];
    private cacheRDirty: boolean = true;

    protected update(dt: number): void {
        // Reuse preallocated vector
        this.node.getPosition(this.tempVec3);
        this.tempVec3.y += 10 * dt;
        this.node.setPosition(this.tempVec3);

        // Use cached array
        const enemies = this.getActiveEnemies();
    }

    private getActiveEnemies(): Enemy[] {
        if (this.cacheDirty) {
            this.activeEnemies.length = 0;
            // Rebuild cache
            this.cacheDirty = false;
        }
        return this.activeEnemies;
    }
}

// Severity: 🔴 Critical
// Impact: Frame drops, GC pauses
// Fix: Preallocate objects, reuse in update
```

## No Object Pooling

```typescript
// ❌ IMPORTANT: instantiate/destroy in gameplay
@ccclass('NoPoolingBad')
export class NoPoolingBad extends Component {
    public shoot(): void {
        const bullet = instantiate(this.bulletPrefab!); // Allocates
        this.scheduleOnce(() => {
            bullet.destroy(); // GC overhead
        }, 2.0);
    }
}

// ✅ CORRECT: Object pooling
@ccclass('NoPoolingGood')
export class NoPoolingGood extends Component {
    private readonly bulletPool: NodePool = new NodePool();

    protected onLoad(): void {
        // Prewarm pool
        for (let i = 0; i < 20; i++) {
            const bullet = instantiate(this.bulletPrefab!);
            this.bulletPool.put(bullet);
        }
    }

    public shoot(): void {
        const bullet = this.bulletPool.get() ?? instantiate(this.bulletPrefab!);
        this.scheduleOnce(() => {
            this.bulletPool.put(bullet);
        }, 2.0);
    }
}

// Severity: 🟡 Important
// Impact: Allocations, GC pauses
// Fix: Implement object pooling for frequent spawn/despawn
```

## Unthrottled Expensive Operations

```typescript
// ❌ IMPORTANT: Expensive operations every frame
@ccclass('UnthrottledBad')
export class UnthrottledBad extends Component {
    protected update(dt: number): void {
        this.recalculatePathfinding(); // A* every frame (60 times/second)!
        this.updateComplexAI();        // Expensive every frame!
    }
}

// ✅ CORRECT: Throttle expensive operations
@ccclass('UnthrottledGood')
export class UnthrottledGood extends Component {
    private frameCount: number = 0;

    protected update(dt: number): void {
        this.frameCount++;

        // Pathfinding once per second
        if (this.frameCount % 60 === 0) {
            this.recalculatePathfinding();
        }

        // AI 6 times per second
        if (this.frameCount % 10 === 0) {
            this.updateComplexAI();
        }

        // Cheap operations every frame
        this.moveTowardsTarget(dt);
    }
}

// Severity: 🟡 Important
// Impact: Poor performance, frame drops
// Fix: Throttle to every N frames (10-60)
```

## Bundle Size >5MB

```typescript
// ❌ CRITICAL: Bundle exceeds playable limit
// Build output: 7.2MB (too large for most ad networks!)

// Common causes:
// 1. Uncompressed textures → Enable compression
// 2. Oversized textures → Reduce to 512x512 max
// 3. Uncompressed audio → Use MP3/OGG at 64-128kbps
// 4. Unused assets → Remove from project
// 5. No code minification → Enable in build settings

// ✅ CORRECT: Optimized to <5MB
// - Enable texture compression (Project Settings)
// - Use sprite atlases (combine textures)
// - Compress audio (64-128kbps)
// - Remove unused assets
// - Enable code minification (drop_console, dead_code)

// Severity: 🔴 Critical
// Impact: Playable rejected by ad networks
// Target: <5MB total bundle
// Fix: Apply size optimization techniques
```

## Loading Resources During Gameplay

```typescript
// ❌ IMPORTANT: Loading during gameplay
@ccclass('LoadingInGameplayBad')
export class LoadingInGameplayBad extends Component {
    protected update(dt: number): void {
        if (this.shouldSpawnEnemy()) {
            // Loading causes frame drop!
            resources.load('sprites/enemy', SpriteFrame, (err, sprite) => {
                this.spawnEnemy(sprite);
            });
        }
    }
}

// ✅ CORRECT: Preload at startup
@ccclass('LoadingInGameplayGood')
export class LoadingInGameplayGood extends Component {
    private enemySprite: SpriteFrame | null = null;

    protected start(): void {
        // Preload once
        resources.load('sprites/enemy', SpriteFrame, (err, sprite) => {
            if (!err) {
                this.enemySprite = sprite;
            }
        });
    }

    protected update(dt: number): void {
        if (this.shouldSpawnEnemy() && this.enemySprite) {
            this.spawnEnemy(this.enemySprite); // Instant, no loading
        }
    }
}

// Severity: 🟡 Important
// Impact: Frame drops during loading
// Fix: Preload all resources at startup
```

## GPU Skinning Disabled

```typescript
// ❌ IMPORTANT: CPU skinning (slower)
@ccclass('CPUSkinningBad')
export class CPUSkinningBad extends Component {
    @property(SkeletalAnimation)
    private skeleton: SkeletalAnimation | null = null;

    protected onLoad(): void {
        // Using default CPU skinning (slower)
    }
}

// ✅ CORRECT: Enable GPU skinning
@ccclass('GPUSkinningGood')
export class GPUSkinningGood extends Component {
    @property(SkeletalAnimation)
    private readonly skeleton: SkeletalAnimation | null = null;

    protected onLoad(): void {
        if (this.skeleton) {
            // GPU handles bone transformations (faster)
            this.skeleton.useBakedAnimation = true;
        }
    }
}

// Severity: 🟢 Nice to Have
// Impact: Better performance for skeletal animations
// Fix: Enable useBakedAnimation for GPU skinning
```

## Summary: Performance Review Checklist

**🔴 Critical (Must Fix):**
- [ ] DrawCall count <10 (use sprite atlases)
- [ ] Zero allocations in update() loop
- [ ] Bundle size <5MB total
- [ ] No loading resources during gameplay

**🟡 Important (Should Fix):**
- [ ] Object pooling for bullets, effects, enemies
- [ ] Expensive operations throttled (every 10-60 frames)
- [ ] Component references cached (not getComponent in update)
- [ ] Node references cached (not find() in update)

**🟢 Nice to Have:**
- [ ] GPU skinning enabled (useBakedAnimation = true)
- [ ] Texture dimensions optimized (512x512 max)
- [ ] Audio compressed (64-128kbps)
- [ ] WeakMap for auto-cleanup caches

**Performance targets: 60fps, <10 DrawCalls, <5MB bundle for playable ads.**

```