Files
onboarding-project/CLAUDE.md
T
Oleksandr Vlasiuk 1d38990d7f some changes
2026-08-17 12:36:56 +03:00

9.9 KiB

MyGamePlayable — Project Instructions

Context rules

This file contains persistent project rules and non-obvious constraints, not history.

Keep this file under 200 lines.

Add only:

  • rules that affect future implementation decisions;
  • non-obvious SDK/library/toolchain pitfalls;
  • structural conventions;
  • public SDK APIs that are not obvious from code.

Do not add:

  • development history, dates, or completed tasks;
  • test/build results;
  • current tuning values;
  • information obvious from nearby code;
  • temporary project state.

If knowledge is specific to one subsystem, prefer docs/<topic>.md. If it explains why a decision was made, put the detailed history in docs/worklog/.

When a pitfall is fixed by a utility, replace the explanation with one short rule pointing to that utility.

Worklog files: docs/worklog/NN-short-english-name.md Update docs/worklog/README.md when adding one.


Stack

  • Three.js 0.185 via @hitplay/playable_template + @hitplay/ads_common.
  • Physics: cannon-es.
  • Tweens: @tweenjs/tween.js via SDK TweenC.
  • VFX: three.quarks, designer .json prefabs.
  • Audio: howler.
  • Input: nipplejs.
  • Debug UI: lil-gui.
  • Analytics: bytebrew-web-sdk.
  • Toolchain: Vite 6 + TypeScript 7.

Runtime rules

  • UpdateController.Instance.onUpdate is the only update event. delta is seconds.
  • SDK calls world.fixedStep() every frame via Physics_internal.update. Never call step() / fixedStep() manually.
  • Physics initialization: Physics_internal.init(...) in beforeResourcesLoadedCb.ts.
  • Call TweenC.init() exactly once. It is already called in beforeResourcesLoadedCb.ts.
  • Use ThreeC.removeFromScene(obj) to remove spawned meshes.

Commands

Command Purpose
npm start Vite dev server
npm run export Single-file HTML → export/output_export_browser_en.html
npm run build 24ADS platform zip → zip/
npm run update Update HitPlay SDK packages

Verify changes with npx vite build.

Use stats.html when investigating bundle-size growth.


Structure

src/index.ts                 entry point / Template.initConfig()
src/controllers/*C.ts        one system per file, flat structure
src/configUIParams/           24ADS config categories
src/templateConfig/*Cb.ts     SDK lifecycle callbacks
src/utils/                    shared utilities
src/resources/                meshes/images/fonts/VFX/sounds
src/css/                      main.css, ui.css, health-bar.css
docs/worklog/                 development history

Do not create controller subdirectories. The SDK uses a flat structure and there is no benefit in mirroring artificial subfolders.


Utilities — reuse, don't reimplement

  • perspectiveScale.ts — perspective compensation for flight toward/away from camera.
  • screenWorldProjection.ts — world↔screen projection.
  • tweenChain.ts — safe tween-chain registration.
  • flashOverlayMaterial.ts — additive hit-flash overlay.
  • pickupFlight.ts — shared drop → scatter → bounce → fly-to-target animation, driven by both ResourceC (wood) and ZombiePartC (zombie-part chunks). Never write a second one.

HealthBarC is shared by crates, zombies, and player. Do not create another health-bar implementation.

Two weapon progressions (bat level from wood, pistol level from zombie-part chunks), each with its own fixed Pay Zone (PayZoneC, instance-based — no relocation), and crate respawn: see docs/progression.md.

HealthBarOptions.variant controls colors via health-bar.css. follow re-samples the anchor every frame for moving owners. Health bars appear on first hit and hide after HIDE_DELAY_S; full-HP entities do not show a bar.


Dependency injection

Controllers must not directly import each other when that would create a cycle. Inject getters/callbacks instead and wire them from TestSceneC.

Existing DI examples:

  • ResourceC.setFlyTarget()
  • HudC.setBalanceGetter()
  • CameraFollowC.setFacingTargetGetter()
  • BreakablePropC.onHit / onDestroyed / onRespawned
  • ZombieC.init(bounds, getPlayerPosition, damagePlayer, isPlayerAlive)
  • ZombieInvasionHudC.setOnComplete()
  • ZombiePartC.setFlyTarget() / PistolHudC.setLevelGetter()

ZombieInvasionHudC.setOnComplete() must be called before init(); init() immediately starts its countdown.


Platform config

24ADS renders config categories from src/configUIParams/ plus SDK categories.

Platform tab Source
Analytics params analytics.ts
Install Banner Settings installBanner.ts
Scene settings globalSettings.ts (global)
Gameplay tuning gameplay.ts
Audio Settings sounds.ts
Screen Orientation orientation.ts
Filter scene / Redirect / Loader / Font SDK

Read runtime values with:

Template.getCategory("<categoryId>")["<parameterId>"]

Config format

configUIParams array parameters use [min, max, default] per axis. Runtime Template.getCategory() returns the actual [x, y, z] value.

Never interpret [min, max, default] as runtime coordinates.

Camera ownership

There must be exactly one writer of camera.rotation: CameraFollowC.

  • CameraC.setCamera() owns FOV only.
  • CameraFollowC.applyRigFromConfig() owns camera position/rotation.
  • camera_position_* is a player-relative offset, not an absolute position.
  • camera_rotation_* is a fixed angle in degrees.
  • camera_fov_* is FOV.
  • Do not write camera position/rotation from CameraC.setCamera(). setCamera() can run again when any camera_* config changes.
  • TestSceneC.CAMERA_OFFSET is fallback only.

Gameplay tuning

gameplay.ts is the source for platform tuning defaults. DebugConfigC mirrors these values locally in lil-gui (C key, dev-only). The debug panel copies defaults once at startup; it does not live-sync.


Critical SDK / runtime pitfalls

  • GLTF loader removes dots from node names: UI_Wood.001 → UI_Wood001. Inspect runtime names with scene.traverse(), not GLB JSON.

  • GLB materials are shared between nodes. Clone a material before modifying one instance.

  • MeshBasicMaterial.color cannot produce a visible white→white hit flash. Use src/utils/flashOverlayMaterial.ts.

  • Zone checks must use new Box3().setFromObject(node), not visual/radius estimates.

  • Perspective changes apparent object size. Use perspectiveScale.ts when compensating for flight toward/away from the camera.

  • Flight targets must be re-sampled every frame because the camera follows the player.

  • Do not rely solely on tween onComplete for critical cleanup. Use a timeout fallback with a small safety margin. removeFromScene() is safe to call twice.

  • screen↔world projection must use renderer.domElement.getBoundingClientRect(), never window.innerWidth/Height. SDK uses a letterboxed 9:16 viewport.

  • nipplejs.force can exceed the visual joystick radius. Clamp it when storing input, not inside every calculation.

  • Math.atan2 and CSS conic-gradient use different zero directions. Account for the 90° offset.

  • Do not stack independent CSS animations by changing animation-name on the same element. Use nested elements.

  • To fade to a base color, animate RGB rather than alpha.

  • Use --lvh / --lvw; avoid raw vh / vw / vmin.


Export / CSS / fonts

  • Export HTML is gzip+base64, not searchable source. Never grep export/*.html to verify bundled content. Inspect the decompressed payload instead.

  • .bannerButton is purged from the release export. Do not add CSS targeting it. .bannerLogo and .bannerButtonText remain.

  • Template.setUpFont() works only when !release. It is for platform preview. Release uses our own @font-face from main.css.

  • Release images must use: ConvertToBase64WhenRelease("./file.webp") from the same directory as the asset.

  • CSS fonts are inlined automatically by the configured assetsInlineLimit.


Asset rules

ZombiePunk_Map.glb:

  • Lootable contains approximately 18 Wooden_Box_XXX crates.
  • Crates may start at S2/S3. BreakablePropC must derive initial HP from actual _SN nodes; never assume three stages.
  • UI_Wood is the resource icon and must be cloned per spawn.
  • UI_Interactive_Zone_02 is the pistol's Pay Zone marker (halved in scale); the bat's own Pay Zone is a runtime clone of it, not a second authored node — see TestSceneC.createPayZoneNodes.
  • UI_Tool_Zone, UI_Wood001, and UI are hidden decorative nodes.

ZombiePunk_Character.glb:

  • Existing clips include Idle, SlowWalk, Loot, WeaponTakeOut, PistolShoot, WeaponPutDown, Death, PistolBack/Forward/Left/Right.
  • Do not invent character states before checking existing clips.
  • Character_Pistol is a skinned mesh (skin:0), not a bone prop. Skeleton animation moves it with the hand.
  • Bat uses Tool_1 / Tool_2 visibility switching.
  • Bullet is a separate hidden rig.

Change discipline

  • Prefer minimal, focused changes.
  • Do not refactor unrelated code.
  • Reuse existing utilities and controllers before creating new ones.
  • Preserve the existing DI pattern.
  • Before adding a new SDK workaround, check src/utils/ and this file.
  • When a new persistent rule is discovered, update this file only if it affects future decisions; otherwise document it in the appropriate source file or docs/worklog/.