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

286 lines
9.9 KiB
Markdown

# 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
```text
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:
```ts
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/`.