@jgengine/core 0.11.0 → 0.13.0
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/CHANGELOG.md +52 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat/effects.d.ts +11 -4
- package/dist/combat/effects.js +24 -17
- package/dist/combat/magazine.d.ts +13 -0
- package/dist/combat/magazine.js +27 -0
- package/dist/combat/vfxPresets.d.ts +176 -0
- package/dist/combat/vfxPresets.js +74 -0
- package/dist/combat/weaponFire.d.ts +114 -0
- package/dist/combat/weaponFire.js +96 -0
- package/dist/combat.d.ts +5 -2
- package/dist/combat.js +4 -1
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/devtools/urlFlags.d.ts +22 -0
- package/dist/devtools/urlFlags.js +62 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/clipRoles.d.ts +51 -0
- package/dist/game/clipRoles.js +142 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/partAnimation.d.ts +78 -0
- package/dist/game/partAnimation.js +147 -0
- package/dist/game/playableGame.d.ts +72 -440
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +22 -1
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/inventory/inventoryModel.d.ts +24 -8
- package/dist/inventory/inventoryModel.js +47 -8
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +48 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +131 -0
- package/dist/runtime/context/combatFx.js +15 -5
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +60 -0
- package/dist/runtime/context/scene.js +329 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +133 -500
- package/dist/runtime/gameContextTypes.d.ts +500 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +17 -2
- package/dist/runtime/headlessRunner.js +15 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +7 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +79 -0
- package/dist/scene/colliders.js +170 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +19 -15
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statModifiers.d.ts +16 -0
- package/dist/stats/statModifiers.js +30 -1
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +62 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/ui/panelModel.d.ts +120 -0
- package/dist/ui/panelModel.js +168 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/buildingLots.d.ts +84 -0
- package/dist/world/buildingLots.js +119 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGenerator.d.ts +41 -0
- package/dist/world/cityGenerator.js +58 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/environmentSummary.js +57 -1
- package/dist/world/features.d.ts +53 -5
- package/dist/world/features.js +15 -3
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/roads.d.ts +19 -2
- package/dist/world/roads.js +60 -3
- package/dist/world/scatter.js +2 -20
- package/dist/world/streetGenerator.d.ts +159 -0
- package/dist/world/streetGenerator.js +684 -0
- package/dist/world/streets.d.ts +103 -1
- package/dist/world/streets.js +273 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +65 -1
- package/dist/world/terrain.js +119 -2
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +19 -1
- package/skills/jgengine-combat/api.md +75 -2
- package/skills/jgengine-combat/capabilities.md +61 -1
- package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
- package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
- package/skills/jgengine-combat/reference.md +33 -4
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +182 -26
- package/skills/jgengine-gameplay/capabilities.md +96 -2
- package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
- package/skills/jgengine-gameplay/reference-systems.md +10 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +453 -21
- package/skills/jgengine-world/capabilities.md +112 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- package/skills/jgengine/reference-cartridge.md +0 -72
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Recipe — make an attack you can actually see
|
|
2
|
+
|
|
3
|
+
**Seam:** authoritative hit resolution (`applyEffect` / `fireProjectile`) ↔ the one-shot
|
|
4
|
+
`combat.vfx` visual the shell renders. They are deliberately separate: **resolving damage
|
|
5
|
+
never draws anything on its own.** If you call `applyEffect`/`fireProjectile` and "nothing
|
|
6
|
+
happens on screen," you have the hit but not the picture — emit a `vfx` burst next to it.
|
|
7
|
+
|
|
8
|
+
The easy path is a named **preset**, so you never hand-pick a `VfxKind` or a hex color.
|
|
9
|
+
|
|
10
|
+
## The one line
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import type { GameContext } from "@jgengine/core/runtime/gameContext";
|
|
14
|
+
|
|
15
|
+
// caster and enemy are entity instance ids (the shell follows their live pose)
|
|
16
|
+
function shootArrow(ctx: GameContext, caster: string, enemy: string): void {
|
|
17
|
+
ctx.scene.entity.vfx({ preset: "arrow", from: caster, to: enemy });
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That renders a visible bolt traveling from the caster to the enemy. Swap the flavor for the
|
|
22
|
+
attack you want — the vocabulary already covers the usual suspects:
|
|
23
|
+
|
|
24
|
+
| Want | `preset` | Archetype |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| arrow / bolt / bullet | `"arrow"`, `"bolt"`, `"bullet"` | traveling `projectile` |
|
|
27
|
+
| fire / ice / poison / arcane spell | `"fireball"`, `"firebolt"`, `"frostbolt"`, `"poison"`, `"arcane"`, `"shadowbolt"`, `"holybolt"` | traveling `projectile` |
|
|
28
|
+
| lightning / laser / web / chain | `"lightning"`, `"laser"`, `"web"`, `"chain"` | connecting `beam` |
|
|
29
|
+
| sword swing / melee hit | `"slash"`, `"sword"`, `"melee"`, `"hit"` | impact `spark` |
|
|
30
|
+
| explosion / shockwave | `"explosion"`, `"blast"`, `"shockwave"`, `"frostNova"` | ground `nova` |
|
|
31
|
+
| heal / buff / shield aura | `"heal"`, `"buff"`, `"shield"`, `"cast"` | lingering `glow` |
|
|
32
|
+
|
|
33
|
+
Full list lives in `vfxPresets` (`@jgengine/core/combat`).
|
|
34
|
+
|
|
35
|
+
## Wiring it to a real projectile attack
|
|
36
|
+
|
|
37
|
+
`fireProjectile` returns a shot id; `settleProjectile` resolves the hit. Fire the visual when
|
|
38
|
+
you fire the shot so the bolt is in flight while it travels, then settle to apply damage:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
function fireAt(ctx: GameContext, caster: string, enemy: string): void {
|
|
42
|
+
const shot = ctx.scene.entity.fireProjectile({
|
|
43
|
+
from: caster,
|
|
44
|
+
via: { item: "shortbow" },
|
|
45
|
+
aim: { yaw: 0, pitch: 0 },
|
|
46
|
+
effect: "arrow-hit",
|
|
47
|
+
});
|
|
48
|
+
ctx.scene.entity.vfx({ preset: "arrow", from: caster, to: enemy }); // <- the picture
|
|
49
|
+
ctx.scene.entity.settleProjectile(shot); // <- the damage
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For a melee swing there is no projectile — just resolve the effect and flash the spark:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
ctx.scene.entity.effect({ from: caster, to: enemy, effect: "cleave" });
|
|
57
|
+
ctx.scene.entity.vfx({ preset: "slash", from: caster, to: enemy });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Overrides and custom bursts
|
|
61
|
+
|
|
62
|
+
Any field on the call wins over the preset, so you keep the archetype but retint it:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
ctx.scene.entity.vfx({ preset: "fireball", color: 0x00ff88, from: caster, to: enemy });
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Skip `preset` entirely and pass `kind` + `color` for a fully bespoke burst. A **misspelled**
|
|
69
|
+
flavor still shows a `spark` rather than nothing, so a typo never silently drops the effect.
|
|
70
|
+
|
|
71
|
+
When you need the raw numbers — e.g. to seed a long-lived `vfxInstance` beam from the same
|
|
72
|
+
vocabulary — call `resolveVfxPreset("lightning")` and read `{ kind, color, durationMs?, radius? }`.
|
|
73
|
+
|
|
74
|
+
## Don't
|
|
75
|
+
|
|
76
|
+
- Don't expect `applyEffect`/`fireProjectile`/`settleProjectile` to draw a visual — they are
|
|
77
|
+
headless authority. The `vfx` call is what the player sees.
|
|
78
|
+
- Don't put damage numbers in a preset. Presets are colors and shapes; combat values live in
|
|
79
|
+
your effect/ability data.
|
|
80
|
+
- Don't disable presentation and wonder where the effect went: `presentationEffects` is on by
|
|
81
|
+
default, but if a game passes `presentationEffects: { vfx: false }` the burst won't render.
|
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
Reference module for the [`jgengine-combat` API](SKILL.md) skill. Load this when you need the combat surface — effects, projectiles, death, feel, and abilities.
|
|
4
4
|
|
|
5
|
+
## Portable named resources
|
|
6
|
+
|
|
7
|
+
`StatPool` (`@jgengine/core/stats/statPool`) is the plain
|
|
8
|
+
`{ current, max, min }` model for any caller-named bounded resource. Existing
|
|
9
|
+
projects implement the two-method `StatPoolAccess` over their own ECS, reducer,
|
|
10
|
+
or save model, then use `applyStatPoolDelta`; reducer-style integrations use
|
|
11
|
+
the pure `createStatPool` / `patchStatPool` / `changeStatPool` transitions.
|
|
12
|
+
Native scene entity stats are a compatibility bridge over the same model. See
|
|
13
|
+
the [portable stat-pool recipe](recipes/portable-stat-pools.md) for complete
|
|
14
|
+
imports, adapter code, ownership, and JSON save/restore.
|
|
15
|
+
|
|
16
|
+
## Portable runtime snapshots
|
|
17
|
+
|
|
18
|
+
`createMagazine` (`combat/magazine`) and `createStats`
|
|
19
|
+
(`stats/statModifiers`) retain their convenience APIs while exposing detached
|
|
20
|
+
plain state through `snapshot()` / `restore(...)`. Magazine snapshots include
|
|
21
|
+
loaded rounds, in-progress reload time, and finite/infinite reserve state;
|
|
22
|
+
stat snapshots include base values plus ordered modifier sources and absolute
|
|
23
|
+
expiry timestamps. Re-supply immutable config and the caller's injected clock
|
|
24
|
+
when rebuilding. The [portable runtime-state recipe](recipes/portable-runtime-state.md)
|
|
25
|
+
shows a complete existing-save round trip, including a caller-owned reserve.
|
|
26
|
+
`createEffectSystem` (`combat/effects`) commits direct or area effects through
|
|
27
|
+
the same `StatPoolAccess` contract. Receive rules still define deterministic
|
|
28
|
+
shield/resource spill order, modifiers, and the final lethal pool; results
|
|
29
|
+
carry exact applied deltas and lethal context. The legacy `resolveStats` input
|
|
30
|
+
remains as a compatibility bridge, while native `GameContext` now supplies its
|
|
31
|
+
`EntityStatsApi` adapter. Compose this committer with pure `resolveDamageHit`
|
|
32
|
+
(matchup/received/status provenance) and `resolveDamage` (ordered bounded
|
|
33
|
+
interceptors). See the [portable damage/effects recipe](recipes/portable-damage-effects.md).
|
|
34
|
+
|
|
5
35
|
## Effects and projectiles
|
|
6
36
|
|
|
7
37
|
Effect ids are **game-defined strings**. Magnitudes **drain** stats: positive subtracts down `receive.<effect>.order` (spilling to the next stat in the order), negative restores. Heals pass a negative amount (`via: { amount: -flashHeal }`, typically read from a `weapon.heal` stat).
|
|
@@ -25,7 +55,7 @@ fireProjectile({ from, via, aim, effect }) // → shotId (pending)
|
|
|
25
55
|
settleProjectile(shotId) // authoritative → { at, hits } | rejection
|
|
26
56
|
```
|
|
27
57
|
|
|
28
|
-
`Aim = { origin, direction } | { yaw, pitch, spread? }`. A free `{ yaw, pitch }` aim defaults to `converge`: the bullet leaves the gun `muzzle` (so a tracer visibly comes from the barrel, not the camera) yet bends toward whatever the eye ray's crosshair covers, so the reticle stays truth. An explicit `{ origin, direction }` aim (enemy AI, turrets) passes through unchanged. Shot origin otherwise resolves via `ShotOriginPolicy` (`eye`: shooter position raised to eye height — 90% of the shooter's own hitbox top when colliders are known, ~1.6m otherwise — so shots trace the sightline and the crosshair is truth; `muzzle`/`camera`/`world` for custom rigs, `legacy` = raw entity position).
|
|
58
|
+
`Aim = { origin, direction } | { yaw, pitch, spread? }`. A free `{ yaw, pitch }` aim defaults to `converge`: the bullet leaves the gun `muzzle` (so a tracer visibly comes from the barrel, not the camera) yet bends toward whatever the eye ray's crosshair covers, so the reticle stays truth. An explicit `{ origin, direction }` aim (enemy AI, turrets) passes through unchanged. Shot origin otherwise resolves via `ShotOriginPolicy` (`eye`: shooter position raised to eye height — 90% of the shooter's own hitbox top when colliders are known, ~1.6m otherwise — so shots trace the sightline and the crosshair is truth; `muzzle`/`camera`/`world` for custom rigs, `legacy` = raw entity position). Hitboxes auto-fit the rendered model: an entity kind mapped in `entityModels` (or an object catalog id in `objectModels`) gets a grounded AABB matching its model's measured bounds — footprint x height composed with `scale`/`targetHeight` exactly like the renderer — so the damage box equals the visual without hand-tuning. Custom-rendered content auto-fits too: the shell measures what `renderEntity`/`renderObject` actually mount (and any model with no index `dims`) and reports the entity-local bounds through `ctx.scene.entity.reportBounds` / `ctx.scene.object.reportBounds`, so a procedural mesh's hitbox wraps the render instead of the humanoid default — flag debug gizmos with `userData.jgMeasureExclude` to keep them out of the measurement, and note the measurement happens on the rendering client, so a never-rendering authoritative host should author `colliders` or model `dims` instead. Entities with nothing measurable at all keep the body-covering humanoid AABB default (feet to ~1.8m); unmodeled objects with a `visual.scale` get a matching grounded physical body. Catalog assets opted into `COLLISION_MESH_ASSET_IDS` (`@jgengine/assets`) go one step further: the fit raycasts the asset's actual triangles (BVH over a quantized mesh shipped in the index), so a shot through a torus hole or archway passes clean instead of hitting the fitted box — same shape on client and headless host. Explicit catalog `colliders`/`halfExtents` and per-instance `setColliders` always override the fit. Hitscan settles into per-hit effects; ballistic shots (`weapon.projectile` with `fuseTime`/`settleOn`) settle to a landing point — the handler then calls `effect({ at: settle.at, radius })`. Settling twice rejects. Prediction is never authority.
|
|
29
59
|
|
|
30
60
|
Raycasts are **object-aware**: the default raycast checks placed scene objects as well as entities, discriminated by `RaycastHit.kind` (`"entity" | "object"` — an `ObjectRaycastHit` also carries `catalogId`). A crate or wall between shooter and target blocks or absorbs the shot instead of every projectile passing through scenery; supply `ProjectileSystemDeps.objects` (`{ list(), halfExtents?(catalogId) }`, matching `ObjectStore.list()` structurally) to opt in, with a `[0.5, 0.5, 0.5]` half-extent default per object.
|
|
31
61
|
|
|
@@ -74,7 +104,8 @@ Layered on top of effects/projectiles/death — none of it replaces them, it add
|
|
|
74
104
|
- **Render cues** (`combat/renderCues`, #542) - the pure math behind a custom `renderEntity`/viewmodel rig's motion feel: `EntityRenderCues { speed, bobPhase, firing, reloading, recoil, hit, dead }`, `advanceMotionCues(cues, speed, dt, tuning?)` advances `bobPhase`/decays `recoil` from a live `groundSpeed(entity)` sample, `applyRenderAnimationEvent(cues, event)` folds in `"fire"` / `"reload"` / `"reloadEnd"` from `ctx.game.playEntityAnimation`, `applyRenderHitEvent`/`applyRenderDeathEvent` fold in `combat.hitReaction`/`entity.died`. `RenderCueTuning` overrides `cyclesPerUnit`/`recoilDecayPerSecond`/`firePulseSeconds`/`hitPulseSeconds`. A game never calls these directly — `@jgengine/shell/render`'s `useEntityRenderCues(instanceId)` (see `jgengine-ui`) wires all four into one ref-returning hook for `useFrame` consumers.
|
|
75
105
|
- **Telegraph** (`combat/telegraph`) — a `TelegraphShape` (`circle | ring | cone | line`) + `windupMs`. `ctx.scene.entity.telegraph({ from, shape, at, dir?, windupMs, kind?, effect? })` emits `combat.telegraph` for the shell to draw a ground decal that fills over the windup, and — if `effect` is bound — applies that effect to everyone inside the shape (`pointInTelegraph`) at activation. Returns a cancel handle. **Turn-scoped variant**, for tactics/turn-based enemy intents instead of a real-time windup: set `TelegraphConfig.turns` (a count of turns instead of `windupMs`), then read `telegraphTurnProgress(config, startedTurn, currentTurn)` (0..1 fill fraction), `telegraphFiredAtTurn(config, startedTurn, currentTurn)` (has it resolved), and `telegraphTurnsRemaining(config, startedTurn, currentTurn)` — the same "enemy will hit this tile" decal, advanced by `turnLoop.round()`/`active()` instead of a clock.
|
|
76
106
|
- **Damage-number typing** — `ctx.scene.entity.floatText({ …, crit?, element?, hitType?, scale? })` (and the `entity.floatText` event) carry hit metadata; the shell's `resolveFloatTextStyle` (`@jgengine/shell/world/floatTextStyle`) maps crit/element to color + scale + glow.
|
|
77
|
-
- **VFX — one
|
|
107
|
+
- **VFX — make an attack visible in one line** — the easy, discoverable path is a named `preset`: `ctx.scene.entity.vfx({ preset: "arrow", from: caster, to: enemy })` renders a visible bolt with zero color/archetype tuning, and `"lightning"`, `"web"`, `"slash"`/`"sword"`, `"shield"`, `"heal"`, `"fireball"`, `"frostbolt"`, `"explosion"` and the rest of `vfxPresets` (`@jgengine/core/combat`) likewise just work — this is the answer to "I fired a projectile at the enemy but couldn't see anything." A preset is pure presentation vocabulary (a `kind` + color + lifetime, like named CSS colors); it never touches damage or hit resolution, which stay in your effect/projectile data. **The authoritative `fireProjectile`/`settleProjectile` and `applyEffect` do not emit any visual on their own** — emit the `vfx` burst yourself when you fire/settle (`fireProjectile(...)` → `vfx({ preset, from, to })`). Any field you also pass overrides the preset (`vfx({ preset: "fireball", color: 0x00ff00 })`); supply `kind` + `color` directly for a fully custom burst with no preset; an unknown flavor name degrades to a visible `spark` rather than nothing. `resolveVfxPreset(name, overrides?)` returns the raw `{ kind, color, durationMs?, radius? }` when you need the numbers (e.g. to seed a retained instance from the same vocabulary). Full walkthrough: [make an attack you can actually see](recipes/visible-attacks.md).
|
|
108
|
+
- **VFX — one-shot vs retained** — `ctx.scene.entity.vfx({ preset?, kind?, color?, from?, to?, radius?, durationMs? })` fires a **one-shot** burst (`combat.vfx`, `kind: projectile | beam | nova | glow | spark`) the shell renders once and expires. For a **long-lived** effect whose endpoints/params move over time (held beam, tether, zone, target line, looping emitter) use the retained registry `ctx.scene.entity.vfxInstance` (`createVfxInstanceStore`, `@jgengine/core/game/vfxInstance`): `upsert({ id?, kind, color, from?, to?, radius?, params?, ttlMs? })` creates or replaces by stable id, `update(id, patch)` nudges dynamic params (a missing id is a no-op, not a throw), `stop(id, { fadeMs? })` disposes with an optional fade, and `tick(now)` enforces the TTL heartbeat; `list()`/`count()` are for debug. Endpoints are `VfxRef`s — an entity **instance id** (the shell resolves its live pose each frame, so the effect follows a moving entity with no per-frame commands) or a fixed `[x, y, z]` world point. State is headless/serializable and the store is renderer-independent; each op emits `combat.vfxInstance` which the shell's `RetainedVfx` overlay binds to render resources (beam is the first shipped retained renderer). Call `vfxInstance.clear()` on scene/level reset. Duet Keys' prism beam is the first adopter (driven from its `onTick`).
|
|
78
109
|
|
|
79
110
|
## Abilities, resources, auto-target, resistance, run drafts
|
|
80
111
|
|
|
@@ -94,5 +125,3 @@ Genre systems layered over the same effects/projectiles/targeting/loot primitive
|
|
|
94
125
|
- **Completion predicates** — `completion: { kind, params }` names a registered evaluator; `BUILTIN_COMPLETION_PREDICATES` ships `immediate`, `manual`/`flag`, `timer` (phase seconds), `cleared` (tagged count drained), `metric` (live threshold), `event` (cumulative count), and `quorum` (N of nested predicates). Signals (`counts`/`metrics`/`events`/`flags`) come in through `ctx.signals`; evaluation is deterministic. Register your own via `config.predicates`.
|
|
95
126
|
- **Spawn providers** — spawning is one optional node adapter: `spawn: { kind, params }` invoked on enter. `BUILTIN_SPAWN_PROVIDERS.points` reads a named authored scene point list from `ctx.spawnPoints` (coordinates stay in the scene, referenced by key — never embedded in the sequence); `list` echoes inline requests. Register catalog/director-backed providers via `config.spawnProviders`.
|
|
96
127
|
- **Injection & scripted control** — `injectPhase(state, phase, { after | before | childOf })` inserts a phase or branch at runtime (e.g. a boss after the final wave) into the serializable tree without disturbing active/completed nodes; `forceCompletePhase` completes the active phase regardless of its predicate for scripted skips and failure/retry, restoring the authored predicate afterward.
|
|
97
|
-
|
|
98
|
-
|
|
@@ -11,6 +11,10 @@ This skill owns non-combat rules and state: systems, catalogs, inventory/items,
|
|
|
11
11
|
|
|
12
12
|
Use [capabilities.md](capabilities.md) for intent discovery, [api.md](api.md) for signatures, and [reference-systems.md](reference-systems.md) for scheduling details before hand-rolling a subsystem.
|
|
13
13
|
|
|
14
|
+
Existing projects can keep player state and saves behind `LevelingStatAccess`.
|
|
15
|
+
Follow the [portable XP/leveling recipe](recipes/portable-xp-leveling.md) for
|
|
16
|
+
custom stat ids, immutable store writes, multiple level events, and JSON resume.
|
|
17
|
+
|
|
14
18
|
## Canonical workflow
|
|
15
19
|
|
|
16
20
|
1. Define plain serializable state and stable ids.
|
|
@@ -30,6 +34,10 @@ Prefer `defineSystem` and `defineGame({ systems })` for scheduled capabilities.
|
|
|
30
34
|
- Random selection accepts injected RNG and stable ordering.
|
|
31
35
|
- Large collections use indexes, queues, or bounded reconciliation rather than repeated scans.
|
|
32
36
|
|
|
37
|
+
## Part-composed character motion
|
|
38
|
+
|
|
39
|
+
A character kit-bashed from primitives/`ModelPart`s (no skeleton, no clips) animates procedurally: tag parts with `role` (`leg.l`, `arm.r`, `head`, `tail`, `wing.l`…) and the shell's part-motion rig drives counter-phase limb swing from the entity's live movement speed, idle breathe/sway, hit flinch on `combat.hitReaction`, and a death topple on `entity.died` — no game-side frame loops. Tune with `ModelConfig.partMotion`; curves are pure and deterministic (`@jgengine/core/game/partAnimation`), so crowds de-sync by instance-id phase and replays stay stable. Soft characters (blobs, slimes) opt into volume-conserving squash-and-stretch with `partMotion: { squashAmp }` — footfall squash, jelly idle breathe, flinch pulse — and `deathStyle: "splat"` flattens out on death instead of the default sideways topple. Rigged GLBs keep using `ModelConfig.animation` — roles are only for rig-less compositions.
|
|
40
|
+
|
|
33
41
|
## Traps
|
|
34
42
|
|
|
35
43
|
- A bare export mention is not an example; use capabilities for discovery and real imports/tests for adoption.
|