원클릭으로
game-engine-architecture
Workflow for building 2D Canvas game components using class-based architecture and Test-Driven Development (TDD).
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Workflow for building 2D Canvas game components using class-based architecture and Test-Driven Development (TDD).
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Run automated WCAG 2.1 AA accessibility audits against local dev servers using Lighthouse CLI and axe-core. Parse JSON reports, identify contrast/label/semantic violations, and output structured remediation with exact code fixes.
Generate images, video, and audio with ComfyUI — install, launch, manage nodes/models, run workflows with parameter injection. Uses the official comfy-cli for lifecycle and direct REST/WebSocket API for execution.
Hermes Kanban multi-agent orchestration — task lifecycle, decomposition playbook, worker pitfalls, Codex lane isolation, and board operations.
Firecrawl gives AI agents and apps fast, reliable web context with strong search, scraping, and interaction tools. One install command sets up three skill segments: live CLI tools, app-integration build skills, and outcome-focused workflow skills. Route the reader to the right usage path after install.
Guide for evaluating AABB collision logic on a live development server using browser tools.
GitHub operations via gh CLI and REST API: auth, repos, PRs, issues, code review, codebase inspection.
| name | game-engine-architecture |
| category | game-development |
| description | Workflow for building 2D Canvas game components using class-based architecture and Test-Driven Development (TDD). |
This skill governs developing game mechanics within the SF2 project (src/js/, game.js, fighter.js, combat.js). Core philosophy: encapsulation and determinism.
/tests/ that verifies the desired live game state change before touching core logic.src/js/ encapsulating state and behavior.game.js via the fixed-timestep deterministic update loop.Physics always runs at exactly 1/60s regardless of display refresh rate.
engine.js → captures real wall-clock delta each RAF frame → game.tick(realDt)
game.tick → accumulates real time (capped at 0.25s), drains while (acc >= FIXED_DT) → _fixedUpdate(FIXED_DT)
_fixedUpdate → the true physics tick — always receives exactly 1/60
const FIXED_DT = 1 / 60; // 16.66ms — strict physics slice
const MAX_FRAME_TIME = 0.25; // spiral-of-death cap
update() calls must use FIXED_DT, not raw frame delta.MAX_FRAME_TIME, a tab switch causes hundreds of catch-up ticks.frameCount increments once per _fixedUpdate, giving deterministic integer indices for rollback/netcode.update() → tick() delegation — legacy update(dt) delegates to tick(dt). Tests calling update(N) still work._fixedUpdate()Correct order: aiEngine.update(dt) → getVirtualKeys() → handleInput(aiKeys) → initiateAttack() → entity updates → projectile update/collide/cull → render.
Reordering (e.g., handleInput before aiEngine.update) breaks AI input or causes aiKeys to be undefined.
Detailed implementation patterns for each subsystem live in the reference files:
| Module | Reference |
|---|---|
| Special Moves & Input Buffer | references/special-moves-input-buffer.md |
| Projectile System | references/projectile-system.md |
| AI Opponent Engine | references/ai-opponent-engine.md |
| Rollback Netcode & Serialization | references/fixed-timestep-serialization.md |
| Training Mode (Pause / Frame Advance / HUD) | references/training-mode.md |
| Playwright Testing Patterns | references/playwright-game-testing.md |
Two flags on Game: this.isPaused and this.stepRequested. P toggles pause; . requests single frame step while paused.
render() called even when paused — HUD must remain visiblestepRequested consumed immediately after single _fixedUpdateengine.js still calls tick() every frame_fixedUpdate() bypasses pause guard — it always runs. Use tick() (not _fixedUpdate()) for pause tests.tick() Logictick(realDt) {
if (realDt > MAX_FRAME_TIME) realDt = MAX_FRAME_TIME;
this.accumulator += realDt;
if (this.isPaused) {
if (this.stepRequested) {
this._fixedUpdate(FIXED_DT);
this.accumulator -= FIXED_DT;
this.stepRequested = false;
}
return; // render() still called by caller
}
while (this.accumulator >= FIXED_DT) {
this._fixedUpdate(FIXED_DT);
this.accumulator -= FIXED_DT;
}
}
_fixedUpdate() ignores isPaused — calling it directly bypasses pause. For pause tests, use g.tick().render() must be called when paused — otherwise HUD vanishesstepRequested must be consumed — failing to reset causes auto-playx, y, not cached positionsstate = 'HITSTUN' with hitstunTimer locks player inputs.handleInput with if (this.state === 'HITSTUN') return;.combat.checkHit: set defender.state = 'HITSTUN', defender.hitstunTimer = 0.25, apply knockback.Fighter.health, hit flag hasBeenHitThisAttack.Game.render before entities: red bg, green fg scaled to health %.rgba(234, 179, 8, 0.3) (transparent yellow).rgba(239, 68, 68, 0.4) (transparent red).checkHit(attacker, defender) in combat.js.aiEngine.update() → getVirtualKeys() → handleInput() → initiateAttack(). Always.update() vs _fixedUpdate() — update() goes through accumulator; _fixedUpdate() is a raw physics tick. Tests needing exact frame control use _fixedUpdate() directly.page.evaluate() call for input buffer tests, to prevent RAF from injecting duplicate frames.page.evaluate() calls.git add . && git commit -m "agent: [concise description]".hasBeenHitThisAttack on attacker, not defender — prevents multi-hit within same attack's active window. Reset in initiateAttack(), never per-frame in game loop.isHoldingBack direction — attackerOnRight ? 'arrowleft' : 'arrowright' for P2. Inverted ternary causes silent blocking failure.handleCombatState phase-before-decrement — phase is computed from current timer BEFORE decrementing, so ACTIVE starts one tick later than naive math.aiEngine.update() clears keys before getVirtualKeys() is read. To inject test inputs for P2, override getVirtualKeys() rather than setting _keyDown directly.'BACKWARD' not 'WALK_BACKWARD' in isHoldingBack state check.