@inneranimalmedia/agentsam-sdk 2.6.7 → 2.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/apps/README.md +25 -0
- package/apps/cad-creator/shared/cad/package.json +1 -1
- package/apps/client-cms-editor/backend/package.json +4 -6
- package/apps/client-cms-editor/frontend/package.json +6 -8
- package/apps/client-cms-editor/frontend/tsconfig.json +27 -8
- package/apps/client-cms-editor/package.json +1 -1
- package/apps/client-cms-editor/shared/cms/package.json +4 -6
- package/docs/LOCAL_ENGINE_COMPUTE_AUDIT_PLAN.md +299 -0
- package/docs/REPO_FILE_MAP.txt +132 -0
- package/migrations/d1/0018_goap_control_plane_remaster.sql +40 -0
- package/migrations/d1/0019_agentsam_agent_run_plan_todo.sql +14 -0
- package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +20 -9
- package/migrations/runtime/0002_goap_blackboard_v1.sql +15 -0
- package/migrations/runtime/0004_agent_run_plan_linkage.sql +8 -0
- package/native/agentsam-machine/cli/Cargo.toml +8 -1
- package/native/agentsam-machine/cli/README.md +21 -0
- package/native/agentsam-machine/core/Cargo.toml +7 -0
- package/native/agentsam-machine/core/README.md +14 -0
- package/native/agentsam-machine/core/build.rs +33 -0
- package/native/agentsam-machine/core/contracts/source-types.v1.json +353 -0
- package/native/agentsam-machine/core/src/lib.rs +6 -0
- package/native/agentsam-machine/core/src/source_types.rs +2 -2
- package/package.json +31 -10
- package/packages/agentsam-brand/package.json +1 -1
- package/packages/agentsam-contracts/package.json +71 -20
- package/packages/agentsam-contracts/scripts/build.mjs +18 -0
- package/packages/agentsam-contracts/test/contracts.test.mjs +4 -1
- package/packages/agentsam-contracts/test/repository.test.mjs +4 -1
- package/packages/agentsam-contracts/tsconfig.build.json +10 -0
- package/packages/agentsam-errors/package.json +3 -2
- package/packages/agentsam-errors/src/index.js +1 -0
- package/packages/agentsam-errors/src/tool.js +56 -0
- package/packages/agentsam-errors/test/tool.test.mjs +58 -0
- package/packages/agentsam-goap/README.md +52 -0
- package/packages/agentsam-goap/package.json +37 -0
- package/packages/agentsam-goap/src/adapters/d1-sqlite.js +570 -0
- package/packages/agentsam-goap/src/adapters/memory.js +199 -0
- package/packages/agentsam-goap/src/contracts.js +226 -0
- package/packages/agentsam-goap/src/control-plane.js +112 -0
- package/packages/agentsam-goap/src/errors.js +157 -0
- package/packages/agentsam-goap/src/index.js +5 -0
- package/packages/agentsam-goap/src/world.js +202 -0
- package/packages/agentsam-goap/test/goap-control-plane.test.mjs +245 -0
- package/packages/agentsam-goap/test/world.test.mjs +79 -0
- package/packages/agentsam-knowledge/package.json +3 -2
- package/packages/agentsam-nav/package.json +1 -1
- package/packages/agentsam-queue-control/package.json +1 -1
- package/packages/agentsam-rapid-rust/src/main.rs +1 -0
- package/packages/agentsam-repository/package.json +3 -2
- package/packages/agentsam-repository/src/goap.js +13 -14
- package/packages/agentsam-repository/src/tickets.js +15 -22
- package/packages/agentsam-repository/test/goap-tickets-workspace-state.test.mjs +19 -8
- package/packages/agentsam-vault/package.json +8 -5
- package/packages/agentsam-workbench/package.json +44 -17
- package/packages/connectors/cloudflare/package.json +4 -2
- package/packages/heuristic-theme/package.json +1 -1
- package/packages/identity/package.json +4 -3
- package/packages/identity/tests/lane1-packed-consumer.test.mjs +10 -2
- package/packages/providers/completeful/package.json +1 -1
- package/packages/theme-church-site/README.md +2 -2
- package/packages/theme-church-site/package.json +3 -4
- package/packages/theme-church-site/src/index.js +1 -1
- package/packages/theme-church-site/test/theme.test.mjs +1 -1
- package/packages/theme-companions-site/README.md +2 -2
- package/packages/theme-companions-site/package.json +3 -4
- package/packages/theme-companions-site/src/index.js +1 -1
- package/packages/theme-companions-site/test/theme.test.mjs +1 -1
- package/packages/theme-floors-site/README.md +2 -2
- package/packages/theme-floors-site/package.json +3 -4
- package/packages/theme-floors-site/src/index.js +1 -1
- package/packages/theme-floors-site/test/theme.test.mjs +1 -1
- package/packages/theme-fuelnfree-site/README.md +2 -2
- package/packages/theme-fuelnfree-site/package.json +3 -4
- package/packages/theme-fuelnfree-site/src/index.js +1 -1
- package/packages/theme-fuelnfree-site/test/theme.test.mjs +1 -1
- package/packages/theme-handyman-site/README.md +2 -2
- package/packages/theme-handyman-site/package.json +3 -4
- package/packages/theme-handyman-site/src/index.js +1 -1
- package/packages/theme-handyman-site/test/theme.test.mjs +1 -1
- package/packages/theme-insurance-site/README.md +2 -2
- package/packages/theme-insurance-site/package.json +3 -4
- package/packages/theme-insurance-site/src/index.js +1 -1
- package/packages/theme-insurance-site/test/theme.test.mjs +1 -1
- package/packages/theme-scenes/package.json +1 -1
- package/packages/theme-shinshu-site/README.md +2 -2
- package/packages/theme-shinshu-site/package.json +3 -4
- package/packages/theme-shinshu-site/src/index.js +1 -1
- package/packages/theme-shinshu-site/test/theme.test.mjs +1 -1
- package/packages/work-graph/package.json +3 -2
- package/protocol/local-engine/benchmark.v1.schema.json +14 -0
- package/protocol/local-engine/engine.v1.schema.json +17 -0
- package/python/agentsam_sdk/repository/intelligence/discovery.py +27 -2
- package/python/agentsam_sdk/repository/intelligence/git.py +4 -0
- package/python/tests/test_repository_intelligence.py +30 -0
- package/src/cli/command-catalog.js +3 -1
- package/src/cli.js +20 -1
- package/src/commands/app.js +10 -0
- package/src/commands/engine.js +10 -0
- package/src/commands/goap-world.js +134 -0
- package/src/commands/goap.js +11 -4
- package/src/commands/machine-binary.js +33 -27
- package/src/commands/machine-runtime.js +266 -0
- package/src/commands/machine.js +155 -51
- package/src/commands/package.js +537 -0
- package/src/commands/plan-ledger.js +370 -0
- package/src/commands/plan.js +183 -0
- package/src/commands/runtime.js +25 -10
- package/src/commands/setup.js +30 -6
- package/src/commands/skill.js +78 -17
- package/src/commands/terminal.js +20 -8
- package/src/errors/contract.js +4 -4
- package/src/errors/diagnostic.js +1 -1
- package/src/errors/index.js +1 -1
- package/src/index.js +20 -0
- package/src/indexing/ingest/materials.js +237 -30
- package/src/knowledge/source.js +1 -1
- package/src/lib/setup/runtime.js +4 -2
- package/src/lib/slash-commands.js +1 -1
- package/src/local/runtime-store.js +24 -2
- package/src/local/sqlite.js +17 -0
- package/src/local-engine/assets.js +37 -0
- package/src/local-engine/audit.js +133 -0
- package/src/local-engine/benchmark.js +26 -0
- package/src/local-engine/contracts.js +101 -0
- package/src/local-engine/engines.js +142 -0
- package/src/local-engine/index.js +18 -0
- package/src/local-engine/runtime.js +39 -0
- package/src/sam/operations/package-audit.js +41 -0
- package/src/sam/seed.js +2 -0
- package/skills/grok-auth/SKILL.md +0 -110
- package/skills/grok-auth/agentsam.skill.json +0 -25
- package/skills/grok-auth/references/grok-identity.md +0 -64
- package/skills/grok-auth/references/per-user-data.md +0 -49
- package/skills/grok-auth/references/prewired-and-env.md +0 -54
- package/skills/grok-auth/references/session-ui.md +0 -133
- package/skills/grok-auth/references/sign-in-methods.md +0 -55
- package/skills/grok-auth/references/wiring.md +0 -71
- package/skills/grok-building-games/SKILL.md +0 -213
- package/skills/grok-building-games/agentsam.skill.json +0 -25
- package/skills/grok-building-games/references/3d-libs.md +0 -64
- package/skills/grok-building-games/references/ai-pathfinding.md +0 -122
- package/skills/grok-building-games/references/audio.md +0 -129
- package/skills/grok-building-games/references/babylon.md +0 -202
- package/skills/grok-building-games/references/collision-physics.md +0 -122
- package/skills/grok-building-games/references/ecs-architecture.md +0 -111
- package/skills/grok-building-games/references/game-feel-juice.md +0 -110
- package/skills/grok-building-games/references/genres/board-card-chess.md +0 -89
- package/skills/grok-building-games/references/genres/endless-runner.md +0 -83
- package/skills/grok-building-games/references/genres/fps.md +0 -105
- package/skills/grok-building-games/references/genres/platformer-2d.md +0 -97
- package/skills/grok-building-games/references/genres/puzzle-match3-tetris.md +0 -131
- package/skills/grok-building-games/references/genres/racing-kart.md +0 -106
- package/skills/grok-building-games/references/genres/topdown-twin-stick.md +0 -97
- package/skills/grok-building-games/references/genres/tower-defense.md +0 -84
- package/skills/grok-building-games/references/genres/voxel-minecraft.md +0 -89
- package/skills/grok-building-games/references/input.md +0 -139
- package/skills/grok-building-games/references/phaser.md +0 -277
- package/skills/grok-building-games/references/procedural-generation.md +0 -141
- package/skills/grok-building-games/references/save-persistence.md +0 -108
- package/skills/grok-building-games/references/threejs-foundational.md +0 -148
- package/skills/grok-controls/SKILL.md +0 -274
- package/skills/grok-controls/agentsam.skill.json +0 -25
- package/skills/grok-design-ui/SKILL.md +0 -122
- package/skills/grok-design-ui/agentsam.skill.json +0 -25
- package/skills/grok-design-ui/references/animations.md +0 -379
- package/skills/grok-design-ui/references/performance.md +0 -88
- package/skills/grok-design-ui/references/refined-ui.md +0 -384
- package/skills/grok-design-ui/references/surfaces.md +0 -247
- package/skills/grok-design-ui/references/typography.md +0 -123
- package/skills/grok-game-animation-frames/SKILL.md +0 -84
- package/skills/grok-game-animation-frames/agentsam.skill.json +0 -25
- package/skills/grok-game-asset-core/SKILL.md +0 -69
- package/skills/grok-game-asset-core/agentsam.skill.json +0 -25
- package/skills/grok-game-character-consistency/SKILL.md +0 -69
- package/skills/grok-game-character-consistency/agentsam.skill.json +0 -25
- package/skills/grok-game-tilesets/SKILL.md +0 -65
- package/skills/grok-game-tilesets/agentsam.skill.json +0 -25
- package/skills/grok-game-ui-icons/SKILL.md +0 -56
- package/skills/grok-game-ui-icons/agentsam.skill.json +0 -25
- package/skills/grok-generate2dmap/LICENSE +0 -21
- package/skills/grok-generate2dmap/SKILL.md +0 -120
- package/skills/grok-generate2dmap/SOURCE.md +0 -6
- package/skills/grok-generate2dmap/agentsam.skill.json +0 -25
- package/skills/grok-generate2dmap/references/deliverables.md +0 -101
- package/skills/grok-generate2dmap/references/layered-map-contract.md +0 -209
- package/skills/grok-generate2dmap/references/map-strategies.md +0 -341
- package/skills/grok-generate2dmap/references/object-production-gate.md +0 -24
- package/skills/grok-generate2dmap/references/pipeline.md +0 -149
- package/skills/grok-generate2dmap/references/prop-pack-contract.md +0 -174
- package/skills/grok-generate2dmap/references/side-scroll-stages.md +0 -29
- package/skills/grok-generate2dmap/scripts/compose_layered_preview.py +0 -157
- package/skills/grok-generate2dmap/scripts/extract_prop_pack.py +0 -369
- package/skills/grok-generate2dsprite/LICENSE +0 -21
- package/skills/grok-generate2dsprite/SKILL.md +0 -313
- package/skills/grok-generate2dsprite/SOURCE.md +0 -6
- package/skills/grok-generate2dsprite/agentsam.skill.json +0 -25
- package/skills/grok-generate2dsprite/references/modes.md +0 -124
- package/skills/grok-generate2dsprite/references/prompt-rules.md +0 -315
- package/skills/grok-generate2dsprite/scripts/generate2dsprite.py +0 -1031
- package/skills/grok-generate2dsprite/scripts/make_layout_guide.py +0 -109
- package/skills/grok-imagine/SKILL.md +0 -201
- package/skills/grok-imagine/agentsam.skill.json +0 -25
- package/skills/grok-multiplayer-p2p/SKILL.md +0 -111
- package/skills/grok-multiplayer-p2p/agentsam.skill.json +0 -25
- package/skills/grok-multiplayer-p2p/references/react-binding.md +0 -144
- package/skills/grok-multiplayer-p2p/references/signaling-relay.md +0 -269
- package/skills/grok-neon/SKILL.md +0 -157
- package/skills/grok-neon/agentsam.skill.json +0 -25
- package/skills/grok-og/SKILL.md +0 -102
- package/skills/grok-og/agentsam.skill.json +0 -25
- package/skills/grok-og/references/brand-pass.md +0 -53
- package/skills/grok-og/references/custom-card.md +0 -107
- package/skills/grok-og/references/favicon-and-icons.md +0 -48
- package/skills/grok-og/references/og-type-contract.md +0 -18
- package/skills/grok-og/references/placeholder-card.md +0 -21
- package/skills/grok-og/references/x-banner.md +0 -41
- package/skills/grok-threejs/SKILL.md +0 -67
- package/skills/grok-threejs/agentsam.skill.json +0 -25
- package/skills/grok-threejs/references/llms-full.txt +0 -2780
- package/skills/grok-video2dsprite/LICENSE +0 -21
- package/skills/grok-video2dsprite/SKILL.md +0 -236
- package/skills/grok-video2dsprite/SOURCE.md +0 -6
- package/skills/grok-video2dsprite/agentsam.skill.json +0 -25
- package/skills/grok-video2dsprite/references/pipeline.md +0 -90
- package/skills/grok-video2dsprite/references/prompt-rules.md +0 -63
- package/skills/grok-video2dsprite/scripts/video2dsprite.py +0 -490
- package/skills/grok-xai-api/SKILL.md +0 -152
- package/skills/grok-xai-api/agentsam.skill.json +0 -25
- package/test/account-session.test.mjs +0 -101
- package/test/agent-capabilities.test.mjs +0 -67
- package/test/app-authority-context.test.mjs +0 -32
- package/test/app-building-skills.test.mjs +0 -61
- package/test/apps-scaffold-contract.test.mjs +0 -115
- package/test/blender-cad.test.mjs +0 -146
- package/test/cad-docker.test.mjs +0 -96
- package/test/capabilities.test.mjs +0 -133
- package/test/cli/api-key-env-whoami.test.mjs +0 -129
- package/test/cli/catalog-authority.test.mjs +0 -64
- package/test/cli/codebaseindex-plan-ux.test.mjs +0 -37
- package/test/cli/eval-live.test.mjs +0 -219
- package/test/cli/go.test.mjs +0 -128
- package/test/cli/local-vault-and-provider-setup.test.mjs +0 -268
- package/test/cli/machine-command.test.mjs +0 -34
- package/test/cli/mcp-client.test.mjs +0 -256
- package/test/cli/preferences-runtime.test.mjs +0 -11
- package/test/cli/runtime-persist.test.mjs +0 -56
- package/test/cli/runtime-ui.test.mjs +0 -141
- package/test/cli/rust-command.test.mjs +0 -21
- package/test/cli/skill-npm-and-env.test.mjs +0 -52
- package/test/cli/status-actions.test.mjs +0 -88
- package/test/cli/wireframes-go-registry.test.mjs +0 -139
- package/test/cli-preferences.test.mjs +0 -46
- package/test/cloudflare-connector.test.mjs +0 -93
- package/test/cloudflare-runtime.test.mjs +0 -82
- package/test/context.test.mjs +0 -159
- package/test/credentials-audit.test.mjs +0 -113
- package/test/deploy-health-scan.test.mjs +0 -95
- package/test/deploy-receipt.test.mjs +0 -91
- package/test/dockerize.test.mjs +0 -93
- package/test/error-diagnostics.test.mjs +0 -124
- package/test/eval-context.test.mjs +0 -37
- package/test/execution-approvals.test.mjs +0 -27
- package/test/fixtures/cad/courtyard-house.json +0 -208
- package/test/fixtures/knowledge-rpc-worker.mjs +0 -16
- package/test/fixtures/project-authority/customer-app/.agentsam/app.json +0 -5
- package/test/fixtures/project-authority/customer-app/apps/my-product/index.ts +0 -1
- package/test/fixtures/project-authority/customer-app/wrangler.toml +0 -17
- package/test/gcloud-auth.test.mjs +0 -111
- package/test/go/build-source-identity.test.mjs +0 -31
- package/test/go/cloudflare-probe.test.mjs +0 -317
- package/test/go/distribution.test.mjs +0 -28
- package/test/google-cloud-connection.test.mjs +0 -83
- package/test/google-cloud-inventory.test.mjs +0 -64
- package/test/indexing-provider.test.mjs +0 -29
- package/test/init-options.test.mjs +0 -35
- package/test/integration/ai-access-onboarding.test.mjs +0 -47
- package/test/integration/app-command.test.mjs +0 -85
- package/test/integration/autorag-project-authority.integration.test.mjs +0 -51
- package/test/integration/autorag-runtime-discovery.test.mjs +0 -107
- package/test/integration/cad-discovery.test.mjs +0 -158
- package/test/integration/cad-docker-executor.test.mjs +0 -249
- package/test/integration/cad-execution.test.mjs +0 -184
- package/test/integration/cad-project-app.test.mjs +0 -31
- package/test/integration/cad-project-d1.test.mjs +0 -25
- package/test/integration/cad-project-native.test.mjs +0 -24
- package/test/integration/cad-project-workflow.test.mjs +0 -9
- package/test/integration/cad-project.test.mjs +0 -85
- package/test/integration/cli-help.test.mjs +0 -41
- package/test/integration/cms-app-scaffold.test.mjs +0 -77
- package/test/integration/cms-site-tenancy-contract.test.mjs +0 -268
- package/test/integration/codebaseindex-ingest.test.mjs +0 -255
- package/test/integration/core-client.integration.test.mjs +0 -55
- package/test/integration/database-hosted-honesty.test.mjs +0 -49
- package/test/integration/desktop-keychain-boundary.test.mjs +0 -45
- package/test/integration/error-runtime-rpc.test.mjs +0 -71
- package/test/integration/features-resolve.test.mjs +0 -147
- package/test/integration/hosted-work-host-authority.test.mjs +0 -47
- package/test/integration/icon-registry.test.mjs +0 -67
- package/test/integration/ingest-discover-models.test.mjs +0 -30
- package/test/integration/inspect-path-cli.test.mjs +0 -46
- package/test/integration/install-script.test.mjs +0 -110
- package/test/integration/inventory-core.test.mjs +0 -53
- package/test/integration/inventory-eligibility.test.mjs +0 -59
- package/test/integration/inventory-evidence.test.mjs +0 -125
- package/test/integration/knowledge-rpc.test.mjs +0 -112
- package/test/integration/local-fs.test.mjs +0 -113
- package/test/integration/local-provider-bridge.test.mjs +0 -65
- package/test/integration/local-studio-settings-production.test.mjs +0 -40
- package/test/integration/lockfile-sync.test.mjs +0 -26
- package/test/integration/merkle-cli.test.mjs +0 -61
- package/test/integration/merkle-persistence-identity.test.mjs +0 -48
- package/test/integration/plugins-cloudflare.integration.test.mjs +0 -28
- package/test/integration/provider-env-cli.test.mjs +0 -60
- package/test/integration/provider-factory.test.mjs +0 -197
- package/test/integration/repository-company-graph.test.mjs +0 -90
- package/test/integration/runtime-migrations.test.mjs +0 -87
- package/test/integration/runtime-status.integration.test.mjs +0 -227
- package/test/integration/sam-activity-recovery.test.mjs +0 -147
- package/test/integration/sam-decision.test.mjs +0 -584
- package/test/integration/sam-kernel.test.mjs +0 -99
- package/test/integration/sam-planning-astar.test.mjs +0 -279
- package/test/integration/scaffold-cloudflare.test.mjs +0 -46
- package/test/integration/skill-runtime.test.mjs +0 -240
- package/test/integration/storage-architecture.test.mjs +0 -154
- package/test/integration/store-discovery.test.mjs +0 -44
- package/test/integration/studio-fs-pty-e2e.test.mjs +0 -294
- package/test/integration/terminal-exec.integration.test.mjs +0 -86
- package/test/integration/theme-package-registry.test.mjs +0 -31
- package/test/integration/tool-runtime.test.mjs +0 -124
- package/test/integration/tool-schema.test.mjs +0 -59
- package/test/jr-dev-skill.test.mjs +0 -26
- package/test/knowledge-context-pack.test.mjs +0 -19
- package/test/knowledge-postgres.test.mjs +0 -40
- package/test/knowledge-service.test.mjs +0 -110
- package/test/knowledge.test.mjs +0 -206
- package/test/live/terminal-transport.live.test.mjs +0 -24
- package/test/local-sessions.test.mjs +0 -57
- package/test/local-studio-deploy.test.mjs +0 -83
- package/test/loopback-oauth.test.mjs +0 -53
- package/test/merkle-render.test.mjs +0 -31
- package/test/mini.test.mjs +0 -140
- package/test/model-catalog.test.mjs +0 -70
- package/test/models.test.mjs +0 -309
- package/test/npm10-lock.test.mjs +0 -29
- package/test/ollama.test.mjs +0 -137
- package/test/open-url.test.mjs +0 -64
- package/test/openai-responses.test.mjs +0 -155
- package/test/portable-context.test.mjs +0 -94
- package/test/project-config.test.mjs +0 -81
- package/test/project-rules.test.mjs +0 -44
- package/test/provider-credentials.test.mjs +0 -106
- package/test/rehydrate.test.mjs +0 -25
- package/test/release-hygiene.test.mjs +0 -42
- package/test/repository-snapshot-view.test.mjs +0 -112
- package/test/responses-runner.test.mjs +0 -465
- package/test/runtime-activity.test.mjs +0 -98
- package/test/sdk-worker-contract.test.mjs +0 -72
- package/test/security.test.mjs +0 -266
- package/test/setup-planner.test.mjs +0 -74
- package/test/setup-runtime.test.mjs +0 -51
- package/test/shell.test.mjs +0 -281
- package/test/skills.test.mjs +0 -22
- package/test/smoke.mjs +0 -122
- package/test/telemetry.test.mjs +0 -79
- package/test/terminal/local-pty.mock.test.mjs +0 -153
- package/test/terminal-machine-identity.test.mjs +0 -88
- package/test/theme-portability.test.mjs +0 -14
- package/test/tools-search.test.mjs +0 -73
- package/test/unit/website-assets-bindings.test.mjs +0 -60
- package/test/whoami-resume.test.mjs +0 -157
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
# Phaser 3 / 4 — Deep Engine Guide (scenes, physics, scale, tilemaps, pooling, architecture)
|
|
2
|
-
|
|
3
|
-
Phaser is the default choice for **2D browser games**. This file goes deep on the parts that actually break in generated games. It assumes the general loop/mobile/perf rules in `threejs-foundational.md` and does **not** repeat them.
|
|
4
|
-
|
|
5
|
-
> **Version note (verified):** Phaser **3** is the mature, ubiquitous line (3.90+). Phaser **4** shipped in 2025 with a rewritten WebGL renderer (internally "Beam") and optional **WebGPU** path, plus the core split into modular packages. Phaser 4 is *intentionally near-API-compatible with Phaser 3* — the Scene lifecycle, Arcade/Matter physics, Scale Manager, Loader and GameObject APIs described here carry over. Prefer Phaser 3 (3.90.x) for maximum stability/plugin compatibility today; use Phaser 4 when you want the faster renderer/WebGPU and can tolerate a younger ecosystem. When unsure, target v3 API surface — it runs on both.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## 1. Scene model — the thing to get right first
|
|
10
|
-
|
|
11
|
-
A Phaser game is a **stack of Scenes**, each an almost self-contained world (own display list, cameras, input, tweens, physics, clock). The `Phaser.Game` instance owns only truly global systems: **Renderer, Animation Manager, global Cache, Registry (global DataManager), Input Manager, Scene Manager, Sound Manager, TimeStep**. Everything else (`this.add`, `this.tweens`, `this.physics`, `this.input`, `this.cameras`) is **per-scene**. A tween created in Scene A is unrelated to one in Scene B.
|
|
12
|
-
|
|
13
|
-
### Lifecycle callbacks (exact order)
|
|
14
|
-
|
|
15
|
-
```js
|
|
16
|
-
class GameScene extends Phaser.Scene {
|
|
17
|
-
constructor() { super('game'); } // unique string key
|
|
18
|
-
init(data) {} // (1) reset per-run state HERE, receives data from start/launch
|
|
19
|
-
preload() {} // (2) queue asset loads only
|
|
20
|
-
create(data) {} // (3) build objects; assets from preload are now ready
|
|
21
|
-
update(time, delta){} // (4) every tick while RUNNING (delta in ms)
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Status flow: `PENDING → INIT (booted) → START → LOADING → CREATING → RUNNING`. `RUNNING` can go to `PAUSED` (renders, no update) or `SLEEPING` (no update, no render), then back. `stop()` → `SHUTDOWN` (can restart). `remove()` → `DESTROYED` (gone).
|
|
26
|
-
|
|
27
|
-
### The scene stack pattern (best-practice architecture)
|
|
28
|
-
|
|
29
|
-
Run **multiple scenes at once**, layered bottom→top by config order:
|
|
30
|
-
|
|
31
|
-
- **Boot** — load the tiny assets needed for the loading screen (bar image, logo); set Scale/registry defaults; `this.scene.start('preload')`.
|
|
32
|
-
- **Preload** — load everything with a visible progress bar (`this.load.on('progress', v => …)`), then `start('menu')`.
|
|
33
|
-
- **Menu / Title** — UI, start button.
|
|
34
|
-
- **Game** — actual gameplay.
|
|
35
|
-
- **UI / HUD** — run **in parallel** over Game via `this.scene.launch('ui')`; keeps HUD stable while Game restarts. Communicate via events/registry, never direct references.
|
|
36
|
-
- **Pause / GameOver** — overlay scenes launched over Game.
|
|
37
|
-
|
|
38
|
-
### Scene control verbs (get these exactly right — a top bug source)
|
|
39
|
-
|
|
40
|
-
| Method | Effect on target | Effect on caller |
|
|
41
|
-
|---|---|---|
|
|
42
|
-
| `start('k')` | **stops** then starts k | **stops** caller |
|
|
43
|
-
| `launch('k')` | starts k (parallel) | caller keeps running |
|
|
44
|
-
| `switch('k')` | starts or **wakes** k | **sleeps** caller |
|
|
45
|
-
| `run('k')` | resume if paused / wake if sleeping / restart if running / else start | caller keeps running |
|
|
46
|
-
| `pause`/`resume` | freeze update, keep render | — |
|
|
47
|
-
| `sleep`/`wake` | freeze update + render | — |
|
|
48
|
-
| `stop` | shutdown | — |
|
|
49
|
-
|
|
50
|
-
Rules of thumb: gameplay you replay from scratch → **start/stop** or **sleep/wake**; a modal (pause menu, shop) over live gameplay → **pause/resume** or **launch** an overlay; menus you revisit → **sleep/wake** is easier to reason about than start/stop.
|
|
51
|
-
|
|
52
|
-
### The #1 Phaser correctness bug: state not reset on restart
|
|
53
|
-
|
|
54
|
-
Scenes are **booted once, started many times**. Module-level or constructor-set flags persist across restarts.
|
|
55
|
-
|
|
56
|
-
```js
|
|
57
|
-
// BROKEN: gameOver stays true after restart → instant game over
|
|
58
|
-
class S extends Phaser.Scene { constructor(){ super('s'); this.gameOver = false; } }
|
|
59
|
-
|
|
60
|
-
// CORRECT: reset run state in init()
|
|
61
|
-
class S extends Phaser.Scene {
|
|
62
|
-
constructor(){ super('s'); }
|
|
63
|
-
init(){ this.gameOver = false; this.score = 0; } // runs on every start
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Related: arrays of destroyed game objects survive restart. **Clean up on SHUTDOWN**:
|
|
68
|
-
```js
|
|
69
|
-
this.events.once('shutdown', () => { this.enemies.length = 0; });
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Cleanup rule
|
|
73
|
-
|
|
74
|
-
On `shutdown`, Phaser auto-destroys the scene's display list, tweens, timers and input. But **external references, global timers, DOM listeners, EventEmitter subscriptions on cross-scene emitters, and pooled arrays are yours to clear**. A destroyed GameObject still registered on an emitter is a classic crash. Always `this.events.once('shutdown', cleanup)`.
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## 2. Physics: Arcade vs Matter (choose deliberately)
|
|
79
|
-
|
|
80
|
-
Phaser ships two physics systems. **Pick Arcade unless you specifically need Matter** — mixing/over-choosing is a common performance and complexity mistake.
|
|
81
|
-
|
|
82
|
-
### Arcade Physics — AABB, fast, for platformers/shooters/arcade
|
|
83
|
-
|
|
84
|
-
- **Only axis-aligned bounding boxes (rectangles) and circles.** No rotation of bodies, no slopes, no polygons. Extremely fast; handles thousands of bodies.
|
|
85
|
-
- Two body types: **Dynamic** (`this.physics.add.sprite`) responds to velocity/gravity/collisions; **Static** (`this.physics.add.staticGroup`) never moves (platforms, walls) and is cheaper.
|
|
86
|
-
- Core calls: `body.setVelocity(x,y)`, `setGravityY`, `setBounce`, `setCollideWorldBounds(true)`, `setImmovable(true)`, `setAllowGravity(false)`.
|
|
87
|
-
- **Collide vs Overlap:**
|
|
88
|
-
- `this.physics.add.collider(a, b, cb)` — separates bodies (they block each other), optional callback.
|
|
89
|
-
- `this.physics.add.overlap(a, b, cb)` — detects overlap without separation (pickups, hitboxes, triggers).
|
|
90
|
-
- Both accept Groups, arrays, or single objects; add a `processCallback` (4th arg) to conditionally allow/deny a collision.
|
|
91
|
-
- Config: `physics: { default: 'arcade', arcade: { gravity: { y: 300 }, debug: true } }`. Turn on `debug` while building — it draws body outlines and velocity vectors and reveals 90% of "collision doesn't work" issues.
|
|
92
|
-
- Set body size explicitly for non-rectangular sprites: `sprite.body.setSize(w,h).setOffset(x,y)` or `setCircle(r)`.
|
|
93
|
-
- **Fast small objects tunnel through thin walls** (AABB, discrete step). Mitigate: thicker colliders, cap max velocity, or set world bounds; Arcade has limited continuous collision.
|
|
94
|
-
|
|
95
|
-
### Matter.js — full rigid-body, for physics-toys/ragdolls/complex shapes
|
|
96
|
-
|
|
97
|
-
- Real rigid-body dynamics: rotation, arbitrary convex/compound polygons, constraints/joints, springs, restitution, friction, sleeping bodies.
|
|
98
|
-
- Use when the *physics itself is the game* (Angry-Birds-like, stacking, contraptions, vehicles) or you need realistic collisions/rotation.
|
|
99
|
-
- Config: `physics: { default: 'matter', matter: { gravity: { y: 1 }, debug: true } }`.
|
|
100
|
-
- `this.matter.add.sprite(x,y,key,null,{ shape:'circle', restitution:0.6 })`. Use collision events: `this.matter.world.on('collisionstart', (e)=>{ for (const p of e.pairs){…} })`, plus **collision filters/categories** for what hits what.
|
|
101
|
-
- Heavier than Arcade; watch body counts on mobile. Enable body sleeping for idle stacks.
|
|
102
|
-
|
|
103
|
-
**Decision:** platformer, top-down shooter, endless runner, breakout, most casual games → **Arcade**. Physics sandbox, realistic stacking/rotation, joints → **Matter**. Don't reach for Matter just for "better collisions" — 90% of 2D games are Arcade games.
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## 3. Scale Manager — responsive/mobile without stretching
|
|
108
|
-
|
|
109
|
-
Set once in game config. The two modes that matter:
|
|
110
|
-
|
|
111
|
-
```js
|
|
112
|
-
scale: {
|
|
113
|
-
mode: Phaser.Scale.FIT, // letterbox: keep aspect, fit inside parent
|
|
114
|
-
autoCenter: Phaser.Scale.CENTER_BOTH,
|
|
115
|
-
width: 800, height: 600, // your fixed design resolution
|
|
116
|
-
parent: 'game',
|
|
117
|
-
// min/max clamp the FIT scaling:
|
|
118
|
-
min: { width: 400, height: 300 }, max: { width: 1600, height: 1200 }
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
- **`FIT`** — canvas scales to fit the parent preserving aspect ratio; letterbox bars appear. Your game logic runs at a **fixed design resolution** (800×600 here) — simplest and most predictable. Best default for most games.
|
|
123
|
-
- **`RESIZE`** — canvas always matches parent size; **no fixed resolution**, game world dimensions change. You must handle the `resize` event and reposition/relayout UI yourself (`this.scale.on('resize', (gameSize)=>{…})`). Use for games that should truly fill the screen (strategy, infinite canvas) where you can reflow layout.
|
|
124
|
-
- `ENVELOP` — cover the parent (may crop). `WIDTH_CONTROLS_HEIGHT` / `HEIGHT_CONTROLS_WIDTH` — lock one axis.
|
|
125
|
-
- `NONE` — fixed, no scaling.
|
|
126
|
-
|
|
127
|
-
**Orientation:** listen `this.scale.on('orientationchange', o => …)` or check `this.scale.isPortrait`. For a landscape-only game, show a "rotate your device" overlay in portrait rather than fighting the layout. Combine with `viewport` meta + `touch-action: none` (see three.js file §7).
|
|
128
|
-
|
|
129
|
-
**Retina/DPR:** Phaser respects `resolution`/DPR via the renderer; for pixel-art keep `pixelArt: true` (sets nearest-neighbor + disables antialias) and `roundPixels: true` to avoid sub-pixel shimmer. Rendering at a modest internal resolution and letting FIT upscale is a legit perf win on mobile.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## 4. Sprites, atlases & animations
|
|
134
|
-
|
|
135
|
-
- **Always pack sprites into a texture atlas** (TexturePacker → JSON Hash/Array, or free tools). One atlas = one texture bind = fewer draw calls and no seams. `this.load.atlas('sheet','sheet.png','sheet.json')`, then `this.add.sprite(x,y,'sheet','frameName')`.
|
|
136
|
-
- Uniform grid frames → `this.load.spritesheet('run','run.png',{ frameWidth:32, frameHeight:32 })`.
|
|
137
|
-
- Animations are **global** (stored on the Animation Manager), defined once, reused by any sprite in any scene:
|
|
138
|
-
```js
|
|
139
|
-
this.anims.create({ key:'run', frames:this.anims.generateFrameNames('sheet',{prefix:'run_',start:0,end:7,zeroPad:2}), frameRate:12, repeat:-1 });
|
|
140
|
-
sprite.play('run');
|
|
141
|
-
```
|
|
142
|
-
- Batching: sprites sharing the **same texture** batch into few draw calls. Interleaving textures (sprite from atlas A, then B, then A) breaks the batch. Group same-texture sprites and set depth thoughtfully.
|
|
143
|
-
- Bitmap fonts (`load.bitmapFont`) render far cheaper than lots of dynamic `Text` objects (each `Text` is its own canvas texture — expensive to update every frame). For frequently-changing text prefer bitmap fonts or update sparingly.
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## 5. Tilemaps
|
|
148
|
-
|
|
149
|
-
For levels, use **Tiled** (`.tmx`/exported JSON) rather than hand-placing sprites.
|
|
150
|
-
|
|
151
|
-
```js
|
|
152
|
-
// preload
|
|
153
|
-
this.load.image('tiles','tileset.png');
|
|
154
|
-
this.load.tilemapTiledJSON('map','level1.json');
|
|
155
|
-
// create
|
|
156
|
-
const map = this.make.tilemap({ key:'map' });
|
|
157
|
-
const tileset = map.addTilesetImage('tilesetNameInTiled','tiles');
|
|
158
|
-
const ground = map.createLayer('Ground', tileset, 0, 0);
|
|
159
|
-
ground.setCollisionByProperty({ collides:true }); // set per-tile in Tiled
|
|
160
|
-
this.physics.add.collider(player, ground);
|
|
161
|
-
// object layer for spawns/enemies:
|
|
162
|
-
const objs = map.getObjectLayer('Objects').objects;
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
- **Static layers** (`createLayer`) are fast (culled, batched). Dynamic tile edits use the same layer API (`putTileAt`, `removeTileAt`) — layers are dynamic in current Phaser.
|
|
166
|
-
- Set collision by **property** or by tile index/array; visualize with `layer.renderDebug(graphics)`.
|
|
167
|
-
- Cull off-screen tiles automatically via the camera; keep tile layers reasonably sized, split huge worlds into chunks.
|
|
168
|
-
- Use **object layers** for spawn points, triggers, and entity placement instead of hardcoding coordinates.
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## 6. Object pooling with Groups (the mobile perf lever)
|
|
173
|
-
|
|
174
|
-
Creating/destroying bullets, enemies, particles every frame thrashes GC and stutters. **Pool them.**
|
|
175
|
-
|
|
176
|
-
```js
|
|
177
|
-
this.bullets = this.physics.add.group({
|
|
178
|
-
classType: Bullet, maxSize: 64, runChildUpdate: true
|
|
179
|
-
});
|
|
180
|
-
// fire:
|
|
181
|
-
const b = this.bullets.get(x, y); // reuse dead one or make new (up to maxSize)
|
|
182
|
-
if (!b) return; // pool exhausted → skip
|
|
183
|
-
b.enableBody(true, x, y, true, true); // reactivate + show
|
|
184
|
-
// on expire/off-screen: DON'T destroy — recycle:
|
|
185
|
-
b.disableBody(true, true); // deactivate + hide, returns to pool
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
- `get()` returns an **inactive** member (recycled) or creates a new one until `maxSize`; returns `null` when full — always null-check.
|
|
189
|
-
- `killAndHide(child)` / `disableBody(true,true)` recycles; avoid `destroy()` for pooled objects.
|
|
190
|
-
- `runChildUpdate:true` calls each active child's `preUpdate`/`update` — put per-object logic in the class.
|
|
191
|
-
- Only iterate **active** members: `this.bullets.getChildren()` and skip `!child.active`, or use `group.getMatching('active', true)`.
|
|
192
|
-
- Reuse tween/particle emitters too; destroy emitters and tweens on `SHUTDOWN`.
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
## 7. Input
|
|
197
|
-
|
|
198
|
-
- Keyboard: `this.cursors = this.input.keyboard.createCursorKeys()` (arrows + space/shift), or `this.keys = this.input.keyboard.addKeys('W,A,S,D')`. **Read state in `update` via `.isDown`**; use `Phaser.Input.Keyboard.JustDown(key)` for single-press actions. Don't drive movement off `keydown` events.
|
|
199
|
-
- Pointer/touch: `this.input.on('pointerdown', p => …)`; enable object interaction with `sprite.setInteractive()` then `sprite.on('pointerdown', …)`. Set explicit hit areas for irregular sprites.
|
|
200
|
-
- Unify keyboard + touch + **Gamepad API** (`this.input.gamepad`) into one input-state object your `update` reads, so control code doesn't branch per device.
|
|
201
|
-
- Drag: `this.input.setDraggable(sprite)` + `'drag'` event. For virtual joysticks on mobile, integrate nipplejs or the rex virtual joystick plugin and feed normalized -1..1 into your input state.
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## 8. Cameras & tweens
|
|
206
|
-
|
|
207
|
-
- Each scene has a main camera (`this.cameras.main`). Follow the player: `camera.startFollow(player, true, 0.08, 0.08)` (lerp for smooth follow), set `camera.setBounds(0,0,mapW,mapH)` and `setZoom()`. `setDeadzone()` avoids jitter around the player.
|
|
208
|
-
- Effects for free polish: `camera.shake(200, 0.01)`, `camera.flash()`, `camera.fade()`. Multiple cameras enable split-screen / minimaps (`camera.ignore(objects)` to exclude).
|
|
209
|
-
- Tweens for juice: `this.tweens.add({ targets:sprite, scale:1.2, yoyo:true, duration:120, ease:'Sine.easeInOut' })`. **Always destroy long-lived/looping tweens on SHUTDOWN** or they reference dead objects.
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## 9. Cross-scene communication (avoid spaghetti)
|
|
214
|
-
|
|
215
|
-
- **Registry** (global DataManager): `this.registry.set('score', 0)` / `get` — shared across all scenes; emits `changedata` events.
|
|
216
|
-
- **Scene events / a shared EventEmitter** ("EventsCenter"): create one `new Phaser.Events.EventEmitter()` in a module, import into scenes, emit/subscribe. Lets the UI scene react to Game scene events with **zero direct coupling**. Always `off()` listeners on shutdown.
|
|
217
|
-
- `this.scene.get('ui').events.emit(...)` works but couples scenes — prefer the shared emitter/registry.
|
|
218
|
-
- Pass one-shot data via `this.scene.start('game', { level: 3 })` → arrives in `init(data)` / `create(data)`.
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## 10. Performance checklist (Phaser-specific)
|
|
223
|
-
|
|
224
|
-
- **Object pooling** for anything spawned repeatedly (bullets, enemies, particles, floating text).
|
|
225
|
-
- **Texture atlases** everywhere; group same-texture draws to keep batches intact.
|
|
226
|
-
- Turn **off physics debug** in production; only add bodies to objects that need them.
|
|
227
|
-
- Process **only active** objects; set `sprite.active=false`/`visible=false` for off-screen entities instead of updating them.
|
|
228
|
-
- Prefer **static** physics bodies / static tile layers for non-moving geometry.
|
|
229
|
-
- Minimize per-frame `Text` updates and `Graphics` re-draws (redrawing a Graphics every frame is expensive) — cache to a texture (`generateTexture`) or use bitmap fonts.
|
|
230
|
-
- Avoid per-frame allocations in `update`; reuse vectors/objects.
|
|
231
|
-
- Destroy tweens, timers (`this.time` events), and particle emitters on `SHUTDOWN`; clear pooled arrays.
|
|
232
|
-
- Lazy-load per-level assets in that level's preload; unload with `this.textures.remove` / `this.cache` when leaving big levels.
|
|
233
|
-
- Smaller canvas + FIT upscale on low-end mobile; consider `Phaser.CANVAS` fallback only if WebGL is unavailable (WebGL is default and much faster).
|
|
234
|
-
- Profile with the browser FPS meter and `game.loop.actualFps`; Phaser 4's WebGPU path reduces CPU draw-call overhead further.
|
|
235
|
-
|
|
236
|
-
---
|
|
237
|
-
|
|
238
|
-
## 11. Common Phaser pitfalls (checklist)
|
|
239
|
-
|
|
240
|
-
- Run-state not reset in `init()` → instant game-over / carried-over score after restart.
|
|
241
|
-
- Not cleaning arrays/emitter listeners on `shutdown` → crashes on destroyed objects after restart.
|
|
242
|
-
- Using `this.scene.start` when you meant `launch` (start stops the caller — kills your HUD).
|
|
243
|
-
- Movement/animation not scaled by `delta` (Phaser `update(time, delta)` gives delta in **milliseconds**).
|
|
244
|
-
- Physics body doesn't match sprite art → set `body.setSize/offset`; wrong/no collider group; forgetting `setCollideWorldBounds`.
|
|
245
|
-
- Fast objects tunneling through thin walls in Arcade → thicker walls / velocity cap.
|
|
246
|
-
- Text objects updated every frame tanking FPS → bitmap fonts / update on change only.
|
|
247
|
-
- Creating instead of pooling bullets/enemies → GC stutter on mobile.
|
|
248
|
-
- `RESIZE` scale mode without repositioning UI → misaligned HUD; use `FIT` unless you handle `resize`.
|
|
249
|
-
- Loading assets in `create` instead of `preload` → assets not ready / race conditions.
|
|
250
|
-
- Mixing Matter + Arcade needlessly, or choosing Matter when Arcade suffices → perf and complexity cost.
|
|
251
|
-
|
|
252
|
-
---
|
|
253
|
-
|
|
254
|
-
## Defaults to apply
|
|
255
|
-
|
|
256
|
-
- **Default to Phaser (v3 API) for any 2D game**; scaffold the **Boot → Preload(progress bar) → Menu → Game → parallel UI → Pause/GameOver** scene stack automatically.
|
|
257
|
-
- **Always put per-run state reset in `init()`** and register a `this.events.once('shutdown', cleanup)` in every gameplay scene — this alone eliminates the most common generated-game bug (broken restart).
|
|
258
|
-
- **Choose Arcade physics by default; only emit Matter when the physics is the game.** Encode the decision table above in the builder's engine-selection step.
|
|
259
|
-
- **Emit object pools (Groups with `maxSize` + recycle via `disableBody`) for bullets/enemies/particles** rather than create/destroy, and **texture atlases** for all sprites.
|
|
260
|
-
- Use **`Scale.FIT` + fixed design resolution + `CENTER_BOTH`** as the responsive default; add a rotate-device overlay for orientation-locked games.
|
|
261
|
-
- Load levels from **Tiled JSON tilemaps** with collision-by-property and object layers for spawns, instead of hardcoded coordinates.
|
|
262
|
-
- Decouple scenes via a **shared EventEmitter + Registry**, never direct scene references.
|
|
263
|
-
- Turn **physics `debug` on during generation/testing, off in the shipped build.**
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## Sources
|
|
268
|
-
- Phaser official docs — Scenes (lifecycle, systems, control methods, restart pitfalls): https://docs.phaser.io/phaser/concepts/scenes
|
|
269
|
-
- Phaser docs — Arcade Physics: https://docs.phaser.io/phaser/concepts/physics/arcade ; Matter Physics: https://docs.phaser.io/phaser/concepts/physics/matter
|
|
270
|
-
- Phaser docs — Scale Manager: https://docs.phaser.io/phaser/concepts/scale-manager
|
|
271
|
-
- Phaser docs — Tilemaps: https://docs.phaser.io/phaser/concepts/tilemaps ; Loader: https://docs.phaser.io/phaser/concepts/loader
|
|
272
|
-
- Phaser docs — Groups & object pooling: https://docs.phaser.io/phaser/concepts/gameobjects/group ; Animations: https://docs.phaser.io/phaser/concepts/animations
|
|
273
|
-
- Phaser docs — Input (keyboard/pointer/gamepad): https://docs.phaser.io/phaser/concepts/input
|
|
274
|
-
- Phaser 4 announcement / release: https://phaser.io/news (Phaser 4 + Beam renderer / WebGPU) and https://github.com/phaserjs/phaser/releases
|
|
275
|
-
- API reference: https://newdocs.phaser.io/docs/latest
|
|
276
|
-
- franzeus.medium.com — "How I optimized my Phaser 3 action game" (pooling, atlases, active-only processing)
|
|
277
|
-
- joshmorony.com — Phaser scaling / responsive guides
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# Procedural Generation (seeded RNG, noise, dungeons/mazes/terrain, wave function collapse)
|
|
2
|
-
|
|
3
|
-
Consolidated from Red Blob Games, the simplex-noise library, and WFC primary sources (see Sources). Focus: what an AI builder needs to generate **varied but reproducible** worlds without the classic bugs — non-deterministic output, disconnected dungeons, blobby/banded terrain.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Seeded RNG is the foundation — never use bare `Math.random()`
|
|
8
|
-
|
|
9
|
-
`Math.random()` is **not seedable**, so you can't reproduce a level, share a seed, debug a bad map, or sync generation across clients in multiplayer. **Always drive procedural generation from a seeded PRNG.**
|
|
10
|
-
|
|
11
|
-
**mulberry32** — the go-to small, fast, good-quality 32-bit seeded PRNG:
|
|
12
|
-
```js
|
|
13
|
-
function mulberry32(seed) {
|
|
14
|
-
return function () {
|
|
15
|
-
let t = (seed += 0x6D2B79F5);
|
|
16
|
-
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
17
|
-
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
18
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296; // [0,1)
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
const rng = mulberry32(12345); // same seed → identical sequence forever
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
Rules:
|
|
25
|
-
- **Hash string seeds to an int first** (e.g. `xmur3`) so `"cave-42"` maps to a stable number.
|
|
26
|
-
- **Use separate RNG streams for independent systems** (terrain vs loot vs enemy placement) so changing one doesn't reshuffle the others (`mulberry32(seed)`, `mulberry32(seed ^ 0x9e3779b9)`, …).
|
|
27
|
-
- Add helpers: `randRange(a,b)=a+rng()*(b-a)`, `randInt`, `pick(arr)`, `shuffle` (Fisher–Yates using `rng`).
|
|
28
|
-
- For **networked/deterministic** generation, both sides must run the *same* PRNG and consume it in the *same order* (deterministic multiplayer is out of scope on this deploy target). Order-of-consumption bugs are the #1 desync source.
|
|
29
|
-
- Alternatives: `alea`, PCG. mulberry32 is plenty for games.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## 2. Noise: value / Perlin / simplex (for terrain, textures, organic fields)
|
|
34
|
-
|
|
35
|
-
Random-per-cell is white noise (harsh, uncorrelated). **Coherent noise** produces smooth, natural-looking fields where nearby points are similar.
|
|
36
|
-
|
|
37
|
-
- **Value noise** — cheap, interpolate random lattice values. OK for simple stuff.
|
|
38
|
-
- **Perlin noise** — classic gradient noise; can show axis-aligned artifacts.
|
|
39
|
-
- **Simplex / OpenSimplex** — fewer directional artifacts, scales better to higher dimensions; preferred default. Use the **`simplex-noise` npm package**.
|
|
40
|
-
- **v4 API changed:** no more `new SimplexNoise()`. Use factory functions and pass your seeded PRNG:
|
|
41
|
-
```js
|
|
42
|
-
import { createNoise2D } from 'simplex-noise';
|
|
43
|
-
const noise2D = createNoise2D(mulberry32(12345)); // returns ~[-1, 1]
|
|
44
|
-
const v = noise2D(x * 0.01, y * 0.01); // scale coords = "frequency"
|
|
45
|
-
```
|
|
46
|
-
(Also `createNoise3D`/`createNoise4D`; passing a PRNG makes output reproducible — without one it uses `Math.random()` = non-reproducible.)
|
|
47
|
-
|
|
48
|
-
**Key techniques (from Red Blob Games "Making maps with noise"):**
|
|
49
|
-
- **Frequency:** multiply input coords by a scale; small scale = large smooth features.
|
|
50
|
-
- **Octaves / fBm:** sum several noise layers at increasing frequency and decreasing amplitude (`amplitude *= persistence(≈0.5)`, `frequency *= lacunarity(≈2)`) for natural detail.
|
|
51
|
-
- **Normalize** noise from [−1,1] to [0,1] before thresholding.
|
|
52
|
-
- **Redistribution:** `elev = Math.pow(elev, k)` to bias toward valleys/plains vs peaks.
|
|
53
|
-
- **Island mask:** multiply by a radial falloff so edges are water.
|
|
54
|
-
- **Biomes:** sample **two independent noise fields** (elevation + moisture) and look up a Whittaker-style biome table — don't derive moisture from elevation (they'd correlate and look wrong).
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## 3. Dungeon / room generation
|
|
59
|
-
|
|
60
|
-
Common approaches, pick per game:
|
|
61
|
-
- **Rooms + corridors (BSP or random placement):** place non-overlapping rooms, then connect. BSP recursively splits space and puts a room in each leaf; connect sibling leaves → guaranteed structure. Random placement: drop rooms, reject overlaps, connect with L-shaped/A* corridors.
|
|
62
|
-
- **Cellular automata caves:** fill grid randomly (~45% wall), run several smoothing passes (`cell = neighbors≥5 ? wall : floor`) → organic caverns. Then **flood-fill and keep only the largest connected region** (or carve tunnels to connect regions).
|
|
63
|
-
- **Drunkard's walk / random walk:** carve floors along a random walk for winding caves.
|
|
64
|
-
|
|
65
|
-
**Critical rule: guarantee connectivity.** After generation, **flood-fill from the entrance and verify every important tile (exit, key rooms, loot) is reachable.** Discard/regenerate or carve connectors otherwise. "Unreachable exit / locked-in player" is the #1 procgen bug. Also validate: min room count, spawn ≠ exit, no soft-locks.
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## 4. Maze generation
|
|
70
|
-
|
|
71
|
-
- Grid where each cell has walls; carve passages.
|
|
72
|
-
- **Recursive backtracker (randomized DFS):** deep, winding mazes with long corridors — most common.
|
|
73
|
-
- **Randomized Prim's / Kruskal's:** more uniform, bushier mazes.
|
|
74
|
-
- All produce a "perfect maze" (exactly one path between any two cells). Add **loops** by knocking out extra walls if you want multiple routes.
|
|
75
|
-
- Use your seeded `rng` for neighbor choice so the maze is reproducible.
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## 5. Terrain generation
|
|
80
|
-
|
|
81
|
-
- **Heightmap from fBm simplex** (see §2): sample noise per cell → elevation → color/mesh. Threshold into water/sand/grass/rock/snow bands.
|
|
82
|
-
- **Domain warping** (offset sample coords by another noise) for more natural, less "blobby" coastlines.
|
|
83
|
-
- For 3D: feed the heightmap into a plane geometry's vertex Y (Three.js `PlaneGeometry` + displace vertices) and `computeVertexNormals()`; consider chunking + LOD for large worlds.
|
|
84
|
-
- Keep generation **deterministic per chunk**: derive each chunk's local seed from `(worldSeed, chunkX, chunkY)` so infinite/streamed worlds are stable and reproducible.
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## 6. Wave Function Collapse (WFC) basics
|
|
89
|
-
|
|
90
|
-
WFC is a constraint-solver that generates output where every local neighborhood matches examples/adjacency rules — great for tile maps, textures, and levels that must "fit together."
|
|
91
|
-
|
|
92
|
-
Algorithm (overlapping or simple-tiled):
|
|
93
|
-
1. Every cell starts in **superposition** (all tiles possible).
|
|
94
|
-
2. **Observe:** pick the lowest-entropy cell (fewest remaining options) and **collapse** it to one tile (weighted-random).
|
|
95
|
-
3. **Propagate:** remove now-impossible neighbors' options based on adjacency rules; repeat until stable.
|
|
96
|
-
4. Loop until all cells collapsed, or **backtrack/restart on contradiction** (a cell with zero options).
|
|
97
|
-
|
|
98
|
-
Rules & gotchas:
|
|
99
|
-
- Adjacency rules come from a sample image (overlapping model) or hand-authored tile edges (simple-tiled model).
|
|
100
|
-
- **Contradictions happen** — implement restart or backtracking; without it, generation hangs/throws.
|
|
101
|
-
- Weight tiles for aesthetics; use noise (from §2) to bias regional weights for large-scale structure, then WFC for coherent local detail.
|
|
102
|
-
- For JS: **mxgmn/WaveFunctionCollapse** (original), Boris the Brave's write-up + **DeBroglie**, or ndarray-based JS ports.
|
|
103
|
-
|
|
104
|
-
> Note: Red Blob Games does **not** have a WFC tutorial (a common false citation). Cite Maxim Gumin's repo and Boris the Brave instead. Red Blob Games *is* the canonical source for **noise maps** and **A\*/grids**.
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## 7. Bug-prevention checklist
|
|
109
|
-
- **`Math.random()` for generation** → non-reproducible, unshareable, undebuggable, desyncs multiplayer; use a seeded PRNG.
|
|
110
|
-
- **String seed used directly** → NaN/garbage; hash to int (xmur3) first.
|
|
111
|
-
- **Shared single RNG stream across systems** → tweaking one system reshuffles all; use separate streams.
|
|
112
|
-
- **No connectivity check** → unreachable exits / trapped player; flood-fill validate & regenerate.
|
|
113
|
-
- **Forgetting to seed simplex-noise (v4)** → falls back to `Math.random()`, non-reproducible; pass your PRNG to `createNoise2D`.
|
|
114
|
-
- **Single-octave noise** → smooth but featureless; add fBm octaves.
|
|
115
|
-
- **Deriving moisture from elevation** → correlated, unrealistic biomes; use independent noise fields.
|
|
116
|
-
- **WFC with no contradiction handling** → infinite loop / crash; add restart/backtrack.
|
|
117
|
-
- **Per-frame regeneration or huge synchronous gen** → jank; generate in chunks / a web worker / on level load.
|
|
118
|
-
- **Non-deterministic chunk seeds** → seams/flicker in streamed worlds; derive chunk seed from `(worldSeed, cx, cy)`.
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Defaults to apply
|
|
123
|
-
- **Every generated procgen game gets a visible/optional seed** driven by **mulberry32** (with xmur3 for string seeds), plus `randRange/randInt/pick/shuffle` helpers and **separate RNG streams** per system. Reproducibility is free QA, shareable content, and multiplayer-safe.
|
|
124
|
-
- **Terrain/organic → seeded `simplex-noise` v4 (`createNoise2D(rng)`) with fBm octaves + redistribution + island mask + independent elevation/moisture** per Red Blob Games.
|
|
125
|
-
- **Dungeons → rooms+corridors or cellular-automata caves, ALWAYS followed by a flood-fill connectivity guarantee** (regenerate on failure). Ship this validation step by default — it prevents the worst procgen bug.
|
|
126
|
-
- **Offer WFC** for tile-based levels/textures (with contradiction restart), optionally seeded by noise for macro structure. Do heavy generation in a worker/on load, not per frame.
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## Sources
|
|
131
|
-
- Red Blob Games — Making maps with noise functions: https://www.redblobgames.com/maps/terrain-from-noise/
|
|
132
|
-
- Red Blob Games — Noise (value/Perlin/simplex intro): https://www.redblobgames.com/articles/noise/introduction.html
|
|
133
|
-
- Red Blob Games — Procedural map/dungeon & grids index: https://www.redblobgames.com/
|
|
134
|
-
- simplex-noise (npm, v4 `createNoise2D` API): https://www.npmjs.com/package/simplex-noise , https://github.com/jwagner/simplex-noise.js
|
|
135
|
-
- mulberry32 / xmur3 seeded PRNGs (bryc's gist): https://github.com/bryc/code/blob/master/jshash/PRNGs.md
|
|
136
|
-
- Stefan Gustavson — Simplex noise demystified (PDF): https://weber.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
|
|
137
|
-
- Ken Perlin — Improving Noise (SIGGRAPH 2002): https://mrl.cs.nyu.edu/~perlin/paper445.pdf
|
|
138
|
-
- Cellular-automata cave generation (RogueBasin): https://www.roguebasin.com/index.php/Cellular_Automata_Method_for_Generating_Random_Cave-Like_Levels
|
|
139
|
-
- BSP dungeon generation (RogueBasin): https://www.roguebasin.com/index.php/Basic_BSP_Dungeon_generation
|
|
140
|
-
- Maze generation algorithms — Jamis Buck ("Buckblog") & "Mazes for Programmers": https://weblog.jamisbuck.org/2011/2/7/maze-generation-algorithm-recap
|
|
141
|
-
- Wave Function Collapse — Maxim Gumin (original): https://github.com/mxgmn/WaveFunctionCollapse ; Boris the Brave, "WFC explained": https://www.boristhebrave.com/2020/04/13/wave-function-collapse-explained/
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
# Save & Persistence for Browser Games (localStorage vs IndexedDB, versioned saves, serialization, autosave)
|
|
2
|
-
|
|
3
|
-
Consolidated from MDN storage docs and the `idb` library (see Sources). Focus: what an AI builder needs so saves **survive updates, don't corrupt, and don't block the frame** — the usual bugs are lost progress on a schema change, quota errors, and jank from synchronous writes.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Pick the right storage
|
|
8
|
-
|
|
9
|
-
**`localStorage`** — simple key/value, **synchronous, string-only, ~5MB** per origin.
|
|
10
|
-
- Good for: small settings, high scores, current-level, a single small save blob, key bindings, volume.
|
|
11
|
-
- **Synchronous = it blocks the main thread.** Fine for occasional small writes; **do NOT write large/frequent blobs to it every frame** (causes hitches). Values must be strings → `JSON.stringify`/`parse`.
|
|
12
|
-
- `sessionStorage` = same API but cleared when the tab closes (use for transient state only).
|
|
13
|
-
|
|
14
|
-
**IndexedDB** — **asynchronous**, transactional, large (hundreds of MB to GB, quota-based), stores **structured data** (objects, arrays, `Blob`, `ArrayBuffer`, typed arrays via the structured clone algorithm — no manual JSON needed).
|
|
15
|
-
- Good for: big/complex saves, multiple save slots, replays, generated assets/level caches, offline data.
|
|
16
|
-
- The raw API is verbose/callbacky — **use the `idb` wrapper** (Jake Archibald) for a clean promise-based API. Don't hand-roll raw IndexedDB.
|
|
17
|
-
|
|
18
|
-
**Rule of thumb:** settings/scores/small single save → `localStorage`; anything large, multi-slot, binary, or frequently written → **IndexedDB (via `idb`)**. Cookies are the wrong tool for game saves (tiny, sent on every request).
|
|
19
|
-
|
|
20
|
-
**Cache API / service worker** is for caching the *game itself* (assets, offline PWA), not for player save data.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## 2. ALWAYS version your saves (the #1 lost-progress bug)
|
|
25
|
-
|
|
26
|
-
Your save schema *will* change between game versions. If you `JSON.parse` an old save into new code with no version handling, you get crashes or silent corruption and angry players who lost progress.
|
|
27
|
-
|
|
28
|
-
**Rule: every save carries a `version` number, and you write migration steps between versions.**
|
|
29
|
-
```js
|
|
30
|
-
const SAVE_VERSION = 3;
|
|
31
|
-
function migrate(save) {
|
|
32
|
-
let s = { ...save };
|
|
33
|
-
if (s.version === 1) { s.coins = s.gold ?? 0; delete s.gold; s.version = 2; }
|
|
34
|
-
if (s.version === 2) { s.settings = { ...defaults.settings, ...s.settings }; s.version = 3; }
|
|
35
|
-
return s; // now at SAVE_VERSION
|
|
36
|
-
}
|
|
37
|
-
function loadSave(raw) {
|
|
38
|
-
let save = raw ?? structuredClone(defaultSave);
|
|
39
|
-
if (save.version !== SAVE_VERSION) save = migrate(save);
|
|
40
|
-
return save;
|
|
41
|
-
}
|
|
42
|
-
```
|
|
43
|
-
- Migrations run **in sequence** (v1→v2→v3) so any old save upgrades.
|
|
44
|
-
- IndexedDB has its own **schema versioning** via the `open(name, version)` + `onupgradeneeded`/`idb`'s `upgrade` callback — use it to create/alter object stores. That's separate from your *data* version above; you often want both.
|
|
45
|
-
- **New fields:** merge over defaults (`{ ...defaults, ...loaded }`) so older saves gain new keys with sane values instead of `undefined`.
|
|
46
|
-
- Keep a **backup of the previous save** before overwriting, so a failed migration/corruption is recoverable.
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
## 3. Serialization: what to save
|
|
51
|
-
|
|
52
|
-
- **Save state, not behavior.** Serialize plain data (positions, inventory, flags, RNG seed + counters). Never try to serialize class instances/functions/DOM/engine objects directly.
|
|
53
|
-
- **Save the seed + progress**, not the whole generated world, for procgen games — regenerate deterministically on load (see procgen skill). Massively smaller and robust.
|
|
54
|
-
- Use `structuredClone` / IndexedDB for objects with `Map`/`Set`/`Date`/typed arrays; **plain `JSON` drops those** (Map→`{}`, Date→string, `undefined`/functions omitted, `NaN`/`Infinity`→`null`). If using JSON, convert them explicitly.
|
|
55
|
-
- **Wrap every load in try/catch.** Corrupt/partial JSON must fall back to defaults, not white-screen the game.
|
|
56
|
-
- Consider a **checksum** or at least shape-validation of loaded data before trusting it. For competitive/leaderboard games remember client saves are trivially editable — validate server-side, don't trust local high scores.
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## 4. Autosave (do it safely)
|
|
61
|
-
|
|
62
|
-
- **Autosave on meaningful events** (level complete, checkpoint, item gained) and on a throttled timer — not every frame.
|
|
63
|
-
- **Debounce/throttle writes** (e.g. at most once every few seconds) to avoid thrashing storage and causing jank; coalesce rapid changes.
|
|
64
|
-
- **Save on `visibilitychange` (hidden) and `pagehide`/`beforeunload`** — mobile browsers kill backgrounded tabs without firing `beforeunload` reliably, so `visibilitychange → hidden` is the most reliable "player is leaving" signal. Do a final synchronous-ish flush there (localStorage) or a queued IndexedDB write.
|
|
65
|
-
- **Write atomically:** for IndexedDB use a transaction; for localStorage, build the full string then set it once (a half-written multi-key save = corruption). Keep the previous save until the new one is confirmed.
|
|
66
|
-
- Give visible feedback ("Saved") and expose manual save/load + multiple slots where appropriate.
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## 5. Quota, availability & privacy gotchas
|
|
71
|
-
- **Storage can throw or be disabled:** private/incognito mode, quota exceeded (`QuotaExceededError`), or blocked third-party storage. **Feature-detect and wrap in try/catch**; degrade gracefully (in-memory only) rather than crashing.
|
|
72
|
-
- **Eviction:** browsers can clear storage under pressure ("best-effort" default). For important saves, request **`navigator.storage.persist()`** to reduce eviction risk, and check `navigator.storage.estimate()` for usage/quota.
|
|
73
|
-
- Storage is **per-origin**; a domain change loses saves. iOS Safari has historically been aggressive about clearing storage from rarely-visited sites — don't treat browser storage as permanent; offer export/import (download/upload a save file) for anything precious, and cloud save for accounts.
|
|
74
|
-
- **`localStorage` blocks the main thread** — keep values small; move big/async work to IndexedDB.
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## 6. Bug-prevention checklist
|
|
79
|
-
- **No version field** → old saves crash new builds / silent data loss; version + sequential migrations.
|
|
80
|
-
- **Parsing without try/catch** → corrupt save white-screens the game; fall back to defaults.
|
|
81
|
-
- **Merging loaded save without defaults** → new fields are `undefined`; spread over defaults.
|
|
82
|
-
- **JSON round-trip of Map/Set/Date/typed arrays** → data silently lost; use structuredClone/IndexedDB or convert explicitly.
|
|
83
|
-
- **Frequent/large `localStorage.setItem`** → main-thread hitches; throttle + use IndexedDB for big data.
|
|
84
|
-
- **No save on tab hide** → mobile players lose progress; save on `visibilitychange`/`pagehide`.
|
|
85
|
-
- **Non-atomic multi-key writes** → partial/corrupt saves; write one blob / use a transaction, keep a backup.
|
|
86
|
-
- **Assuming storage always works** → crashes in private mode / over quota; feature-detect + try/catch + graceful degrade.
|
|
87
|
-
- **Trusting local high scores** → trivially cheatable; validate server-side for leaderboards.
|
|
88
|
-
- **Saving the whole procgen world** → bloated saves; save seed + progress and regenerate.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Defaults to apply
|
|
93
|
-
- **Ship a tiny save module by default:** `save(state)` / `load()` with a **`version` field + sequential `migrate()`**, **try/catch → defaults**, and **merge-over-defaults**. This single pattern prevents the most damaging bug (progress lost on update).
|
|
94
|
-
- **Storage routing rule baked in:** settings/scores/small save → `localStorage`; large/multi-slot/binary/frequent → **IndexedDB via `idb`**.
|
|
95
|
-
- **Autosave defaults:** save on checkpoints + throttled timer + **`visibilitychange`/`pagehide`**, written atomically with a retained backup.
|
|
96
|
-
- **Persist the seed, not the world** for procgen; offer **export/import** for precious saves and request `storage.persist()`. Never trust local scores for leaderboards.
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Sources
|
|
101
|
-
- MDN — Web Storage API (`localStorage`/`sessionStorage`): https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
|
|
102
|
-
- MDN — IndexedDB API + Using IndexedDB (versioning/`onupgradeneeded`): https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API , https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
|
|
103
|
-
- MDN — Structured clone algorithm (what serializes): https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm ; `structuredClone()`: https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
|
|
104
|
-
- MDN — Storage quotas & eviction / `navigator.storage`: https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria , https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/persist
|
|
105
|
-
- MDN — `Document.visibilitychange` / Page Visibility API: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
|
|
106
|
-
- `idb` (promise-based IndexedDB wrapper, Jake Archibald): https://github.com/jakearchibald/idb
|
|
107
|
-
- web.dev — "Storage for the web" (choosing storage): https://web.dev/articles/storage-for-the-web
|
|
108
|
-
- localForage (localStorage-like API over IndexedDB): https://github.com/localForage/localForage
|