@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,500 @@
|
|
|
1
|
+
import type { CardPile, CardPileConfig } from "../cards/cardPile.js";
|
|
2
|
+
import type { EffectInput, EffectResult, EffectVia, ReceiveMap, SingleTargetEffectInput } from "../combat/effects.js";
|
|
3
|
+
import type { ProjectileSystem } from "../combat/projectiles.js";
|
|
4
|
+
import type { HitReaction, HitReactionConfig, ImpactPresetName } from "../combat/hitReaction.js";
|
|
5
|
+
import type { VfxPresetName } from "../combat/vfxPresets.js";
|
|
6
|
+
import type { TelegraphShape } from "../combat/telegraph.js";
|
|
7
|
+
import type { CommandDefinition, CommandResult } from "../commands/commandRegistry.js";
|
|
8
|
+
import type { ChargeOptions as WalletChargeOptions } from "../economy/wallet.js";
|
|
9
|
+
import type { Cosmetics } from "../game/cosmetics.js";
|
|
10
|
+
import type { GameDefinition } from "../game/defineGame.js";
|
|
11
|
+
import type { TerrainField } from "../world/terrain.js";
|
|
12
|
+
import type { GameEventMap, GameEvents, VfxKind } from "../game/events.js";
|
|
13
|
+
import type { VfxInstanceStore } from "../game/vfxInstance.js";
|
|
14
|
+
import type { GameFeed } from "../game/feed.js";
|
|
15
|
+
import type { Leaderboard } from "../game/leaderboard.js";
|
|
16
|
+
import type { Loadouts } from "../game/loadout.js";
|
|
17
|
+
import type { Drop, LootTableDef } from "../game/lootTable.js";
|
|
18
|
+
import type { GameDialogue } from "../game/dialogue.js";
|
|
19
|
+
import type { QuestJournal } from "../game/quest.js";
|
|
20
|
+
import type { WorldItemRecord, WorldItemSpawnInput } from "../game/worldItem.js";
|
|
21
|
+
import type { Chat } from "../game/chat.js";
|
|
22
|
+
import type { Social } from "../game/social.js";
|
|
23
|
+
import type { TradeField, TradeSystem } from "../game/trade.js";
|
|
24
|
+
import type { Unlocks } from "../game/unlocks.js";
|
|
25
|
+
import type { InventoryLayout, InventorySet } from "../inventory/inventoryModel.js";
|
|
26
|
+
import type { ContextVerb } from "../interaction/contextMenu.js";
|
|
27
|
+
import type { ProximityPrompt } from "../interaction/proximityPrompt.js";
|
|
28
|
+
import type { ItemUseHandler, ItemUseInput, ItemUseRejection, ItemUseResult } from "../item/use.js";
|
|
29
|
+
import type { WeaponStats } from "../item/weapon.js";
|
|
30
|
+
import type { PoseAllowedStates, PoseState } from "../movement/poseState.js";
|
|
31
|
+
import type { ModelAssetRef } from "../scene/assetCatalog.js";
|
|
32
|
+
import type { BodyBind } from "../scene/bodyBind.js";
|
|
33
|
+
import type { PaintLayer } from "../scene/paintLayer.js";
|
|
34
|
+
import type { EntityStatsApi, StatCatalog } from "../scene/entityStats.js";
|
|
35
|
+
import type { EntityBlackboard, EntityPose, EntityPosition, EntityStore, SceneEntity, SpawnOptions, SpawnPose } from "../scene/entityStore.js";
|
|
36
|
+
import type { Forms } from "../scene/form.js";
|
|
37
|
+
import type { EntityColliderSet, MeasuredBounds, ModelBodySource } from "../scene/colliders.js";
|
|
38
|
+
import type { ObjectRaycastHit, ObjectRaycastInput } from "../scene/objectQuery.js";
|
|
39
|
+
import type { ObjectStore } from "../scene/objectStore.js";
|
|
40
|
+
import type { Roster } from "../scene/roster.js";
|
|
41
|
+
import type { SelectionSet } from "../scene/selection.js";
|
|
42
|
+
import type { ConnectedPlayers } from "../game/connectedPlayers.js";
|
|
43
|
+
import type { Possession } from "../scene/possession.js";
|
|
44
|
+
import type { SceneRaycastHit, SceneRaycastInput } from "../scene/sceneRaycast.js";
|
|
45
|
+
import type { MoveTowardOptions, SpatialApi } from "../scene/spatial.js";
|
|
46
|
+
import type { CycleTargetOptions } from "../scene/targeting.js";
|
|
47
|
+
import type { Stats } from "../stats/statModifiers.js";
|
|
48
|
+
import type { ObservableKeyedStore } from "../store/observableKeyedStore.js";
|
|
49
|
+
import type { SnapshotModule, SnapshotViewer, WorldSnapshot } from "./worldSnapshot.js";
|
|
50
|
+
import type { ReplicationPolicy } from "./worldProjection.js";
|
|
51
|
+
import type { RuntimeSave, RuntimeSaveOptions } from "./runtimeSave.js";
|
|
52
|
+
import type { SimClock } from "../time/simClock.js";
|
|
53
|
+
import type { TurnLoop, TurnLoopConfig } from "../turn/turnLoop.js";
|
|
54
|
+
import type { RaceState, RaceStateConfig } from "../game/race.js";
|
|
55
|
+
import type { CameraDirector } from "./cameraDirector.js";
|
|
56
|
+
import type { InputSnapshot } from "./inputSnapshot.js";
|
|
57
|
+
import type { MotionIntents } from "./motionIntents.js";
|
|
58
|
+
import type { OnDeathSpec } from "../combat/death.js";
|
|
59
|
+
export interface GameContextItemEntry {
|
|
60
|
+
use?: string;
|
|
61
|
+
weapon?: Record<string, unknown>;
|
|
62
|
+
trade?: TradeField;
|
|
63
|
+
/** Rarity id read by the `worldItem` loot-filter/render binding when this item drops to the ground (#32/#33). */
|
|
64
|
+
rarity?: string;
|
|
65
|
+
/** Base item type read by the loot-filter rule evaluator (#33); defaults to the item id when absent. */
|
|
66
|
+
baseType?: string;
|
|
67
|
+
}
|
|
68
|
+
export type CatalogEntityRole = "player" | "enemy" | "hostile" | "npc" | "vehicle";
|
|
69
|
+
export interface GameContextEntityEntry {
|
|
70
|
+
stats?: StatCatalog;
|
|
71
|
+
receive?: ReceiveMap;
|
|
72
|
+
onDeath?: OnDeathSpec;
|
|
73
|
+
movement?: PoseAllowedStates & {
|
|
74
|
+
walkSpeed?: number;
|
|
75
|
+
};
|
|
76
|
+
role?: CatalogEntityRole;
|
|
77
|
+
/** Right-click context-menu verbs for this entity (#31). */
|
|
78
|
+
verbs?: readonly ContextVerb[];
|
|
79
|
+
/** Purpose-specific colliders (physical body + named damage hitboxes). */
|
|
80
|
+
colliders?: EntityColliderSet;
|
|
81
|
+
/** Uniform visual scale of the rendered mesh; derives a matching hitbox (and eye-height) when no explicit `colliders` are set. */
|
|
82
|
+
scale?: number;
|
|
83
|
+
}
|
|
84
|
+
export interface GameContextObjectEntry {
|
|
85
|
+
proximityPrompt?: ProximityPrompt;
|
|
86
|
+
breakable?: false | {
|
|
87
|
+
baseBreakTime: number;
|
|
88
|
+
};
|
|
89
|
+
slotInventory?: InventoryLayout;
|
|
90
|
+
/** Right-click context-menu verbs for this object (#31). */
|
|
91
|
+
verbs?: readonly ContextVerb[];
|
|
92
|
+
colliders?: EntityColliderSet;
|
|
93
|
+
halfExtents?: readonly [number, number, number];
|
|
94
|
+
}
|
|
95
|
+
export interface GameContextContent {
|
|
96
|
+
itemById?(itemId: string): GameContextItemEntry | null | undefined;
|
|
97
|
+
entityById?(catalogId: string): GameContextEntityEntry | null | undefined;
|
|
98
|
+
objectById?(catalogId: string): GameContextObjectEntry | null | undefined;
|
|
99
|
+
}
|
|
100
|
+
export interface GameContextOptions<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> {
|
|
101
|
+
definition: GameDefinition<TAssetRef, TMultiplayer>;
|
|
102
|
+
content: GameContextContent;
|
|
103
|
+
player: {
|
|
104
|
+
userId: string;
|
|
105
|
+
isNew: boolean;
|
|
106
|
+
};
|
|
107
|
+
now?: () => number;
|
|
108
|
+
occluder?: (from: EntityPosition, to: EntityPosition) => boolean;
|
|
109
|
+
/** Bind `ctx.game.save` to a pluggable backend (offline/single-player whole-world save). The shell resolves this from `defineGame({ save })`; multiplayer leaves it off (the host persists). */
|
|
110
|
+
save?: RuntimeSaveOptions;
|
|
111
|
+
/**
|
|
112
|
+
* Host-side per-viewer replication policy — private-state and area-of-interest projection over the
|
|
113
|
+
* wire. Bound on the authoritative host only. Unset (the default) replicates the whole world to every
|
|
114
|
+
* client, exactly as before; the simulation is identical either way, so a game plays the same.
|
|
115
|
+
*/
|
|
116
|
+
replication?: ReplicationPolicy;
|
|
117
|
+
/**
|
|
118
|
+
* Render-model lookup feeding collider auto-fit: what each entity kind / object catalog id renders as
|
|
119
|
+
* (resolved `entityModels`/`objectModels` — the shell wires this automatically). When set, entities and
|
|
120
|
+
* objects without authored `colliders` get hitboxes fitted to their model's measured bounds instead of
|
|
121
|
+
* the humanoid/unit-cube defaults. A multiplayer host should receive the same lookup as its clients so
|
|
122
|
+
* both resolve identical colliders.
|
|
123
|
+
*/
|
|
124
|
+
models?: GameContextModels;
|
|
125
|
+
}
|
|
126
|
+
/** Per-kind render-model lookup for {@link GameContextOptions.models}; a resolved `ModelConfig` satisfies {@link ModelBodySource} structurally.
|
|
127
|
+
* @capability collider-autofit hitboxes and physical bodies fit each kind's rendered model bounds automatically — no hand-tuned sizes
|
|
128
|
+
*/
|
|
129
|
+
export interface GameContextModels {
|
|
130
|
+
entity?(kind: string): ModelBodySource | null | undefined;
|
|
131
|
+
object?(catalogId: string): ModelBodySource | null | undefined;
|
|
132
|
+
}
|
|
133
|
+
export interface SceneObjectContext extends ObjectStore {
|
|
134
|
+
catalog(instanceId: string): GameContextObjectEntry | null;
|
|
135
|
+
raycast(input: ObjectRaycastInput): ObjectRaycastHit | null;
|
|
136
|
+
raycastAll(input: ObjectRaycastInput): readonly ObjectRaycastHit[];
|
|
137
|
+
setColliders(instanceId: string, colliders: EntityColliderSet | null): void;
|
|
138
|
+
collidersOf(instanceId: string): EntityColliderSet | null;
|
|
139
|
+
/**
|
|
140
|
+
* Report the entity-local AABB the renderer actually mounted for `catalogId` — stored per catalog
|
|
141
|
+
* id as a blocking physical body and consulted after authored `colliders` and index-`dims` fitted
|
|
142
|
+
* models, before the visual-scale fallback. The shell reports custom `renderObject` content and
|
|
143
|
+
* dims-less models automatically, so a placed prop's body wraps its real rendered shape instead of
|
|
144
|
+
* a unit-cube guess. Pass `null` to clear. Returns `false` when the bounds are degenerate.
|
|
145
|
+
* @capability measured-colliders hitboxes and blocking bodies wrap what the renderer actually mounted — custom render props and dims-less models stop falling back to fixed-size boxes.
|
|
146
|
+
*/
|
|
147
|
+
reportBounds(catalogId: string, bounds: MeasuredBounds | null): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Per-instance selection/highlight state for placed objects — the reactive counterpart to
|
|
150
|
+
* `worldHealthBars`/`nameplates` (entities), so a build-mode or RTS selection ring reads
|
|
151
|
+
* `ctx.scene.object.selection` instead of hand-rolling one through `WorldOverlay` against external
|
|
152
|
+
* state. Mutations (`add`/`remove`/`toggle`/`replace`/`clear`) bump `ctx.version()`/notify
|
|
153
|
+
* `ctx.subscribe` like every other `ctx` surface. Render it with `WorldObjectHighlights`
|
|
154
|
+
* (`@jgengine/shell/world/WorldHud`).
|
|
155
|
+
*/
|
|
156
|
+
selection: SelectionSet;
|
|
157
|
+
}
|
|
158
|
+
export interface FloatTextInput {
|
|
159
|
+
instanceId?: string;
|
|
160
|
+
position?: [number, number, number];
|
|
161
|
+
text?: string;
|
|
162
|
+
kind?: string;
|
|
163
|
+
amount?: number;
|
|
164
|
+
hitType?: string;
|
|
165
|
+
element?: string;
|
|
166
|
+
crit?: boolean;
|
|
167
|
+
scale?: number;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Request a transient spell/ability VFX burst. The easy path is a named `preset` —
|
|
171
|
+
* `vfx({ preset: "arrow", from: caster, to: enemy })` renders a visible bolt with no color
|
|
172
|
+
* or archetype tuning; `"lightning"`, `"web"`, `"slash"`, `"shield"`, `"heal"`, `"explosion"`
|
|
173
|
+
* and the rest of {@link vfxPresets} likewise just work. `from`/`to` accept an instance id
|
|
174
|
+
* (the shell follows its live pose) or a fixed world point. Anything you also pass — `kind`,
|
|
175
|
+
* `color` (`0xRRGGBB`), `radius`, `durationMs` — overrides the preset; supply `kind` + `color`
|
|
176
|
+
* yourself for a fully custom burst with no preset. `durationMs` defaults per `kind`.
|
|
177
|
+
*/
|
|
178
|
+
export interface VfxInput {
|
|
179
|
+
/** A named visual flavor from {@link vfxPresets}. Fills in `kind`/`color`/defaults; explicit fields below still win. */
|
|
180
|
+
preset?: VfxPresetName | (string & {});
|
|
181
|
+
/** Visual archetype. Optional when `preset` is given; required otherwise. */
|
|
182
|
+
kind?: VfxKind;
|
|
183
|
+
/** `0xRRGGBB` tint. Optional when `preset` is given; required otherwise. */
|
|
184
|
+
color?: number;
|
|
185
|
+
from?: string | readonly [number, number, number];
|
|
186
|
+
to?: string | readonly [number, number, number];
|
|
187
|
+
radius?: number;
|
|
188
|
+
durationMs?: number;
|
|
189
|
+
}
|
|
190
|
+
export interface TelegraphInput {
|
|
191
|
+
from: string;
|
|
192
|
+
shape: TelegraphShape;
|
|
193
|
+
at: [number, number, number];
|
|
194
|
+
dir?: number;
|
|
195
|
+
windupMs: number;
|
|
196
|
+
kind?: string;
|
|
197
|
+
effect?: {
|
|
198
|
+
effect: string;
|
|
199
|
+
via?: EffectVia;
|
|
200
|
+
radius?: number;
|
|
201
|
+
falloff?: "linear" | "none";
|
|
202
|
+
los?: boolean;
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
export interface HitReactionInput {
|
|
206
|
+
from: string;
|
|
207
|
+
to: string;
|
|
208
|
+
config: HitReactionConfig | ImpactPresetName;
|
|
209
|
+
power?: number;
|
|
210
|
+
}
|
|
211
|
+
/** Options for {@link SceneEntityContext.moveTowardCommit}: {@link MoveTowardOptions} plus an optional facing turn. */
|
|
212
|
+
export interface MoveTowardCommitOptions extends MoveTowardOptions {
|
|
213
|
+
/** Rotate the entity to face its direction of travel instead of preserving its current `rotationY`. Default false. */
|
|
214
|
+
face?: boolean;
|
|
215
|
+
}
|
|
216
|
+
export interface SceneEntityContext {
|
|
217
|
+
spawn(name: string, options?: SpawnOptions): string;
|
|
218
|
+
despawn(instanceId: string): boolean;
|
|
219
|
+
update: EntityStore["update"];
|
|
220
|
+
setPose(instanceId: string, pose: EntityPose): boolean;
|
|
221
|
+
setPoseConstraint: EntityStore["setPoseConstraint"];
|
|
222
|
+
get(instanceId: string): SceneEntity | null;
|
|
223
|
+
list(): readonly SceneEntity[];
|
|
224
|
+
ids: EntityStore["ids"];
|
|
225
|
+
subscribeMembership: EntityStore["subscribeMembership"];
|
|
226
|
+
spawnPoseOf(instanceId: string): SpawnPose | null;
|
|
227
|
+
resetToSpawn(instanceId: string): boolean;
|
|
228
|
+
resetAllToSpawn(filter?: (entity: SceneEntity) => boolean): number;
|
|
229
|
+
blackboard: EntityBlackboard;
|
|
230
|
+
stats: EntityStatsApi;
|
|
231
|
+
floatText(input: FloatTextInput): void;
|
|
232
|
+
vfx(input: VfxInput): void;
|
|
233
|
+
/**
|
|
234
|
+
* Retained VFX registry for long-lived, updatable effects (held beams, tethers, zones, target lines) whose
|
|
235
|
+
* endpoints and params change over time — the persistent complement to the one-shot {@link SceneEntityContext.vfx}
|
|
236
|
+
* burst. `upsert` creates/replaces by stable id, `update` nudges dynamic params, `stop` disposes with an optional
|
|
237
|
+
* fade; endpoints given as an entity instance id are followed live by the renderer without per-frame commands.
|
|
238
|
+
*/
|
|
239
|
+
vfxInstance: VfxInstanceStore;
|
|
240
|
+
telegraph(input: TelegraphInput): () => void;
|
|
241
|
+
hitReaction(input: HitReactionInput): HitReaction | null;
|
|
242
|
+
setTarget(fromId: string, toId: string | null): void;
|
|
243
|
+
getTarget(fromId: string): string | null;
|
|
244
|
+
cycleTarget(fromId: string, options?: CycleTargetOptions): string | null;
|
|
245
|
+
canReceive(instanceId: string, effect: string, magnitude?: number): string | null;
|
|
246
|
+
preview(input: SingleTargetEffectInput): number;
|
|
247
|
+
effect(input: EffectInput): EffectResult[];
|
|
248
|
+
willHitProjectile: ProjectileSystem["willHitProjectile"];
|
|
249
|
+
fireProjectile: ProjectileSystem["fireProjectile"];
|
|
250
|
+
settleProjectile: ProjectileSystem["settleProjectile"];
|
|
251
|
+
distance: SpatialApi["distance"];
|
|
252
|
+
inRadius: SpatialApi["inRadius"];
|
|
253
|
+
hasLineOfSight: SpatialApi["hasLineOfSight"];
|
|
254
|
+
queryArc: SpatialApi["queryArc"];
|
|
255
|
+
moveToward: SpatialApi["moveToward"];
|
|
256
|
+
/**
|
|
257
|
+
* `moveToward` plus commit: steps `instanceId` toward `target` and immediately `setPose`s the
|
|
258
|
+
* result, so a per-tick mover no longer pairs `moveToward` with a hand-written `setPose`.
|
|
259
|
+
* `options.face` turns the entity to its direction of travel; omitted preserves the current
|
|
260
|
+
* `rotationY`. Returns the committed position (`null`, committing nothing, when `moveToward` would
|
|
261
|
+
* — unknown instance/target).
|
|
262
|
+
*/
|
|
263
|
+
moveTowardCommit(instanceId: string, target: EntityPosition | string, options: MoveTowardCommitOptions): EntityPosition | null;
|
|
264
|
+
invalidateSpatial: SpatialApi["invalidate"];
|
|
265
|
+
setColliders(instanceId: string, colliders: EntityColliderSet | null): void;
|
|
266
|
+
collidersOf(instanceId: string): EntityColliderSet | null;
|
|
267
|
+
/**
|
|
268
|
+
* Report the entity-local AABB the renderer actually mounted for `kind` — stored per kind as a
|
|
269
|
+
* tight damage hitbox and consulted after authored `colliders` and index-`dims` fitted models,
|
|
270
|
+
* before the scale/humanoid-default fallbacks. The shell reports custom `renderEntity` content and
|
|
271
|
+
* dims-less models automatically, so hitboxes wrap the rendered mesh instead of the fixed
|
|
272
|
+
* 0.7×1.8×0.7 box. Runtime-measured on the client; authoritative hosts that never render should
|
|
273
|
+
* author `colliders` or model `dims` instead. Pass `null` to clear. Returns `false` when the
|
|
274
|
+
* bounds are degenerate.
|
|
275
|
+
* @capability measured-hitboxes an entity rendered with a custom mesh takes hits in a box matching its rendered bounds, not the fixed humanoid rectangle.
|
|
276
|
+
*/
|
|
277
|
+
reportBounds(kind: string, bounds: MeasuredBounds | null): boolean;
|
|
278
|
+
/** Uniform visual scale from the entity's catalog entry (1 when unscaled). */
|
|
279
|
+
visualScaleOf(instanceId: string): number;
|
|
280
|
+
form: Forms;
|
|
281
|
+
paint: PaintLayer;
|
|
282
|
+
/**
|
|
283
|
+
* Lazily creates (on first call) or returns the existing declarative bind for `key` — the sim-snapshot →
|
|
284
|
+
* scene-entity pose mirror (#673). Call `bind(key).sync(bodies, dt)` once per tick with every sim body's
|
|
285
|
+
* current snapshot instead of hand-writing `setPose` per body: an id seen for the first time is spawned
|
|
286
|
+
* from its `kind`, an id already bound is posed, and a previously-bound id absent from this tick's
|
|
287
|
+
* `bodies` is despawned — no per-game spawn/despawn dance.
|
|
288
|
+
*/
|
|
289
|
+
bind(key: string): BodyBind;
|
|
290
|
+
}
|
|
291
|
+
export type WorldItemPickupResult = {
|
|
292
|
+
status: "ok";
|
|
293
|
+
record: WorldItemRecord;
|
|
294
|
+
} | {
|
|
295
|
+
status: "rejected";
|
|
296
|
+
reason: string;
|
|
297
|
+
};
|
|
298
|
+
export interface SceneWorldItemContext {
|
|
299
|
+
spawn(input: WorldItemSpawnInput): WorldItemRecord;
|
|
300
|
+
get(instanceId: string): WorldItemRecord | null;
|
|
301
|
+
list(): readonly WorldItemRecord[];
|
|
302
|
+
nearestInRadius(from: EntityPosition, radius: number, filter?: (record: WorldItemRecord) => boolean): string | null;
|
|
303
|
+
pickup(instanceId: string, userId: string): WorldItemPickupResult;
|
|
304
|
+
consume(instanceId: string): WorldItemRecord | null;
|
|
305
|
+
}
|
|
306
|
+
export interface GameContextCommands {
|
|
307
|
+
define<TInput>(name: string, definition: CommandDefinition<GameContext, TInput>): void;
|
|
308
|
+
has(name: string): boolean;
|
|
309
|
+
names(): string[];
|
|
310
|
+
run(name: string, input: unknown): CommandResult<GameContext>;
|
|
311
|
+
/** Run a command attributed to `actorUserId` — a shared-world host routes each client's command through this so the handler can read {@link actor}. */
|
|
312
|
+
runAs(actorUserId: string, name: string, input: unknown): CommandResult<GameContext>;
|
|
313
|
+
/** The user a command is running on behalf of, or `null` outside a {@link runAs} call — commands default to `ctx.player.userId` when this is `null`. */
|
|
314
|
+
actor(): string | null;
|
|
315
|
+
}
|
|
316
|
+
export interface GameContextFeed extends Omit<GameFeed, "bind"> {
|
|
317
|
+
bind(action: keyof GameEventMap): () => void;
|
|
318
|
+
}
|
|
319
|
+
export interface GameContextLoot {
|
|
320
|
+
register(def: LootTableDef): void;
|
|
321
|
+
has(id: string): boolean;
|
|
322
|
+
roll(id: string, rng?: () => number): Drop[];
|
|
323
|
+
grantToPlayer(userId: string, drops: Drop[], source?: string): void;
|
|
324
|
+
}
|
|
325
|
+
export interface GameContextEconomy {
|
|
326
|
+
balance(userId: string, currencyId: string): number;
|
|
327
|
+
grant(userId: string, currencyId: string, amount: number): void;
|
|
328
|
+
/** `options.overdraft` opts this charge into carrying a negative balance (`true` unlimited, `{ max }` capped) — omitted keeps the strict no-debt default. */
|
|
329
|
+
charge(userId: string, currencyId: string, amount: number, options?: WalletChargeOptions): {
|
|
330
|
+
reason: string;
|
|
331
|
+
} | null;
|
|
332
|
+
/** True once `balance(userId, currencyId)` has gone negative under an overdraft-enabled charge. */
|
|
333
|
+
isOverdrawn(userId: string, currencyId: string): boolean;
|
|
334
|
+
}
|
|
335
|
+
export interface GameContextItemUse {
|
|
336
|
+
register(handlers: Record<string, ItemUseHandler<GameContext>>): void;
|
|
337
|
+
registered(): string[];
|
|
338
|
+
can(input: ItemUseInput): ItemUseRejection | null;
|
|
339
|
+
use(input: ItemUseInput): ItemUseResult<GameContext>;
|
|
340
|
+
}
|
|
341
|
+
export interface GameContextWorld {
|
|
342
|
+
ground: TerrainField;
|
|
343
|
+
groundHeightAt(x: number, z: number): number;
|
|
344
|
+
}
|
|
345
|
+
export interface GameContextCards {
|
|
346
|
+
/** Lazily creates (on first call, `config` required) or returns the existing notify-wrapped pile for `id`. */
|
|
347
|
+
pile(id: string, config?: CardPileConfig): CardPile;
|
|
348
|
+
}
|
|
349
|
+
export interface GameContextTurn {
|
|
350
|
+
/** Lazily creates (on first call, `config` required) or returns the existing notify-wrapped turn loop for `id`. */
|
|
351
|
+
loop(id: string, config?: TurnLoopConfig): TurnLoop;
|
|
352
|
+
}
|
|
353
|
+
export interface GameContextRace {
|
|
354
|
+
/** Lazily creates (on first call, `config` required) or returns the existing reactive race for `id` — discrete mutations and eventful `update` calls bump `ctx.version()`, so HUDs stop hand-managing `store.set` (#286.2). */
|
|
355
|
+
state(id: string, config?: RaceStateConfig): RaceState;
|
|
356
|
+
}
|
|
357
|
+
/** Reachable audio seam on `ctx.game`: `play`, `music`, and `resume` route through the `audio.play`/`audio.music`/`audio.resume` events the shell's audio engine listens on, so game code triggers sound without importing the shell. Retained `loop`/`setLoop`/`stopLoop` add id-keyed loops with live pitch/gain control over `audio.loopStart`/`audio.loopSet`/`audio.loopStop` (#1051). */
|
|
358
|
+
export interface GameAudio {
|
|
359
|
+
play(sound: string, at?: readonly [number, number, number]): void;
|
|
360
|
+
/** Crossfade the procedural soundtrack to `theme` (null fades out); `transpose` shifts the incoming theme's key in semitones. */
|
|
361
|
+
music(theme: string | null, transpose?: number): void;
|
|
362
|
+
resume(): void;
|
|
363
|
+
/**
|
|
364
|
+
* Start — or idempotently keep — the retained, id-keyed loop `id` playing catalog `sound`, emitting
|
|
365
|
+
* `audio.loopStart`. Re-calling with the same `sound` does not restart it (no click); a different `sound`
|
|
366
|
+
* replaces the source. Pair with {@link GameAudio.setLoop} to track a live signal — an RPM-pitched engine
|
|
367
|
+
* loop, a slip-scaled tire squeal (#1051) — and {@link GameAudio.stopLoop} to end it.
|
|
368
|
+
*/
|
|
369
|
+
loop(id: string, sound: string, options?: {
|
|
370
|
+
at?: readonly [number, number, number];
|
|
371
|
+
}): void;
|
|
372
|
+
/**
|
|
373
|
+
* Live-update retained loop `id` via `audio.loopSet`: `rate` re-pitches it (1 = authored pitch, clamped
|
|
374
|
+
* 0.25–4 by the shell), `gain` rescales volume (0–1), `at` repositions its emitter. Cheap to call every
|
|
375
|
+
* tick (~60 Hz) — the shell ramps rate/gain over ~20 ms to avoid zipper noise. A no-op when `id` is not a
|
|
376
|
+
* live loop (an update may race a stop) (#1051).
|
|
377
|
+
*/
|
|
378
|
+
setLoop(id: string, params: {
|
|
379
|
+
rate?: number;
|
|
380
|
+
gain?: number;
|
|
381
|
+
at?: readonly [number, number, number];
|
|
382
|
+
}): void;
|
|
383
|
+
/** Stop and dispose retained loop `id` (emits `audio.loopStop`); an unknown `id` is ignored (#1051). */
|
|
384
|
+
stopLoop(id: string): void;
|
|
385
|
+
}
|
|
386
|
+
/** The live engine handle a game's loop, systems, commands, and UI read and mutate — entities, objects, the reactive store, opted-in `game.*` subsystems, world queries, and the sim clock. One context = one running world. */
|
|
387
|
+
export interface GameContext {
|
|
388
|
+
scene: {
|
|
389
|
+
object: SceneObjectContext;
|
|
390
|
+
entity: SceneEntityContext;
|
|
391
|
+
worldItem: SceneWorldItemContext;
|
|
392
|
+
raycast(input: SceneRaycastInput): SceneRaycastHit | null;
|
|
393
|
+
raycastAll(input: SceneRaycastInput): readonly SceneRaycastHit[];
|
|
394
|
+
};
|
|
395
|
+
world: GameContextWorld;
|
|
396
|
+
game: {
|
|
397
|
+
commands: GameContextCommands;
|
|
398
|
+
events: GameEvents;
|
|
399
|
+
audio: GameAudio;
|
|
400
|
+
/** Play a model's one-shot animation clip bound to `event` in its `animation.oneShots` (e.g. an attack swing) — emits `entity.animation` for the shell to pick up. */
|
|
401
|
+
playEntityAnimation(instanceId: string, event: string): void;
|
|
402
|
+
feed: GameContextFeed;
|
|
403
|
+
loot: GameContextLoot;
|
|
404
|
+
/** Shop/vendor barter — present only when `features.trade` is set. */
|
|
405
|
+
trade?: TradeSystem;
|
|
406
|
+
/** Quest/mission journal — present only when `features.quest` is set. */
|
|
407
|
+
quest?: QuestJournal;
|
|
408
|
+
/** Talkable-NPC dialogue open/close bridge — present only when `features.dialogue` is set. */
|
|
409
|
+
dialogue?: GameDialogue;
|
|
410
|
+
/** Friends/party/presence/emotes/world-invites — present only when `features.social` is set. */
|
|
411
|
+
social?: Social;
|
|
412
|
+
/** Channels + messages — present only when `features.chat` is set (implies `social`). */
|
|
413
|
+
chat?: Chat;
|
|
414
|
+
/** Earned unlockable content — present only when `features.unlocks` is set. */
|
|
415
|
+
unlocks?: Unlocks;
|
|
416
|
+
economy: GameContextEconomy;
|
|
417
|
+
/** Competitive score tracking — present only when `features.leaderboard` is set. */
|
|
418
|
+
leaderboard?: Leaderboard;
|
|
419
|
+
/** Owned-entity roster — present only when `features.roster` is set. */
|
|
420
|
+
roster?: Roster;
|
|
421
|
+
/** Game-defined keyed reactive store slot (#163.1); mutations bump `ctx.version()`/notify `ctx.subscribe`. */
|
|
422
|
+
store: ObservableKeyedStore<unknown>;
|
|
423
|
+
/** Card pile zones — present only when `features.cards` is set. */
|
|
424
|
+
cards?: GameContextCards;
|
|
425
|
+
/** Turn/phase loop — present only when `features.turn` is set. */
|
|
426
|
+
turn?: GameContextTurn;
|
|
427
|
+
/** Lap/checkpoint race state — present only when `features.race` is set. */
|
|
428
|
+
race?: GameContextRace;
|
|
429
|
+
/** Connected-player set for a shared world — present only when `features.players` is set. */
|
|
430
|
+
players?: ConnectedPlayers;
|
|
431
|
+
/** Whole-world save/load bound to a pluggable backend — present only when `defineGame({ save })` is set (offline/single-player). Drive save points and quest/area checkpoints with `checkpoint()`, restore on boot with `load()`. */
|
|
432
|
+
save?: RuntimeSave;
|
|
433
|
+
/**
|
|
434
|
+
* Register a save-only snapshot module after boot (system-owned persistence).
|
|
435
|
+
* Used by `composeGameLoop` / `installSystems` when a system declares `save`.
|
|
436
|
+
*/
|
|
437
|
+
registerSave?(module: SnapshotModule): void;
|
|
438
|
+
/**
|
|
439
|
+
* Register a host→client replication module after boot (system-owned replication).
|
|
440
|
+
* Used by `composeGameLoop` / `installSystems` when a system declares `replicate`.
|
|
441
|
+
*/
|
|
442
|
+
registerReplicate?(module: SnapshotModule): void;
|
|
443
|
+
};
|
|
444
|
+
player: {
|
|
445
|
+
/** The acting player's id — the command actor inside `runAs`, the local player everywhere else. */
|
|
446
|
+
userId: string;
|
|
447
|
+
isNew: boolean;
|
|
448
|
+
/** The acting player's inventory set — the command actor's bags inside `runAs`, the local player's everywhere else. */
|
|
449
|
+
inventory: InventorySet<string>;
|
|
450
|
+
/** A specific player's inventory set — how a host reads or grants into any connected player's bags. */
|
|
451
|
+
inventoryFor(userId: string): InventorySet<string>;
|
|
452
|
+
/** The acting player's stat modifiers — the command actor's inside `runAs`, the local player's everywhere else. */
|
|
453
|
+
stats: Stats<string>;
|
|
454
|
+
/** A specific player's stat modifiers — how a host reads or buffs any connected player's stats. */
|
|
455
|
+
statsFor(userId: string): Stats<string>;
|
|
456
|
+
loadout: Loadouts;
|
|
457
|
+
applyLoadout(userId: string, loadoutId: string): {
|
|
458
|
+
reason: string;
|
|
459
|
+
} | null;
|
|
460
|
+
movement: PoseState;
|
|
461
|
+
possession: Possession;
|
|
462
|
+
/** Cosmetic skins/customization — present only when `features.cosmetics` is set. */
|
|
463
|
+
cosmetics?: Cosmetics;
|
|
464
|
+
/** Motion seam into the movement integrator (#162.4); routes to the command actor's queue (or the local player outside a command), so a command's impulse lands on whoever ran it. See `MotionIntents`. */
|
|
465
|
+
motion: MotionIntents;
|
|
466
|
+
/** A specific player's motion queue — how the host-side per-player movement integrator drains each connected player's impulses. */
|
|
467
|
+
motionFor(userId: string): MotionIntents;
|
|
468
|
+
};
|
|
469
|
+
item: {
|
|
470
|
+
use: GameContextItemUse;
|
|
471
|
+
weapon: WeaponStats;
|
|
472
|
+
};
|
|
473
|
+
time: SimClock;
|
|
474
|
+
/** Runtime camera-follow/cinematic override (#196.2); the shell reads `followedEntityId()` each frame. */
|
|
475
|
+
camera: CameraDirector;
|
|
476
|
+
/** Held-input snapshot published by the shell each frame before `onTick` (#164.1). */
|
|
477
|
+
input: InputSnapshot;
|
|
478
|
+
subscribe(listener: () => void): () => void;
|
|
479
|
+
version(): number;
|
|
480
|
+
/**
|
|
481
|
+
* Bump {@link version}/notify {@link subscribe} directly — the escape hatch for a command that only
|
|
482
|
+
* mutates game-owned state outside the entity/object/economy/store layers (an external session map,
|
|
483
|
+
* a plain closure variable) and needs to force a reactive HUD refresh without faking a `ctx.game.store.set`.
|
|
484
|
+
*/
|
|
485
|
+
touch(): void;
|
|
486
|
+
/**
|
|
487
|
+
* Gather every opted-in live subsystem into one {@link WorldSnapshot} — entities, entity stats, the
|
|
488
|
+
* keyed store, the action feed, plus leaderboard/chat when those features are on. The full-world
|
|
489
|
+
* baseline a host sends a joining client; {@link hydrate} is its inverse. Pass a `viewer` and the host
|
|
490
|
+
* projects the snapshot to only what that viewer may see (private state, area of interest) when a
|
|
491
|
+
* {@link GameContextOptions.replication} policy is set; without one the viewer argument is a no-op.
|
|
492
|
+
*/
|
|
493
|
+
snapshot(viewer?: SnapshotViewer): WorldSnapshot;
|
|
494
|
+
/** Apply a {@link WorldSnapshot} from an authoritative host, hydrating each subsystem key present in it. */
|
|
495
|
+
hydrate(snapshot: WorldSnapshot): void;
|
|
496
|
+
/** Aggregate world-dirty version summed across replicated modules; the host replicator skips an unchanged commit. @internal */
|
|
497
|
+
replicationVersion(): number;
|
|
498
|
+
/** True when a {@link GameContextOptions.replication} policy makes {@link snapshot} viewer-dependent (private/AOI projection is active). @internal */
|
|
499
|
+
replicatesPerViewer(): boolean;
|
|
500
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GameDefinition, GameLoop } from "../game/defineGame.js";
|
|
2
2
|
import type { PointerAxisState } from "../input/pointerAxis.js";
|
|
3
3
|
import type { ModelAssetRef } from "../scene/assetCatalog.js";
|
|
4
|
-
import { type GameContext, type GameContextContent } from "./gameContext.js";
|
|
4
|
+
import { type GameContext, type GameContextContent, type GameContextModels } from "./gameContext.js";
|
|
5
5
|
import type { InputSnapshot } from "./inputSnapshot.js";
|
|
6
6
|
/** One step's worth of player intent handed to {@link HeadlessRunner.step} — the held-action set and pointer state the shell would otherwise publish from the browser each frame. */
|
|
7
7
|
export interface HeadlessInput {
|
|
@@ -27,12 +27,25 @@ export interface HeadlessRunnerOptions<TAssetRef extends ModelAssetRef, TMultipl
|
|
|
27
27
|
maxStepSeconds?: number;
|
|
28
28
|
/**
|
|
29
29
|
* Drive the built-in keyboard player-movement controller from the held-action set each step, exactly as the shell's
|
|
30
|
-
* FrameDriver does for pose-driven games. Off by default —
|
|
30
|
+
* FrameDriver does for pose-driven games. Off by default — tick-driven games move their own entities inside
|
|
31
31
|
* `onTick` and don't need it.
|
|
32
32
|
*/
|
|
33
33
|
playerMovement?: boolean;
|
|
34
34
|
/** Yaw heading (radians) fed to the movement controller when `playerMovement` is on; the shell supplies its camera yaw. */
|
|
35
35
|
heading?: number;
|
|
36
|
+
/** Render-model lookup for collider auto-fit — pass what the shell would derive from `entityModels`/`objectModels` so headless raycasts hit the same boxes as play. */
|
|
37
|
+
models?: GameContextModels;
|
|
38
|
+
}
|
|
39
|
+
/** Canvas-free command/intent surface for {@link HeadlessRunner} — invoke a game's registered UI commands and read the resulting reactive state off `ctx`. */
|
|
40
|
+
export interface HeadlessCommandInterface {
|
|
41
|
+
/** Invoke a registered command/intent by name against the live context. Throws on `unknown-command` or a validation rejection so a headless UI-flow test fails loudly instead of silently no-op'ing. */
|
|
42
|
+
invoke(name: string, input?: unknown): void;
|
|
43
|
+
/** Non-throwing {@link invoke} returning the raw command result (inspect `status`). */
|
|
44
|
+
tryInvoke(name: string, input?: unknown): ReturnType<GameContext["game"]["commands"]["run"]>;
|
|
45
|
+
/** Registered command names. */
|
|
46
|
+
names(): string[];
|
|
47
|
+
/** Is a command registered? */
|
|
48
|
+
has(name: string): boolean;
|
|
36
49
|
}
|
|
37
50
|
/**
|
|
38
51
|
* A renderer-free driver for a game loop: builds a {@link GameContext} from a {@link GameDefinition}, runs the init
|
|
@@ -56,5 +69,7 @@ export interface HeadlessRunner {
|
|
|
56
69
|
step(dtSeconds: number, input?: HeadlessInput): number;
|
|
57
70
|
/** Publish input without advancing — for pre-seeding the held set before the first {@link step}. */
|
|
58
71
|
publishInput(input: HeadlessInput): void;
|
|
72
|
+
/** Drive the game's own UI commands / intents headlessly — the same registry `ctx.game.commands` and the shell's command sink dispatch into. No renderer, no pointer simulation: dispatch an intent, then read reactive state off `ctx`. */
|
|
73
|
+
readonly ui: HeadlessCommandInterface;
|
|
59
74
|
}
|
|
60
75
|
export declare function createHeadlessRunner<TAssetRef extends ModelAssetRef, TMultiplayer>(options: HeadlessRunnerOptions<TAssetRef, TMultiplayer>): HeadlessRunner;
|
|
@@ -11,6 +11,7 @@ export function createHeadlessRunner(options) {
|
|
|
11
11
|
content,
|
|
12
12
|
player,
|
|
13
13
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
14
|
+
...(options.models === undefined ? {} : { models: options.models }),
|
|
14
15
|
});
|
|
15
16
|
loop?.onInit?.(ctx);
|
|
16
17
|
loop?.onNewPlayer?.(ctx);
|
|
@@ -28,6 +29,20 @@ export function createHeadlessRunner(options) {
|
|
|
28
29
|
input: ctx.input,
|
|
29
30
|
userId: player.userId,
|
|
30
31
|
publishInput,
|
|
32
|
+
ui: {
|
|
33
|
+
invoke(name, input) {
|
|
34
|
+
const result = ctx.game.commands.run(name, input);
|
|
35
|
+
if (result.status === "unknown-command") {
|
|
36
|
+
throw new Error(`headless ui.invoke: unknown command "${name}" — define it via ctx.game.commands.define`);
|
|
37
|
+
}
|
|
38
|
+
if (result.status === "rejected") {
|
|
39
|
+
throw new Error(`headless ui.invoke: command "${name}" rejected — ${result.reason}`);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
tryInvoke: (name, input) => ctx.game.commands.run(name, input),
|
|
43
|
+
names: () => ctx.game.commands.names(),
|
|
44
|
+
has: (name) => ctx.game.commands.has(name),
|
|
45
|
+
},
|
|
31
46
|
step(dtSeconds, input) {
|
|
32
47
|
if (input !== undefined)
|
|
33
48
|
publishInput(input);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { GameServerStatus, SessionVisibility } from "./hostPersistence.js";
|
|
2
|
+
/**
|
|
3
|
+
* True when a server's `visibility` should surface in public listings / browse results.
|
|
4
|
+
* @capability host-listing-filter exclude private sessions from public browse results
|
|
5
|
+
*/
|
|
6
|
+
export declare function isListablePublicly(visibility: SessionVisibility | undefined): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Private-server join-code gate. Existing members always pass; non-members must present a
|
|
9
|
+
* matching `joinCode` (loose-normalized via {@link normalizeJoinCode}). Callers still decide
|
|
10
|
+
* whether the server is private — this only answers the code/membership half.
|
|
11
|
+
* @capability host-join-code-gate membership-or-code gate for private hosted servers
|
|
12
|
+
*/
|
|
13
|
+
export declare function canJoinPrivateServer(args: {
|
|
14
|
+
isMember: boolean;
|
|
15
|
+
joinCode: string | undefined;
|
|
16
|
+
suppliedCode: string | undefined;
|
|
17
|
+
}): boolean;
|
|
18
|
+
/** Whether `userId` is already on the server's member roster. */
|
|
19
|
+
export declare function isServerMember(memberUserIds: readonly string[], userId: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* True when the server has no free slots for a non-member. Existing members never count as
|
|
22
|
+
* "full" so rejoin/leave cycles keep working.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isServerFull(memberUserIds: readonly string[], slotsPerServer: number, userId: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether a private-visibility server blocks this join (non-member without a matching code).
|
|
27
|
+
* Public / undefined visibility never blocks.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isPrivateJoinBlocked(args: {
|
|
30
|
+
visibility: SessionVisibility | undefined;
|
|
31
|
+
memberUserIds: readonly string[];
|
|
32
|
+
userId: string;
|
|
33
|
+
joinCode: string | undefined;
|
|
34
|
+
suppliedCode: string | undefined;
|
|
35
|
+
}): boolean;
|
|
36
|
+
/** Roster after a successful join: unchanged when already a member, else appended. */
|
|
37
|
+
export declare function withJoinedMember(memberUserIds: readonly string[], userId: string): string[];
|
|
38
|
+
/** Roster after a leave: `userId` removed; order of remaining members preserved. */
|
|
39
|
+
export declare function withoutMember(memberUserIds: readonly string[], userId: string): string[];
|
|
40
|
+
/** Status after a leave: empty rooms reopen; non-empty rooms keep their current status. */
|
|
41
|
+
export declare function statusAfterLeave(remainingMemberCount: number, currentStatus: GameServerStatus): GameServerStatus;
|
|
42
|
+
/**
|
|
43
|
+
* Auto-match candidate when no `serverId` is supplied: already a member, or a public room with
|
|
44
|
+
* free capacity. Private rooms are never auto-picked (join-by-code / direct id only).
|
|
45
|
+
*/
|
|
46
|
+
export declare function isAutoJoinCandidate(args: {
|
|
47
|
+
memberUserIds: readonly string[];
|
|
48
|
+
slotsPerServer: number;
|
|
49
|
+
visibility: SessionVisibility | undefined;
|
|
50
|
+
userId: string;
|
|
51
|
+
}): boolean;
|