Makelore 2.0 initial clean snapshot
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
# Practical Game Design Standards
|
||||
|
||||
Use this reference to turn a game idea into decisions that can be implemented and tested. Treat the GDD as a living agreement about player experience, not as a pitch deck or a complete prediction of the final game.
|
||||
|
||||
## Contents
|
||||
|
||||
1. [Build a traceable design chain](#1-build-a-traceable-design-chain)
|
||||
2. [Set pillars and non-goals](#2-set-pillars-and-non-goals)
|
||||
3. [Specify systems before building them](#3-specify-systems-before-building-them)
|
||||
4. [Protect feel, readability, and fairness](#4-protect-feel-readability-and-fairness)
|
||||
5. [Balance with explicit targets](#5-balance-with-explicit-targets)
|
||||
6. [Plan progression and content](#6-plan-progression-and-content)
|
||||
7. [Design UX and accessibility with the game](#7-design-ux-and-accessibility-with-the-game)
|
||||
8. [Control scope, risk, and playtesting](#8-control-scope-risk-and-playtesting)
|
||||
9. [Review designs and propagate changes](#9-review-designs-and-propagate-changes)
|
||||
|
||||
## 1. Build a traceable design chain
|
||||
|
||||
Connect every important feature through this chain:
|
||||
|
||||
**Fantasy → Verbs → Mechanics → Rules → Feedback → Loop**
|
||||
|
||||
| Link | Question to answer | Useful output |
|
||||
|---|---|---|
|
||||
| Fantasy | Who is the player, and what should it feel like to be them? | One concrete experience statement |
|
||||
| Verbs | What meaningful actions express that fantasy? | A short list of player verbs |
|
||||
| Mechanics | What systems make those actions possible? | State, interactions, resources, and relationships |
|
||||
| Rules | What creates decisions, limits, risk, and consequences? | Preconditions, transitions, formulas, and invariants |
|
||||
| Feedback | How does the player perceive action, state, and consequence? | Visual, audio, motion, UI, and control response |
|
||||
| Loop | Why does the player repeat the actions with new decisions? | A closed sequence from goal to result to next goal |
|
||||
|
||||
Write the chain in player language before implementation language. For example:
|
||||
|
||||
> Feel like a nimble rooftop courier → run, jump, route, recover → momentum and traversal systems → speed trades off against safe landings → immediate motion and landing feedback → choose route, traverse, deliver, improve, retry.
|
||||
|
||||
Check traceability in both directions:
|
||||
|
||||
- Remove or defer a mechanic that supports no intended verb or pillar.
|
||||
- Add rules or feedback when a promised verb has no meaningful decision or readable result.
|
||||
- Repair any loop that has no clear goal, result, or reason to repeat.
|
||||
- Distinguish a core loop from a list of activities. A loop changes state and creates the next decision.
|
||||
|
||||
Avoid starting with content volume, feature names, or technology. First prove that the smallest version of the chain is enjoyable and understandable.
|
||||
|
||||
## 2. Set pillars and non-goals
|
||||
|
||||
Define two to four design pillars. Make each pillar a decision rule, not a slogan.
|
||||
|
||||
A useful pillar contains:
|
||||
|
||||
- The experience to protect.
|
||||
- An observable implication for rules, presentation, or content.
|
||||
- A trade-off it wins when two good ideas conflict.
|
||||
|
||||
Weak: “Fast and fun.”
|
||||
|
||||
Stronger: “Keep decisions fast: the player can read the next threat and act without opening a menu.”
|
||||
|
||||
Write explicit non-goals beside the pillars. Non-goals prevent accidental expansion and make trade-offs honest. Examples include “no long-term crafting economy,” “no competitive balance,” or “no precision platforming on touch input.” Do not describe quality failures as non-goals.
|
||||
|
||||
Use the following filter for every proposed feature:
|
||||
|
||||
1. Name the pillar or required player outcome it supports.
|
||||
2. State what new decision or expression it creates.
|
||||
3. State its implementation, content, UX, and testing cost.
|
||||
4. Cut or defer it if the benefit cannot justify the cost inside the current milestone.
|
||||
|
||||
## 3. Specify systems before building them
|
||||
|
||||
Give each scheduled system a stable ID such as `SYS-001`. Keep the specification short enough to review, but precise enough that two implementers would produce the same player-facing behavior.
|
||||
|
||||
Include these fields:
|
||||
|
||||
| Field | Required decision |
|
||||
|---|---|
|
||||
| Purpose | Player need and pillar supported |
|
||||
| Player decision | Choice, skill, or expression created |
|
||||
| Inputs and preconditions | What starts the system and when it is legal |
|
||||
| State | Data the system owns or reads |
|
||||
| Rules and transitions | Ordered behavior, formulas, priority, and invariants |
|
||||
| Outputs | State changes and events visible to other systems |
|
||||
| Feedback | How success, failure, availability, and cause are communicated |
|
||||
| Tunables | Named values, units, defaults, safe ranges, and intended effect |
|
||||
| Edge cases | Interruptions, simultaneous events, limits, and recovery behavior |
|
||||
| Dependencies | Systems, content, assets, and platform assumptions |
|
||||
| Non-goals | Behavior explicitly outside this version |
|
||||
| Acceptance | Up to three observable conditions for completion |
|
||||
| Playtest question | The most uncertain player-facing assumption |
|
||||
|
||||
Prefer state transitions over vague prose. For any action, define at least:
|
||||
|
||||
`available → started → active → resolved or interrupted → recovered`
|
||||
|
||||
Specify conflict rules where events can overlap. Define which event wins, whether inputs buffer, which state owns cleanup, and how the player recovers. Avoid hidden special cases; if an exception is necessary, record why it exists.
|
||||
|
||||
Keep tunable values outside scattered behavior code where practical. Name values by their design meaning rather than by their current number.
|
||||
|
||||
A system is ready to build when its purpose, normal path, failure path, feedback, major edge cases, and acceptance conditions are known. Unknown numeric values are acceptable when they are marked as tuning hypotheses with test ranges.
|
||||
|
||||
## 4. Protect feel, readability, and fairness
|
||||
|
||||
### Game feel
|
||||
|
||||
Make the control response fit the promised fantasy. For each core verb, examine:
|
||||
|
||||
- Input recognition and input-to-visible-response delay.
|
||||
- Start, active, contact, recovery, and cancellation phases.
|
||||
- Acceleration, deceleration, momentum, hit-stop, shake, animation, and sound.
|
||||
- Input buffering, forgiveness windows, aim assistance, or snapping where appropriate.
|
||||
- Whether feedback reinforces the true rule instead of disguising it.
|
||||
|
||||
Layer feedback by purpose:
|
||||
|
||||
1. Confirm the input or state change immediately.
|
||||
2. Show the world consequence at the point of interaction.
|
||||
3. Reinforce importance with secondary motion, audio, particles, or UI.
|
||||
|
||||
Do not let animation delay a supposedly immediate action unless anticipation is itself part of the challenge. Add intensity only after the action is mechanically legible.
|
||||
|
||||
### Readability
|
||||
|
||||
Ensure the player can answer these questions during play:
|
||||
|
||||
- What can I act on now?
|
||||
- What is dangerous, helpful, or unavailable?
|
||||
- What changed, and what caused it?
|
||||
- What is my current goal and progress?
|
||||
- What can I do after a failure?
|
||||
|
||||
Use hierarchy, contrast, shape, timing, position, motion, and sound consistently. Never rely on color, sound, small text, or brief animation alone for critical information. Test the game at the actual play size and under the busiest expected state, not only in isolated scenes.
|
||||
|
||||
### Fairness
|
||||
|
||||
Make outcomes explainable even when the game is difficult:
|
||||
|
||||
- Telegraph threats early enough for the intended skill level and input method.
|
||||
- Keep cause and effect consistent across similar situations.
|
||||
- Avoid unavoidable damage, hidden rule changes, and punishment from unreadable off-screen events.
|
||||
- Provide recovery, mitigation, or a deliberate commitment trade-off.
|
||||
- Distinguish execution difficulty from missing information.
|
||||
- On failure, communicate the cause and make retry friction proportional to the lesson.
|
||||
|
||||
Fair does not mean easy. It means the player can form a correct model, make a meaningful choice, and understand the result.
|
||||
|
||||
## 5. Balance with explicit targets
|
||||
|
||||
Define the experience target before changing numbers. Examples include desired encounter duration, number of mistakes allowed, time between meaningful choices, resource pressure, or frequency of a signature action.
|
||||
|
||||
For every important tuning value, record:
|
||||
|
||||
- Name, unit, default, and allowed range.
|
||||
- Player-facing purpose.
|
||||
- Systems and content affected.
|
||||
- Evidence for the current value.
|
||||
|
||||
Balance in controlled passes:
|
||||
|
||||
1. Establish a simple baseline case.
|
||||
2. Test low, expected, and high-skill or high-power cases.
|
||||
3. Change one variable or one tightly coupled group at a time.
|
||||
4. Record the observed effect, not only the new value.
|
||||
5. Retest earlier cases after changing shared values.
|
||||
|
||||
Watch for dominant strategies, dead choices, positive feedback that removes recovery, resource sources without meaningful sinks, and upgrades that erase core decisions. Prefer trade-offs and new options over upgrades that only increase every number.
|
||||
|
||||
Use formulas as inspectable models, not as proof of fun. Validate them through play. If a value has no plausible range or target effect, the design question is still unresolved.
|
||||
|
||||
## 6. Plan progression and content
|
||||
|
||||
Make progression change what the player understands, chooses, or can express. Numerical growth may support progression, but should not be its only form.
|
||||
|
||||
Introduce mechanics with this sequence when practical:
|
||||
|
||||
**Teach → Test → Twist → Combine → Master**
|
||||
|
||||
- Teach one idea in a low-cost situation.
|
||||
- Test whether the player can use it intentionally.
|
||||
- Twist one assumption without changing all rules at once.
|
||||
- Combine it with a previously understood idea.
|
||||
- Offer mastery through efficiency, risk, expression, or optional challenge.
|
||||
|
||||
Plan content as a matrix of reusable variables rather than a list of isolated levels. Useful axes include space, timing, enemy role, objective, constraint, reward, and modifier. Recombine proven elements before creating new systems.
|
||||
|
||||
For economies, list every source, sink, cap, conversion, and reset. State why the resource exists and what decision scarcity creates. Avoid currencies that merely add steps between the player and an obvious purchase.
|
||||
|
||||
Set explicit content budgets for the current milestone: number of stages, enemy roles, upgrades, tutorials, audiovisual sets, and supported modes. Do not produce full content before the core loop, authoring pipeline, and representative content slice are validated.
|
||||
|
||||
## 7. Design UX and accessibility with the game
|
||||
|
||||
Treat menus, onboarding, pause, failure, restart, and settings as part of the playable loop.
|
||||
|
||||
Define the complete state path:
|
||||
|
||||
`launch → start → learn → play → pause/resume → result → retry or continue`
|
||||
|
||||
Keep the current goal and available actions clear. Teach through a safe action and immediate consequence where possible; use text to clarify, not to replace interaction. Do not require the player to remember information that can remain visible or be reviewed.
|
||||
|
||||
Establish an accessibility baseline at the start:
|
||||
|
||||
- Support the target input methods and make essential controls remappable where feasible.
|
||||
- Provide alternatives for hold, rapid repeat, precise timing, and simultaneous inputs when those are not core challenges.
|
||||
- Keep text readable at the target display size and allow scaling when the interface needs it.
|
||||
- Do not encode critical state with color or audio alone; support captions or visual equivalents.
|
||||
- Offer control over music, effects, motion, shake, flashes, and other intense presentation as relevant.
|
||||
- Preserve settings and avoid erasing progress without confirmation.
|
||||
- Make focus, selection, disabled state, and navigation order unambiguous.
|
||||
|
||||
Test accessibility options in real play. An option that breaks feedback, layout, timing, or progression is not complete.
|
||||
|
||||
## 8. Control scope, risk, and playtesting
|
||||
|
||||
### Scope around a closed loop
|
||||
|
||||
Build the smallest slice that lets a player start, perform the core verbs, receive a meaningful result, and retry or continue. A slice is not complete when it contains many systems but no full player journey.
|
||||
|
||||
Separate scope into:
|
||||
|
||||
- **Must:** required to test the current design claim.
|
||||
- **Later:** valuable after the claim is validated.
|
||||
- **Not now:** explicitly excluded from the milestone.
|
||||
|
||||
Count content and polish work, not only code. Every mechanic creates costs in UI, feedback, audio, content, onboarding, accessibility, balance, testing, and maintenance.
|
||||
|
||||
### Attack the largest uncertainty
|
||||
|
||||
Track risks as testable questions. Consider:
|
||||
|
||||
- Desirability: is the loop appealing enough to repeat?
|
||||
- Comprehension: can the player form the intended model?
|
||||
- Usability: can the player perform the verbs reliably?
|
||||
- Feasibility: can the system meet technical and production constraints?
|
||||
- Content: can enough varied play be produced at an affordable rate?
|
||||
- Balance: do intended choices remain viable across progression?
|
||||
|
||||
Prioritize high-uncertainty, high-impact risks. Build a prototype to answer one major question, not to resemble a finished game. Use disposable presentation when polished assets would not change the answer.
|
||||
|
||||
### Run evidence-seeking playtests
|
||||
|
||||
For each test:
|
||||
|
||||
1. Write one primary question and the behavior that would answer it.
|
||||
2. Use a build that isolates the relevant decision when possible.
|
||||
3. Let the player act without coaching; record where intervention became necessary.
|
||||
4. Capture behavior, outcome, confusion, and recovery before asking for opinions.
|
||||
5. Separate observation from interpretation.
|
||||
6. Decide `keep`, `change`, or `inconclusive`, then name the next test.
|
||||
|
||||
Do not ask only “Was it fun?” Ask what the player thought the goal was, why they made a choice, what they expected, and what caused a result. Prefer repeated behavioral evidence over a single stated preference.
|
||||
|
||||
## 9. Review designs and propagate changes
|
||||
|
||||
Review a proposed system in this order:
|
||||
|
||||
1. Trace it to fantasy, verbs, pillars, and the core loop.
|
||||
2. Check that it creates a meaningful player decision or expression.
|
||||
3. Walk through normal, failure, interrupted, and repeated use.
|
||||
4. Check feedback, readability, fairness, and accessibility.
|
||||
5. Check tuning surfaces, content burden, dependencies, and scope.
|
||||
6. Confirm acceptance conditions and the playtest question.
|
||||
|
||||
Classify discrepancies before editing:
|
||||
|
||||
- **Implementation defect:** the build disagrees with the current GDD.
|
||||
- **Design change:** evidence suggests the intended behavior should change.
|
||||
- **Documentation drift:** the build and intended design agree, but the GDD is stale.
|
||||
|
||||
For a design change, update the source GDD section first or in the same change. Then inspect and update every affected layer:
|
||||
|
||||
`core loop → system rules → shared state → controls/camera → feedback/UI/audio → onboarding/accessibility → tuning/content → acceptance tests/tasks`
|
||||
|
||||
Record consequential decisions with date, evidence, decision, and affected sections. Do not preserve a bad decision merely because it was previously documented; preserve the reason for changing it.
|
||||
|
||||
Call a design increment complete only when:
|
||||
|
||||
- The build demonstrates the intended player-facing behavior.
|
||||
- Its acceptance conditions pass in the target play environment.
|
||||
- The main uncertainty has evidence from a playtest or an explicit reason it could not yet be tested.
|
||||
- The GDD, task board, and implemented behavior agree.
|
||||
@@ -0,0 +1,306 @@
|
||||
# Phaser 3 Engineering Standards
|
||||
|
||||
Use this reference when creating, extending, or reviewing a Phaser 3 game. It defines a small, reliable default and the conditions under which that default should grow.
|
||||
|
||||
## Rule levels
|
||||
|
||||
- **Required** rules apply to all new work. In an existing project, preserve a sound established convention when changing it would be unrelated or risky; record the exception.
|
||||
- **Conditional** guidance applies only when its stated condition is true. Do not add its machinery in anticipation of possible future needs.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Compatibility baseline](#compatibility-baseline)
|
||||
- [Grow-on-demand structure](#grow-on-demand-structure)
|
||||
- [Scenes and lifecycle](#scenes-and-lifecycle)
|
||||
- [Assets and loading](#assets-and-loading)
|
||||
- [Input actions](#input-actions)
|
||||
- [Physics choice and rules](#physics-choice-and-rules)
|
||||
- [Scale and responsive layout](#scale-and-responsive-layout)
|
||||
- [State ownership](#state-ownership)
|
||||
- [Time, delta, and determinism](#time-delta-and-determinism)
|
||||
- [Tuning and configuration](#tuning-and-configuration)
|
||||
- [Verification and completion](#verification-and-completion)
|
||||
- [Common failure patterns](#common-failure-patterns)
|
||||
- [Official references](#official-references)
|
||||
|
||||
## Compatibility baseline
|
||||
|
||||
### Required
|
||||
|
||||
- For a new project, declare the exact dependency `"phaser": "3.90.0"` and commit the package-manager lockfile. Do not use `latest`, `^3.90.0`, `~3.90.0`, or an unversioned install command. `latest` now resolves to Phaser 4 and can silently change the API surface.
|
||||
- Start new projects with Vite, the `vanilla-ts` template, strict TypeScript, and no UI framework. Set `compilerOptions.strict` to `true`; fix type errors instead of broadly disabling checks or spreading `any`.
|
||||
- Use ES modules and keep one obvious browser entry point that creates exactly one `Phaser.Game` instance.
|
||||
- Use the Phaser 3.90 API documentation as the authority. Official examples may target an earlier Phaser 3 release, so verify copied APIs against 3.90 and reject Phaser 2 or 4 code.
|
||||
- Keep the dependency surface small. A library must solve a present, demonstrated need and must not duplicate a Phaser facility already sufficient for the game.
|
||||
|
||||
### Conditional
|
||||
|
||||
- In an existing project, keep its working bundler, language, package manager, and framework unless migration is explicitly in scope.
|
||||
- Add React, Vue, Svelte, or another UI framework only when Phaser is embedded in an application whose non-game UI already needs it. Keep the Phaser game boundary explicit.
|
||||
- Upgrade Phaser only as a deliberate compatibility task with release-note review and a complete browser regression pass.
|
||||
|
||||
## Grow-on-demand structure
|
||||
|
||||
### Required
|
||||
|
||||
Begin with the smallest structure that gives the current game clear ownership:
|
||||
|
||||
```text
|
||||
index.html
|
||||
src/
|
||||
main.ts
|
||||
game/
|
||||
config.ts
|
||||
scenes/
|
||||
GameScene.ts
|
||||
public/
|
||||
assets/
|
||||
```
|
||||
|
||||
- `main.ts` owns game startup only; `config.ts` owns `Phaser.Types.Core.GameConfig`; Scenes own presentation and orchestration.
|
||||
- Keep a one-scene prototype in one scene file until a real boundary appears.
|
||||
- Organize new files by cohesive gameplay system or responsibility, not by generic class type alone.
|
||||
- Delete abandoned experiments and unused assets once a direction is chosen. Do not leave two active implementations of the same rule.
|
||||
- Avoid speculative architecture: no service container, event bus, ECS, repository layer, global state library, or plugin framework without a present need.
|
||||
|
||||
### Conditional
|
||||
|
||||
- Add `BootScene` when startup configuration or a tiny boot asset set must load before the main preload.
|
||||
- Add `PreloadScene` when load time is visible enough to need progress, error, or retry UI.
|
||||
- Add `MenuScene`, `ResultScene`, or overlay Scenes when they have distinct lifecycle, input, or rendering needs—not merely to shorten a file.
|
||||
- Extract `gameplay/`, `ui/`, `audio/`, `data/`, or `shared/` only after each has more than one cohesive consumer.
|
||||
- Introduce a pure domain module when rules such as scoring, progression, spawning, or combat can be expressed without Phaser objects; this is also the preferred unit-test boundary.
|
||||
|
||||
## Scenes and lifecycle
|
||||
|
||||
Phaser starts a Scene through `init` → `preload` → `create`, then calls `update(time, delta)` while it is running. A stopped Scene instance can start again; its constructor is not a per-run reset hook.
|
||||
|
||||
### Required
|
||||
|
||||
- Give every Scene one stable, unique key. Keep its constructor limited to `super(...)` and values that truly live for the entire Scene instance.
|
||||
- Reset every run-specific flag, counter, collection, and reference in `init` or `create`. A restart must behave like a fresh run.
|
||||
- Load required assets in `preload` before using them in `create`. Keep gameplay setup out of `preload`.
|
||||
- Create input handlers, colliders, timers, tweens, and subscriptions from one visible setup path. Prevent a second registration on restart.
|
||||
- Treat `shutdown` as the cleanup boundary. Remove anything not owned and cleaned by the Scene systems: `window` or `document` listeners, game/registry/external-emitter listeners, observers, intervals, network subscriptions, and retained callbacks.
|
||||
- Use `destroy` only for final Scene removal. Do not rely on it for ordinary stop, start, or restart cleanup.
|
||||
- Guard terminal transitions so collisions or consecutive update frames cannot start, stop, or restart a Scene twice.
|
||||
- Pass small typed data objects between Scenes. Never pass live Game Objects, physics bodies, cameras, or Scene plugins as shared state.
|
||||
- Make `this.scene.restart()` safe to run repeatedly. Smoke-test at least two complete restarts.
|
||||
- Guard global animation creation with `this.anims.exists(key)` or create global animations once in a dedicated startup path.
|
||||
|
||||
### Conditional
|
||||
|
||||
- When using `sleep`/`wake`, handle their events explicitly: sleep does not run shutdown, and wake does not rerun `create`.
|
||||
- When using `pause`/`resume`, decide which timers, physics, input, audio, and overlay Scenes should pause; verify the decision rather than assuming all systems share one clock.
|
||||
- Run a separate UI Scene above gameplay only when UI must retain an independent lifecycle. Define which Scene owns shared data and transition authority.
|
||||
- Stream assets during play only when level size requires it; specify loading, cancellation, cache, and failure behavior first.
|
||||
|
||||
## Assets and loading
|
||||
|
||||
### Required
|
||||
|
||||
- Give every texture, atlas, animation, audio clip, tilemap, and data file a stable semantic key. Centralize keys as typed constants; do not scatter raw string keys through gameplay code.
|
||||
- Use namespaced keys such as `player.body`, `player.run`, and `audio.hit` to avoid collisions and clarify asset type and ownership.
|
||||
- Keep filenames lowercase and portable; avoid spaces, case-only distinctions, and platform-specific path separators.
|
||||
- For Vite assets placed under `public/assets`, derive the loader base from `import.meta.env.BASE_URL`. Do not assume the game is always hosted at domain root `/`.
|
||||
- Surface load failures. A missing required asset must produce a clear error or retry state, not a silent blank canvas.
|
||||
- Load each shared asset once and reuse Phaser's global caches. Do not reload the same level-independent asset in every Scene.
|
||||
- Keep source art separate from runtime exports. Commit only optimized runtime formats and sizes the game actually uses.
|
||||
- Match spritesheet frame dimensions exactly. Verify atlas frame names, animation ranges, texture filtering, and origins in the browser.
|
||||
|
||||
### Conditional
|
||||
|
||||
- Use a texture atlas when many small sprites are displayed together or a production art pipeline already emits one.
|
||||
- Enable `pixelArt` and disable smoothing only for intentional pixel art; render at integer-friendly scales and inspect movement for shimmer.
|
||||
- Preload alternate audio formats when the supported browser matrix requires them. Start or resume audio only after a user gesture and preserve the user's mute setting.
|
||||
- Add asset manifests, generated key types, compression, or lazy loading only when asset volume makes manual management error-prone or initial load materially slow.
|
||||
|
||||
## Input actions
|
||||
|
||||
### Required
|
||||
|
||||
- Translate devices into semantic actions such as `moveLeft`, `jump`, `confirm`, `pause`, and `restart`. Gameplay rules consume actions, not scattered key codes or pointer events.
|
||||
- Distinguish held actions from edge-triggered actions. Poll `isDown` for continuous movement; use `Phaser.Input.Keyboard.JustDown`, `JustUp`, or explicit event edges for one-shot actions.
|
||||
- Keep input registration and teardown with the owning Scene. Clear latched actions on shutdown, pause, focus loss, and device disconnection.
|
||||
- Prevent browser defaults for gameplay keys such as arrows or Space when scrolling would interfere with play.
|
||||
- Convert pointer screen coordinates through the relevant camera before using them as world coordinates.
|
||||
- Ensure one physical gesture produces at most one gameplay command, even when keyboard, pointer, and UI handlers overlap.
|
||||
- Keep restart, pause, and debug controls separate from the core movement state so they cannot remain stuck.
|
||||
|
||||
### Conditional
|
||||
|
||||
- When touch is supported, provide visible touch controls or direct gestures; test multi-touch, finger occlusion, cancellation, and both target orientations.
|
||||
- When gamepads are supported, handle connect/disconnect at runtime, dead zones, axis normalization, and a keyboard fallback.
|
||||
- Add rebinding and persisted mappings when the brief or accessibility requirements call for them. Store semantic action mappings, not engine objects.
|
||||
- Use input buffering or coyote time only when the design calls for forgiving timing; keep its duration in tuning configuration.
|
||||
|
||||
## Physics choice and rules
|
||||
|
||||
Choose the least complex model that can express the game:
|
||||
|
||||
| Need | Choice |
|
||||
| --- | --- |
|
||||
| No collision or only simple range checks | Manual movement and geometry checks |
|
||||
| Fast axis-aligned or circular collision, platformer, top-down movement | Arcade Physics |
|
||||
| Rotated/compound bodies, joints, torque, stacking, physical simulation | Matter Physics |
|
||||
|
||||
### Required
|
||||
|
||||
- Default to Arcade Physics for ordinary arcade gameplay. Use Matter only when a documented mechanic needs its capabilities.
|
||||
- Use one physics system for interacting objects. Arcade and Matter bodies do not collide with each other; never attach the same Game Object to both.
|
||||
- Specify units for velocity, acceleration, gravity, dimensions, and cooldowns. Treat Arcade velocity as pixels per second; do not multiply a velocity passed to Arcade by frame delta.
|
||||
- Match physics bodies to the intended collision silhouette, not blindly to transparent texture bounds. Recheck offsets after changing scale or origin.
|
||||
- Separate collision (`collider`, with resolution) from trigger detection (`overlap`, without resolution) intentionally.
|
||||
- Make collision callbacks idempotent or guard them; physics can report contact across multiple steps.
|
||||
- Define world bounds and out-of-bounds behavior explicitly. Do not let important objects disappear forever without a recovery or terminal rule.
|
||||
- Keep physics debug rendering development-only and disable it in production builds.
|
||||
|
||||
### Conditional
|
||||
|
||||
- After moving or scaling an Arcade static body, call `refreshBody()` so its body matches its visual transform.
|
||||
- Use Arcade collision categories, body masks, immovable/pushable settings, and custom process callbacks when layers have distinct interaction rules.
|
||||
- For Matter, define body shapes, friction, restitution, mass/density, collision categories, and sleeping deliberately; test unstable stacks and high-speed tunneling.
|
||||
- Use a fixed simulation strategy or seeded replay only when determinism is a product requirement. Isolate rendering interpolation from authoritative simulation state.
|
||||
|
||||
## Scale and responsive layout
|
||||
|
||||
### Required
|
||||
|
||||
- Choose one logical game resolution and design gameplay in those world units.
|
||||
- For new fixed-composition games, default to `Phaser.Scale.FIT` with `Phaser.Scale.CENTER_BOTH`. Let CSS size the canvas container; do not derive simulation coordinates from CSS pixels.
|
||||
- Give the parent element an explicit usable size. Remove unintended page margins and prevent page scrolling around the game canvas.
|
||||
- Anchor HUD and menus to camera/viewport bounds, not world objects, unless the UI is intentionally diegetic.
|
||||
- Test the minimum and maximum supported aspect ratios. Critical play space, text, and controls must remain visible and usable.
|
||||
- Recompute responsive layout from current dimensions; do not incrementally nudge old positions on every resize event.
|
||||
- Keep camera bounds, physics bounds, and visible world bounds conceptually separate and configure each intentionally.
|
||||
|
||||
### Conditional
|
||||
|
||||
- Use `RESIZE` instead of `FIT` only when the design should reveal more or less world as the viewport changes. Listen for resize and relayout cameras, UI, and hit areas.
|
||||
- For mobile targets, account for orientation changes, browser chrome, display cutouts, and CSS safe-area insets. Do not place essential controls at unsafe edges.
|
||||
- Raise render resolution for high-density displays only after profiling fill rate and texture memory; cap it on lower-powered mobile devices.
|
||||
- Letterbox intentionally when preserving composition matters more than filling every pixel. Give the surrounding page a designed background.
|
||||
|
||||
## State ownership
|
||||
|
||||
Use the narrowest owner that matches the state's lifetime:
|
||||
|
||||
| State lifetime | Preferred owner |
|
||||
| --- | --- |
|
||||
| One Game Object | That object or its focused controller |
|
||||
| One Scene run | The Scene or a Scene-owned system |
|
||||
| One play session across Scenes | A typed plain-data session store |
|
||||
| Across browser sessions | A versioned persistence adapter |
|
||||
| Static balance values | Read-only tuning/configuration modules |
|
||||
|
||||
### Required
|
||||
|
||||
- Assign one authoritative writer for each piece of state. Other systems receive snapshots, queries, or explicit commands.
|
||||
- Keep rules state as plain serializable data where practical. Keep Phaser Game Objects, bodies, sounds, and timers out of saves and cross-Scene stores.
|
||||
- Separate definition data, current runtime state, and rendered view. Do not mutate imported configuration to represent a run.
|
||||
- Use the Phaser registry only behind named, typed accessors for genuinely game-global values. Do not turn it into an unstructured dumping ground.
|
||||
- Make transitions explicit: title → playing → paused → result → restart. Reject commands invalid for the current state.
|
||||
- Avoid mutable globals and values attached to `window`. Destroy the `Phaser.Game` instance if the host page unmounts or recreates it.
|
||||
- Version persistent save data, validate it on load, and provide a safe fallback for corrupt or old data.
|
||||
|
||||
### Conditional
|
||||
|
||||
- Use events for discrete notifications with multiple legitimate listeners. Use direct calls or returned values for single-owner commands and queries.
|
||||
- Introduce a finite-state machine when booleans allow impossible combinations or transition guards are being duplicated.
|
||||
- Add immutable updates, snapshots, or replay logs only when undo, deterministic replay, networking, or difficult state debugging requires them.
|
||||
|
||||
## Time, delta, and determinism
|
||||
|
||||
### Required
|
||||
|
||||
- Remember that `update(time, delta)` receives `delta` in milliseconds. Convert once with `const dt = delta / 1000` for manual velocities expressed per second.
|
||||
- Make manual movement, meters, cooldowns, spawning, and animation-independent rules frame-rate independent. Never express gameplay duration as a count of render frames.
|
||||
- Do not multiply Arcade Physics velocity by `delta`; the physics world already integrates it.
|
||||
- Prefer Scene-owned `this.time` events and tweens to raw `setTimeout` or `setInterval` so lifecycle and pausing remain coherent.
|
||||
- Decide what happens on tab blur or a long frame. Pause, or clamp the delta used by manual integration to prevent a large teleport or instant timer drain.
|
||||
- Remove or invalidate delayed callbacks when their owner shuts down. A callback from a previous run must never mutate the next run.
|
||||
- Store durations with unit-bearing names such as `spawnIntervalMs` or `invulnerabilitySeconds`.
|
||||
|
||||
### Conditional
|
||||
|
||||
- Use wall-clock time only for mechanics that must advance while the game is closed; validate clock jumps and persist a reference timestamp.
|
||||
- Use `Phaser.Math.RandomDataGenerator` with an explicit seed when reproducing a run, testing procedural output, or synchronizing simulations matters.
|
||||
- Use a custom fixed-step loop only when the design or networking model requires it. Define accumulation, maximum catch-up steps, interpolation, and overload behavior.
|
||||
|
||||
## Tuning and configuration
|
||||
|
||||
### Required
|
||||
|
||||
- Put player-facing balance values in small typed, read-only configuration objects grouped by system. Keep engine boot configuration separate from gameplay tuning.
|
||||
- Name values by meaning and unit; include a short rationale or valid range when a number is not self-evident.
|
||||
- Avoid repeated magic numbers in Scenes and callbacks. One mechanic must read from one source of truth.
|
||||
- Keep tunable values traceable to the relevant game-design rule and acceptance criteria. When tuning changes the designed behavior, update both.
|
||||
- Clamp user- or data-supplied values at system boundaries. Reject invalid level or content data with a useful message.
|
||||
- Keep secrets and privileged rules off the client. Everything shipped to a browser can be inspected or modified.
|
||||
|
||||
### Conditional
|
||||
|
||||
- Load level/content JSON when non-programmers need to edit repeated content or when many levels share one schema. Validate it before constructing gameplay objects.
|
||||
- Add a development-only tuning panel when iteration speed justifies it. Ensure its state and debug commands cannot leak into production behavior.
|
||||
- Introduce schema generation, migrations, or remote configuration only when content volume or live operations creates a current need.
|
||||
|
||||
## Verification and completion
|
||||
|
||||
### Required automated checks
|
||||
|
||||
- Install from the committed lockfile in a clean environment.
|
||||
- Run strict type checking and every existing lint or automated test command.
|
||||
- Run the production build. A development server compiling successfully is not a substitute.
|
||||
- Keep rules such as score calculation, spawn selection, damage, progression, and save migration pure where possible, and add focused tests when they are nontrivial or regression-prone.
|
||||
|
||||
### Required browser smoke check
|
||||
|
||||
Run the built game in a real browser, preferably from the production preview, and verify:
|
||||
|
||||
1. The page loads into the intended first interactive state.
|
||||
2. The primary input performs the core action and visible/audio feedback occurs.
|
||||
3. The player can reach the success or failure result through the intended loop.
|
||||
4. Restart returns to a clean initial run; complete and restart the loop at least twice.
|
||||
5. There are no new console errors, unhandled promise rejections, failed required asset requests, or duplicate-event symptoms.
|
||||
6. Pause/focus loss and return do not cause stuck input, a time jump, or resumed sounds that should remain paused.
|
||||
7. The supported minimum and maximum viewport sizes remain playable.
|
||||
|
||||
### Conditional checks
|
||||
|
||||
- Test each declared input family—keyboard, pointer, touch, and gamepad—on representative hardware.
|
||||
- Add browser automation for stable critical flows such as boot, start, result, restart, save/load, and resize when manual regression becomes costly.
|
||||
- Profile frame time, draw calls, memory, asset size, and mobile thermal behavior when measured performance misses its target. Optimize measured bottlenecks, not guesses.
|
||||
- Test offline, slow network, load failure, localization, accessibility, and persistence recovery when the product claims those capabilities.
|
||||
|
||||
## Common failure patterns
|
||||
|
||||
| Symptom | Likely cause | Required response |
|
||||
| --- | --- | --- |
|
||||
| Phaser 4 types or APIs appear in a Phaser 3 game | Installed `latest` or a version range resolved unexpectedly | Pin `phaser` to exactly `3.90.0`, recreate the lockfile deliberately, and use the versioned API docs |
|
||||
| Blank canvas with no visible error | Wrong parent size, Scene not registered, or required asset failed | Inspect console/network, verify parent dimensions and Scene order, and surface loader errors |
|
||||
| Input fires twice after restart | Listener registered on every `create` without cleanup | Register once per run and unsubscribe on shutdown |
|
||||
| Result Scene starts repeatedly | Transition is triggered from repeated overlap/update callbacks | Add a terminal-state guard before changing Scenes |
|
||||
| Score, health, or flags survive restart | State initialized only in the constructor | Reset all run state in `init` or `create` |
|
||||
| Movement speed changes with frame rate | Per-frame movement or wrong delta units | Express speed per second and use `delta / 1000` for manual integration |
|
||||
| Object teleports after tab return | A large delta was integrated after suspension | Pause on blur or clamp manual-integration delta |
|
||||
| Pointer aim is offset when camera moves or canvas scales | Screen coordinates used as world coordinates | Convert through the active camera |
|
||||
| Sprite and collision do not line up | Body size/offset was not updated after origin or scale changes | Configure and visually debug the body after final transform |
|
||||
| Static collider remains at its old position | Arcade static body changed without refresh | Call `refreshBody()` after the transform |
|
||||
| Assets work locally but 404 under a subpath | Root-relative URL ignored Vite base path | Derive loader URLs from `import.meta.env.BASE_URL` |
|
||||
| Audio is silent on mobile | Playback started before a user gesture | Unlock/resume audio from an explicit interaction and expose mute state |
|
||||
| Pixel art is blurred or shimmers | Filtering or fractional scaling/positioning | Enable pixel-art settings and use integer-friendly scales and positions |
|
||||
| Game exists twice after navigation | Host recreated the game without destroying the old instance | Keep one owner and call `game.destroy(true)` during host teardown |
|
||||
|
||||
## Official references
|
||||
|
||||
- [Phaser 3.90.0 release](https://github.com/phaserjs/phaser/releases/tag/v3.90.0)
|
||||
- [Phaser 3.90.0 API documentation](https://docs.phaser.io/api-documentation/3.90.0/api-documentation)
|
||||
- [Scenes and lifecycle](https://docs.phaser.io/phaser/concepts/scenes)
|
||||
- [Loader](https://docs.phaser.io/phaser/concepts/loader)
|
||||
- [Input](https://docs.phaser.io/phaser/concepts/input)
|
||||
- [Arcade Physics](https://docs.phaser.io/phaser/concepts/physics/arcade)
|
||||
- [Matter Physics](https://docs.phaser.io/phaser/concepts/physics/matter)
|
||||
- [Scale Manager](https://docs.phaser.io/phaser/concepts/scale-manager)
|
||||
- [Time and Clock](https://docs.phaser.io/phaser/concepts/time)
|
||||
- [Vite getting started](https://vite.dev/guide/)
|
||||
- [TypeScript strict mode](https://www.typescriptlang.org/tsconfig/strict.html)
|
||||
@@ -0,0 +1,58 @@
|
||||
# Web 3D Engineering Standards
|
||||
|
||||
Use this reference when a game explicitly chooses the 3D path. The default web 3D engine is Three.js with TypeScript; this path is opt-in and does not silently convert an existing 2D Phaser game.
|
||||
|
||||
## Choose and pin the runtime
|
||||
|
||||
- Record `Rendering Mode: 3D` and the exact `three` version in `TECH_STACK.md` before implementation.
|
||||
- Install Three.js through the project's package manager. Do not depend on a CDN-only runtime, an unpinned `latest` tag, or a copied dependency whose version is not recorded in the lockfile.
|
||||
- Keep the browser game boundary explicit: the renderer owns scene presentation, while rules, scoring, progression, and content state stay in plain TypeScript modules where practical.
|
||||
- Use `WebGLRenderer` as the compatibility baseline unless the project has a separately verified WebGPU plan. Do not claim WebGPU support from a local-only experiment.
|
||||
|
||||
## Keep a small 3D architecture
|
||||
|
||||
- Keep one obvious browser entry point that creates one renderer, one scene root, and one active camera graph for the current game view.
|
||||
- Separate responsibilities into `main.ts`/bootstrap, renderer setup, scene or world composition, input mapping, domain state, and asset loading. Do not put all behavior in one animation loop.
|
||||
- Use a typed camera contract and explicit coordinate/unit conventions. Record world-up, player height, movement units, near/far planes, and collision assumptions in `TECH_STACK.md` or the relevant role artifact.
|
||||
- Prefer a small vertical slice: one camera, one controllable object, one interaction, one feedback path, and one restart/continue outcome before adding a large world.
|
||||
- Keep animation and gameplay timing based on elapsed time, not frame count. Make pause, resume, restart, and visibility changes explicit.
|
||||
|
||||
## Load and own 3D assets
|
||||
|
||||
- Prefer GLB/glTF for runtime models. Load them through `GLTFLoader` or the project's verified loader boundary, and keep model paths project-relative.
|
||||
- Use a loading state with progress or a clear fallback. A missing model, texture, environment map, or decoder must produce an actionable error instead of an empty canvas.
|
||||
- Keep source models, runtime copies, previews, and license metadata distinct. `ASSET_PLAN.md` is the source-of-truth handoff; only approved project-local paths enter the runtime.
|
||||
- When removing a model or scene, dispose geometries, materials, textures, render targets, and other GPU-owned resources that are no longer used. Do not assume JavaScript garbage collection releases GPU memory.
|
||||
- Keep texture dimensions, color space, normal-map conventions, compression, polygon budget, animation clips, and file size visible in `ART_GUIDE.md` or `ASSET_PLAN.md`.
|
||||
|
||||
## Camera, input, and responsive rendering
|
||||
|
||||
- Update the camera aspect/projection and renderer size from the actual drawing buffer when the viewport changes. Do not stretch a 3D scene by leaving a stale aspect ratio.
|
||||
- Cap device pixel ratio to the project's measured performance target. Prefer stable frame time and readable interaction over maximum resolution.
|
||||
- Make pointer lock, pointer/touch input, keyboard input, and camera movement explicit. Provide a non-pointer-lock fallback for the first playable result when the platform allows it.
|
||||
- Ensure the game remains understandable on the supported desktop and mobile viewports. Record unsupported controls or browser constraints in `TEST_REPORT.md`.
|
||||
|
||||
## Performance and visual correctness
|
||||
|
||||
- Measure frame time, draw calls, visible triangles, texture memory, model size, and load time before optimizing. Fix the measured bottleneck.
|
||||
- Frustum-cull or simplify content only when the scene needs it; do not add a scene graph or streaming framework without evidence.
|
||||
- Verify lighting, tone mapping, output color space, shadows, transparency, depth ordering, and animation transitions in the target browser. A model loading successfully is not visual acceptance.
|
||||
- Test resize, device-pixel-ratio changes, tab background/foreground, pause/resume, restart, and a missing-asset failure path.
|
||||
|
||||
## Completion evidence
|
||||
|
||||
For a 3D playable result, record:
|
||||
|
||||
- the exact `three` version and browser/runtime assumptions;
|
||||
- a real-browser path from load to interaction, feedback, outcome, and restart/continue;
|
||||
- desktop and mobile viewport behavior, camera/resize evidence, and console/network results;
|
||||
- model/texture paths, license metadata, approval state, and any placeholder or unsupported feature;
|
||||
- measured performance risks and the next smallest playable result.
|
||||
|
||||
Do not call a 3D game complete because the renderer compiled or a screenshot exists. The playable interaction and the relevant browser evidence must be observed.
|
||||
|
||||
## References
|
||||
|
||||
- [Three.js documentation](https://threejs.org/docs/)
|
||||
- [Three.js examples](https://threejs.org/examples/)
|
||||
- [glTF specification](https://www.khronos.org/gltf/)
|
||||
Reference in New Issue
Block a user