@jgengine/core 0.26.0 → 0.28.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 +174 -0
- package/dist/ai/policeResponse.d.ts +253 -0
- package/dist/ai/policeResponse.js +421 -0
- package/dist/anim/actionLayer.d.ts +47 -0
- package/dist/anim/actionLayer.js +63 -0
- package/dist/anim/ikSolver.d.ts +14 -0
- package/dist/anim/ikSolver.js +37 -0
- package/dist/anim/locomotionGraph.d.ts +58 -3
- package/dist/anim/locomotionGraph.js +142 -15
- package/dist/anim/ragdollRig.d.ts +53 -0
- package/dist/anim/ragdollRig.js +132 -0
- package/dist/anim/skeletonStandard.d.ts +140 -0
- package/dist/anim/skeletonStandard.js +326 -0
- package/dist/combat/damageRuntime.d.ts +14 -1
- package/dist/combat/damageRuntime.js +62 -4
- package/dist/combat/death.js +7 -3
- package/dist/combat/deathReason.d.ts +7 -1
- package/dist/combat/effects.d.ts +25 -0
- package/dist/combat/effects.js +16 -0
- package/dist/combat/statusRuntime.d.ts +2 -0
- package/dist/combat/statusRuntime.js +1 -0
- package/dist/economy/loanBook.d.ts +182 -0
- package/dist/economy/loanBook.js +303 -0
- package/dist/economy/market.d.ts +161 -0
- package/dist/economy/market.js +248 -0
- package/dist/economy/transactionLedger.d.ts +162 -0
- package/dist/economy/transactionLedger.js +301 -0
- package/dist/editor/creatorStorage.js +3 -1
- package/dist/editor/simulation.d.ts +5 -2
- package/dist/editor/simulation.js +29 -19
- package/dist/game/cameraConfig.d.ts +14 -0
- package/dist/game/clipRoles.d.ts +5 -4
- package/dist/game/clipRoles.js +34 -4
- package/dist/game/events.d.ts +27 -0
- package/dist/game/gameSave.d.ts +142 -0
- package/dist/game/gameSave.js +174 -0
- package/dist/game/playableGame.d.ts +74 -6
- package/dist/game/saveSlotIndex.d.ts +54 -0
- package/dist/game/saveSlotIndex.js +71 -0
- package/dist/game/saveSlots.d.ts +2 -0
- package/dist/game/saveSlots.js +1 -1
- package/dist/game/saveStore.d.ts +8 -0
- package/dist/game/saveStore.js +16 -0
- package/dist/gameplay.d.ts +17 -2
- package/dist/gameplay.js +15 -0
- package/dist/inventory/carryLoad.d.ts +48 -0
- package/dist/inventory/carryLoad.js +44 -0
- package/dist/inventory/equipment.d.ts +95 -0
- package/dist/inventory/equipment.js +169 -0
- package/dist/inventory/inventoryModel.d.ts +15 -6
- package/dist/inventory/inventoryModel.js +54 -5
- package/dist/inventory/shapedGrid.d.ts +26 -0
- package/dist/inventory/shapedGrid.js +47 -0
- package/dist/loot/lootGen.d.ts +186 -0
- package/dist/loot/lootGen.js +437 -0
- package/dist/loot/lootWeapon.d.ts +35 -0
- package/dist/loot/lootWeapon.js +70 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +156 -1
- package/dist/mission/compositions.d.ts +85 -0
- package/dist/mission/compositions.js +130 -0
- package/dist/mission/mission.d.ts +435 -0
- package/dist/mission/mission.js +770 -0
- package/dist/mission/triggers.d.ts +61 -0
- package/dist/mission/triggers.js +108 -0
- package/dist/nav/laneGraph.d.ts +178 -0
- package/dist/nav/laneGraph.js +671 -0
- package/dist/nav/traffic.d.ts +222 -0
- package/dist/nav/traffic.js +918 -0
- package/dist/physics/kinematicVehicle.d.ts +36 -0
- package/dist/physics/kinematicVehicle.js +15 -1
- package/dist/physics/vehicleDamage.d.ts +136 -0
- package/dist/physics/vehicleDamage.js +214 -0
- package/dist/physics/vehicleParts.d.ts +62 -0
- package/dist/physics/vehicleParts.js +65 -0
- package/dist/progression/gearScore.d.ts +56 -0
- package/dist/progression/gearScore.js +59 -0
- package/dist/progression/skillTree.d.ts +129 -0
- package/dist/progression/skillTree.js +265 -0
- package/dist/progression/xpCurve.d.ts +60 -0
- package/dist/progression/xpCurve.js +141 -0
- package/dist/runtime/context/combat.js +11 -0
- package/dist/scene/builtinSceneKinds.js +2 -0
- package/dist/settings/accessibilitySettings.d.ts +81 -0
- package/dist/settings/accessibilitySettings.js +152 -0
- package/dist/settings/graphicsProfile.d.ts +3 -0
- package/dist/settings/graphicsProfile.js +4 -0
- package/dist/settings/settingsModel.d.ts +1 -1
- package/dist/settings/settingsModel.js +1 -0
- package/dist/sim/nearAgents.d.ts +8 -1
- package/dist/sim/nearAgents.js +34 -17
- package/dist/sim/offScreen.d.ts +2 -1
- package/dist/sim/offScreen.js +14 -3
- package/dist/sim/population.d.ts +147 -0
- package/dist/sim/population.js +182 -0
- package/dist/sim/queue.d.ts +90 -0
- package/dist/sim/queue.js +171 -0
- package/dist/sim/rides.d.ts +287 -0
- package/dist/sim/rides.js +471 -0
- package/dist/sim/routine.js +19 -11
- package/dist/ui/loadingTips.d.ts +41 -0
- package/dist/ui/loadingTips.js +64 -0
- package/dist/ui/menuNav.d.ts +79 -0
- package/dist/ui/menuNav.js +147 -0
- package/dist/ui/menuTransition.d.ts +50 -0
- package/dist/ui/menuTransition.js +67 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +2 -0
- package/dist/vfx/effectLibrary.d.ts +98 -0
- package/dist/vfx/effectLibrary.js +181 -0
- package/dist/vfx/particleBudget.d.ts +40 -0
- package/dist/vfx/particleBudget.js +39 -0
- package/dist/vfx/particleDirector.d.ts +16 -0
- package/dist/vfx/particleDirector.js +18 -0
- package/dist/world/authoredSimulation.js +7 -1
- package/dist/world/captureCamera.d.ts +30 -0
- package/dist/world/captureCamera.js +19 -0
- package/dist/world/decalKind.d.ts +48 -0
- package/dist/world/decalKind.js +86 -0
- package/dist/world/features.d.ts +18 -5
- package/dist/world/features.js +1 -0
- package/dist/world/grassKind.d.ts +5 -0
- package/dist/world/grassKind.js +3 -0
- package/dist/world/terraform.d.ts +8 -0
- package/dist/world/terrain.d.ts +1 -0
- package/dist/world/terrain.js +2 -0
- package/dist/world.d.ts +7 -2
- package/dist/world.js +5 -0
- package/package.json +1 -1
- package/skills/jgengine/api.md +2 -2
- package/skills/jgengine-combat/SKILL.md +9 -1
- package/skills/jgengine-combat/api.md +5 -4
- package/skills/jgengine-combat/reference.md +4 -0
- package/skills/jgengine-gameplay/SKILL.md +86 -0
- package/skills/jgengine-gameplay/api.md +485 -16
- package/skills/jgengine-gameplay/capabilities.md +131 -4
- package/skills/jgengine-gameplay/reference-systems.md +118 -0
- package/skills/jgengine-world/SKILL.md +8 -0
- package/skills/jgengine-world/api.md +243 -11
- package/skills/jgengine-world/capabilities.md +61 -1
- package/skills/jgengine-world/recipes/character-animation.md +25 -2
- package/skills/jgengine-world/recipes/vehicle-feel.md +5 -0
- package/skills/jgengine-world/reference.md +1 -1
|
@@ -13,6 +13,7 @@ import { registerWaterKind } from "../world/waterKind.js";
|
|
|
13
13
|
import { registerGrassKind } from "../world/grassKind.js";
|
|
14
14
|
import { registerSoilKind } from "../world/soilKind.js";
|
|
15
15
|
import { registerPoleLineKind } from "../world/poleLineKind.js";
|
|
16
|
+
import { registerDecalKind } from "../world/decalKind.js";
|
|
16
17
|
import { registerBuildingGenerator } from "../world/buildingGenerator.js";
|
|
17
18
|
import { registerCityKind } from "../world/cityKind.js";
|
|
18
19
|
import { describeScatterCoverage, densityCoverage } from "../world/scatterCoverage.js";
|
|
@@ -80,5 +81,6 @@ export function registerBuiltinSceneKinds() {
|
|
|
80
81
|
registerSoilKind();
|
|
81
82
|
registerCityKind();
|
|
82
83
|
registerPoleLineKind();
|
|
84
|
+
registerDecalKind();
|
|
83
85
|
registerBuildingGenerator();
|
|
84
86
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { AccessibilityState, ColorblindMode } from "../ui/accessibility.js";
|
|
2
|
+
import type { GameSettingDef, SettingOption, SettingValue, SettingsStore } from "./settingsModel.js";
|
|
3
|
+
/**
|
|
4
|
+
* A "hold or toggle" preference for actions that can be either (aim, sprint, crouch, block). Shared
|
|
5
|
+
* because players expect one setting to cover them all, rather than each game inventing a flag.
|
|
6
|
+
*/
|
|
7
|
+
export type HoldVsToggle = "hold" | "toggle";
|
|
8
|
+
/** The persisted settings ids behind the accessibility section. */
|
|
9
|
+
export declare const ACCESSIBILITY_SETTING_IDS: {
|
|
10
|
+
readonly textScale: "accessibility.textScale";
|
|
11
|
+
readonly colorblind: "accessibility.colorblind";
|
|
12
|
+
readonly reducedMotion: "accessibility.reducedMotion";
|
|
13
|
+
readonly highContrast: "accessibility.highContrast";
|
|
14
|
+
readonly captions: "accessibility.captions";
|
|
15
|
+
readonly holdVsToggle: "controls.holdVsToggle";
|
|
16
|
+
};
|
|
17
|
+
/** Text-scale choices, as numeric strings the settings store persists. */
|
|
18
|
+
export declare const TEXT_SCALE_OPTIONS: readonly SettingOption[];
|
|
19
|
+
/** Colorblind palette choices. `none` keeps the game's own colours. */
|
|
20
|
+
export declare const COLORBLIND_OPTIONS: readonly SettingOption[];
|
|
21
|
+
/** Hold-versus-toggle choices for actions that support both. */
|
|
22
|
+
export declare const HOLD_VS_TOGGLE_OPTIONS: readonly SettingOption[];
|
|
23
|
+
/** Read a stored text scale, clamped to the supported range. */
|
|
24
|
+
export declare function readTextScale(store: SettingsStore): number;
|
|
25
|
+
/** Read a stored colorblind mode, falling back to `none` for an unknown value. */
|
|
26
|
+
export declare function readColorblind(store: SettingsStore): ColorblindMode;
|
|
27
|
+
/** Read the hold-or-toggle preference, falling back to `hold`. */
|
|
28
|
+
export declare function readHoldVsToggle(store: SettingsStore): HoldVsToggle;
|
|
29
|
+
/**
|
|
30
|
+
* Read the whole accessibility state the presentation needs (reduced motion, high contrast, text
|
|
31
|
+
* scale, colorblind mode, captions) plus the hold/toggle preference, from the shared settings store.
|
|
32
|
+
*
|
|
33
|
+
* @capability accessibility-settings read accessibility preferences and the hold-vs-toggle choice from the shared settings store
|
|
34
|
+
*/
|
|
35
|
+
export declare function readAccessibilitySettings(store: SettingsStore): {
|
|
36
|
+
state: AccessibilityState;
|
|
37
|
+
holdVsToggle: HoldVsToggle;
|
|
38
|
+
};
|
|
39
|
+
/** One accessibility setting as the shared settings menu renders it. */
|
|
40
|
+
export interface AccessibilitySettingRow {
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
43
|
+
kind: "slider" | "toggle" | "select";
|
|
44
|
+
value: SettingValue;
|
|
45
|
+
min?: number;
|
|
46
|
+
max?: number;
|
|
47
|
+
step?: number;
|
|
48
|
+
options?: readonly SettingOption[];
|
|
49
|
+
format?: (value: number) => string;
|
|
50
|
+
set: (value: SettingValue) => void;
|
|
51
|
+
/** When this row drives presentation, the accessibility state key it maps to. */
|
|
52
|
+
affects?: keyof AccessibilityState;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The accessibility rows, wired to the settings store: text scale, colorblind palette, reduced
|
|
56
|
+
* motion, high contrast, captions and hold-vs-toggle. The shell adds them as its `accessibility`
|
|
57
|
+
* category and any game can render them inside its own settings layout.
|
|
58
|
+
*
|
|
59
|
+
* @capability accessibility-settings-rows accessibility setting rows (text scale, colorblind, reduced motion, high contrast, captions, hold-vs-toggle) backed by the shared settings store
|
|
60
|
+
*/
|
|
61
|
+
export declare function accessibilitySettingRows(store: SettingsStore): AccessibilitySettingRow[];
|
|
62
|
+
/**
|
|
63
|
+
* Declarative form of the accessibility rows for `defineGame({ settings })`, for a game that wants
|
|
64
|
+
* them inside its own setting categories rather than the built-in `accessibility` one.
|
|
65
|
+
*/
|
|
66
|
+
export declare function accessibilitySettingDefs(category?: string): GameSettingDef[];
|
|
67
|
+
/** One frame of an action's raw input. */
|
|
68
|
+
export interface HoldToggleInput {
|
|
69
|
+
/** The action's key/button went down this frame (the edge). */
|
|
70
|
+
pressed: boolean;
|
|
71
|
+
/** The action's key/button is down. */
|
|
72
|
+
held: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* The action's active state for one frame under a {@link HoldVsToggle} preference: `hold` follows the
|
|
76
|
+
* button, `toggle` latches on each press edge. Call with the previous result so toggle mode can flip
|
|
77
|
+
* without the caller keeping a second flag.
|
|
78
|
+
*
|
|
79
|
+
* @capability hold-vs-toggle resolve one action frame under a hold-or-toggle preference
|
|
80
|
+
*/
|
|
81
|
+
export declare function holdToggleActive(previous: boolean, input: HoldToggleInput, mode: HoldVsToggle): boolean;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { DEFAULT_ACCESSIBILITY, clampTextScale } from "../ui/accessibility.js";
|
|
2
|
+
/** The persisted settings ids behind the accessibility section. */
|
|
3
|
+
export const ACCESSIBILITY_SETTING_IDS = {
|
|
4
|
+
textScale: "accessibility.textScale",
|
|
5
|
+
colorblind: "accessibility.colorblind",
|
|
6
|
+
reducedMotion: "accessibility.reducedMotion",
|
|
7
|
+
highContrast: "accessibility.highContrast",
|
|
8
|
+
captions: "accessibility.captions",
|
|
9
|
+
holdVsToggle: "controls.holdVsToggle",
|
|
10
|
+
};
|
|
11
|
+
/** Text-scale choices, as numeric strings the settings store persists. */
|
|
12
|
+
export const TEXT_SCALE_OPTIONS = [
|
|
13
|
+
{ value: "1", label: "100%" },
|
|
14
|
+
{ value: "1.15", label: "115%" },
|
|
15
|
+
{ value: "1.3", label: "130%" },
|
|
16
|
+
{ value: "1.5", label: "150%" },
|
|
17
|
+
{ value: "1.75", label: "175%" },
|
|
18
|
+
{ value: "2", label: "200%" },
|
|
19
|
+
];
|
|
20
|
+
/** Colorblind palette choices. `none` keeps the game's own colours. */
|
|
21
|
+
export const COLORBLIND_OPTIONS = [
|
|
22
|
+
{ value: "none", label: "Off" },
|
|
23
|
+
{ value: "protanopia", label: "Protanopia" },
|
|
24
|
+
{ value: "deuteranopia", label: "Deuteranopia" },
|
|
25
|
+
{ value: "tritanopia", label: "Tritanopia" },
|
|
26
|
+
{ value: "grayscale", label: "Grayscale" },
|
|
27
|
+
];
|
|
28
|
+
/** Hold-versus-toggle choices for actions that support both. */
|
|
29
|
+
export const HOLD_VS_TOGGLE_OPTIONS = [
|
|
30
|
+
{ value: "hold", label: "Hold" },
|
|
31
|
+
{ value: "toggle", label: "Toggle" },
|
|
32
|
+
];
|
|
33
|
+
/** Read a stored text scale, clamped to the supported range. */
|
|
34
|
+
export function readTextScale(store) {
|
|
35
|
+
return clampTextScale(Number(store.get(ACCESSIBILITY_SETTING_IDS.textScale, 1)));
|
|
36
|
+
}
|
|
37
|
+
/** Read a stored colorblind mode, falling back to `none` for an unknown value. */
|
|
38
|
+
export function readColorblind(store) {
|
|
39
|
+
const value = String(store.get(ACCESSIBILITY_SETTING_IDS.colorblind, "none"));
|
|
40
|
+
const known = COLORBLIND_OPTIONS.some((option) => option.value === value);
|
|
41
|
+
return known ? value : "none";
|
|
42
|
+
}
|
|
43
|
+
/** Read the hold-or-toggle preference, falling back to `hold`. */
|
|
44
|
+
export function readHoldVsToggle(store) {
|
|
45
|
+
const value = String(store.get(ACCESSIBILITY_SETTING_IDS.holdVsToggle, "hold"));
|
|
46
|
+
return value === "toggle" ? "toggle" : "hold";
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Read the whole accessibility state the presentation needs (reduced motion, high contrast, text
|
|
50
|
+
* scale, colorblind mode, captions) plus the hold/toggle preference, from the shared settings store.
|
|
51
|
+
*
|
|
52
|
+
* @capability accessibility-settings read accessibility preferences and the hold-vs-toggle choice from the shared settings store
|
|
53
|
+
*/
|
|
54
|
+
export function readAccessibilitySettings(store) {
|
|
55
|
+
return {
|
|
56
|
+
state: {
|
|
57
|
+
reducedMotion: Boolean(store.get(ACCESSIBILITY_SETTING_IDS.reducedMotion, DEFAULT_ACCESSIBILITY.reducedMotion)),
|
|
58
|
+
highContrast: Boolean(store.get(ACCESSIBILITY_SETTING_IDS.highContrast, DEFAULT_ACCESSIBILITY.highContrast)),
|
|
59
|
+
textScale: readTextScale(store),
|
|
60
|
+
colorblind: readColorblind(store),
|
|
61
|
+
captions: Boolean(store.get(ACCESSIBILITY_SETTING_IDS.captions, DEFAULT_ACCESSIBILITY.captions)),
|
|
62
|
+
},
|
|
63
|
+
holdVsToggle: readHoldVsToggle(store),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The accessibility rows, wired to the settings store: text scale, colorblind palette, reduced
|
|
68
|
+
* motion, high contrast, captions and hold-vs-toggle. The shell adds them as its `accessibility`
|
|
69
|
+
* category and any game can render them inside its own settings layout.
|
|
70
|
+
*
|
|
71
|
+
* @capability accessibility-settings-rows accessibility setting rows (text scale, colorblind, reduced motion, high contrast, captions, hold-vs-toggle) backed by the shared settings store
|
|
72
|
+
*/
|
|
73
|
+
export function accessibilitySettingRows(store) {
|
|
74
|
+
return [
|
|
75
|
+
{
|
|
76
|
+
id: ACCESSIBILITY_SETTING_IDS.textScale,
|
|
77
|
+
label: "Text size",
|
|
78
|
+
kind: "select",
|
|
79
|
+
value: String(readTextScale(store)),
|
|
80
|
+
options: TEXT_SCALE_OPTIONS,
|
|
81
|
+
set: (value) => store.set(ACCESSIBILITY_SETTING_IDS.textScale, Number(value)),
|
|
82
|
+
affects: "textScale",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: ACCESSIBILITY_SETTING_IDS.colorblind,
|
|
86
|
+
label: "Colourblind palette",
|
|
87
|
+
kind: "select",
|
|
88
|
+
value: readColorblind(store),
|
|
89
|
+
options: COLORBLIND_OPTIONS,
|
|
90
|
+
set: (value) => store.set(ACCESSIBILITY_SETTING_IDS.colorblind, String(value)),
|
|
91
|
+
affects: "colorblind",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
id: ACCESSIBILITY_SETTING_IDS.reducedMotion,
|
|
95
|
+
label: "Reduce motion",
|
|
96
|
+
kind: "toggle",
|
|
97
|
+
value: Boolean(store.get(ACCESSIBILITY_SETTING_IDS.reducedMotion, DEFAULT_ACCESSIBILITY.reducedMotion)),
|
|
98
|
+
set: (value) => store.set(ACCESSIBILITY_SETTING_IDS.reducedMotion, Boolean(value)),
|
|
99
|
+
affects: "reducedMotion",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: ACCESSIBILITY_SETTING_IDS.highContrast,
|
|
103
|
+
label: "High contrast",
|
|
104
|
+
kind: "toggle",
|
|
105
|
+
value: Boolean(store.get(ACCESSIBILITY_SETTING_IDS.highContrast, DEFAULT_ACCESSIBILITY.highContrast)),
|
|
106
|
+
set: (value) => store.set(ACCESSIBILITY_SETTING_IDS.highContrast, Boolean(value)),
|
|
107
|
+
affects: "highContrast",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: ACCESSIBILITY_SETTING_IDS.captions,
|
|
111
|
+
label: "Captions",
|
|
112
|
+
kind: "toggle",
|
|
113
|
+
value: Boolean(store.get(ACCESSIBILITY_SETTING_IDS.captions, DEFAULT_ACCESSIBILITY.captions)),
|
|
114
|
+
set: (value) => store.set(ACCESSIBILITY_SETTING_IDS.captions, Boolean(value)),
|
|
115
|
+
affects: "captions",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: ACCESSIBILITY_SETTING_IDS.holdVsToggle,
|
|
119
|
+
label: "Hold or toggle aim",
|
|
120
|
+
kind: "select",
|
|
121
|
+
value: readHoldVsToggle(store),
|
|
122
|
+
options: HOLD_VS_TOGGLE_OPTIONS,
|
|
123
|
+
set: (value) => store.set(ACCESSIBILITY_SETTING_IDS.holdVsToggle, String(value)),
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Declarative form of the accessibility rows for `defineGame({ settings })`, for a game that wants
|
|
129
|
+
* them inside its own setting categories rather than the built-in `accessibility` one.
|
|
130
|
+
*/
|
|
131
|
+
export function accessibilitySettingDefs(category = "accessibility") {
|
|
132
|
+
return [
|
|
133
|
+
{ id: ACCESSIBILITY_SETTING_IDS.textScale, label: "Text size", category, kind: "select", default: "1", options: TEXT_SCALE_OPTIONS },
|
|
134
|
+
{ id: ACCESSIBILITY_SETTING_IDS.colorblind, label: "Colourblind palette", category, kind: "select", default: "none", options: COLORBLIND_OPTIONS },
|
|
135
|
+
{ id: ACCESSIBILITY_SETTING_IDS.reducedMotion, label: "Reduce motion", category, kind: "toggle", default: false },
|
|
136
|
+
{ id: ACCESSIBILITY_SETTING_IDS.highContrast, label: "High contrast", category, kind: "toggle", default: false },
|
|
137
|
+
{ id: ACCESSIBILITY_SETTING_IDS.captions, label: "Captions", category, kind: "toggle", default: false },
|
|
138
|
+
{ id: ACCESSIBILITY_SETTING_IDS.holdVsToggle, label: "Hold or toggle aim", category: "controls", kind: "select", default: "hold", options: HOLD_VS_TOGGLE_OPTIONS },
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The action's active state for one frame under a {@link HoldVsToggle} preference: `hold` follows the
|
|
143
|
+
* button, `toggle` latches on each press edge. Call with the previous result so toggle mode can flip
|
|
144
|
+
* without the caller keeping a second flag.
|
|
145
|
+
*
|
|
146
|
+
* @capability hold-vs-toggle resolve one action frame under a hold-or-toggle preference
|
|
147
|
+
*/
|
|
148
|
+
export function holdToggleActive(previous, input, mode) {
|
|
149
|
+
if (mode === "hold")
|
|
150
|
+
return input.held;
|
|
151
|
+
return input.pressed ? !previous : previous;
|
|
152
|
+
}
|
|
@@ -10,7 +10,10 @@ export interface GraphicsProfile {
|
|
|
10
10
|
shadowMapSize: 512 | 1024 | 2048 | 4096;
|
|
11
11
|
cascades: 1 | 2 | 3 | 4;
|
|
12
12
|
drawDistance: number;
|
|
13
|
+
/** Largest pool one particle effect may allocate. */
|
|
13
14
|
particleCap: number;
|
|
15
|
+
/** Live particles the whole scene may hold across every effect; the VFX library drops lowest-priority effects past it. */
|
|
16
|
+
particleBudget: number;
|
|
14
17
|
/** Anisotropic texture filtering samples; clamped to the GPU maximum. 1 is plain trilinear. */
|
|
15
18
|
anisotropy: 1 | 2 | 4 | 8 | 16;
|
|
16
19
|
postStages: {
|
|
@@ -6,6 +6,7 @@ export const DEFAULT_GRAPHICS_PROFILES = {
|
|
|
6
6
|
cascades: 1,
|
|
7
7
|
drawDistance: 120,
|
|
8
8
|
particleCap: 128,
|
|
9
|
+
particleBudget: 1024,
|
|
9
10
|
anisotropy: 2,
|
|
10
11
|
postStages: { ao: false, bloom: true, dof: false, smaa: true },
|
|
11
12
|
surfaceDetail: "lite",
|
|
@@ -16,6 +17,7 @@ export const DEFAULT_GRAPHICS_PROFILES = {
|
|
|
16
17
|
cascades: 2,
|
|
17
18
|
drawDistance: 240,
|
|
18
19
|
particleCap: 256,
|
|
20
|
+
particleBudget: 2048,
|
|
19
21
|
anisotropy: 4,
|
|
20
22
|
postStages: { ao: true, bloom: true, dof: true, smaa: true },
|
|
21
23
|
surfaceDetail: "full",
|
|
@@ -26,6 +28,7 @@ export const DEFAULT_GRAPHICS_PROFILES = {
|
|
|
26
28
|
cascades: 4,
|
|
27
29
|
drawDistance: 400,
|
|
28
30
|
particleCap: 512,
|
|
31
|
+
particleBudget: 4096,
|
|
29
32
|
anisotropy: 8,
|
|
30
33
|
postStages: { ao: true, bloom: true, dof: true, smaa: true },
|
|
31
34
|
surfaceDetail: "full",
|
|
@@ -36,6 +39,7 @@ export const DEFAULT_GRAPHICS_PROFILES = {
|
|
|
36
39
|
cascades: 4,
|
|
37
40
|
drawDistance: 800,
|
|
38
41
|
particleCap: 1024,
|
|
42
|
+
particleBudget: 8192,
|
|
39
43
|
anisotropy: 16,
|
|
40
44
|
postStages: { ao: true, bloom: true, dof: true, smaa: true },
|
|
41
45
|
surfaceDetail: "full",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GameContext } from "../runtime/gameContext.js";
|
|
2
2
|
export type SettingValue = number | boolean | string;
|
|
3
3
|
export type SettingKind = "slider" | "toggle" | "select";
|
|
4
|
-
export type BuiltInSettingCategory = "sound" | "graphics" | "gameplay" | "controls";
|
|
4
|
+
export type BuiltInSettingCategory = "sound" | "graphics" | "gameplay" | "controls" | "accessibility";
|
|
5
5
|
/** Built-in category ids keep autocomplete; any other string makes a fresh category. */
|
|
6
6
|
export type SettingCategory = BuiltInSettingCategory | (string & {});
|
|
7
7
|
export declare const BUILT_IN_SETTING_CATEGORIES: readonly BuiltInSettingCategory[];
|
package/dist/sim/nearAgents.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Vec3 } from "../world/geometry.js";
|
|
2
|
-
import { type NavCrowd, type NavCrowdOptions } from "../nav/navCrowd.js";
|
|
2
|
+
import { type NavCrowd, type NavCrowdOptions, type NavCrowdSnapshot } from "../nav/navCrowd.js";
|
|
3
3
|
import type { NavRuntime } from "../nav/navRuntime.js";
|
|
4
4
|
import { type UtilityBrainSnapshot, type UtilityCandidate, type UtilityInterrupt, type UtilityOption } from "../ai/utilityBrain.js";
|
|
5
5
|
import { type NeedDefinition, type NeedSnapshot } from "./needs.js";
|
|
@@ -51,11 +51,18 @@ export interface NearCrowdAgents extends NearAgents {
|
|
|
51
51
|
decisions(): number;
|
|
52
52
|
view(id: string): NearAgentView | null;
|
|
53
53
|
crowd(): NavCrowd;
|
|
54
|
+
/**
|
|
55
|
+
* Serializable state: each agent's needs and held pick, plus the crowd's own routes. The crowd is
|
|
56
|
+
* part of the snapshot because a restored agent must resume the route it was walking, not re-plan a
|
|
57
|
+
* fresh one from wherever it stood.
|
|
58
|
+
*/
|
|
54
59
|
snapshot(): {
|
|
55
60
|
agents: NearAgentSnapshot[];
|
|
61
|
+
crowd: NavCrowdSnapshot;
|
|
56
62
|
};
|
|
57
63
|
restore(next: {
|
|
58
64
|
agents: readonly NearAgentSnapshot[];
|
|
65
|
+
crowd: NavCrowdSnapshot;
|
|
59
66
|
}): void;
|
|
60
67
|
}
|
|
61
68
|
/**
|
package/dist/sim/nearAgents.js
CHANGED
|
@@ -33,16 +33,19 @@ export function createNearAgents(options) {
|
|
|
33
33
|
needs: agent.needs.snapshot(),
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
+
function makeBrain(forNeeds) {
|
|
37
|
+
return createUtilityBrain({
|
|
38
|
+
needs: forNeeds,
|
|
39
|
+
options: options.options ?? [],
|
|
40
|
+
...(options.interrupts === undefined ? {} : { interrupts: options.interrupts }),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
36
43
|
function mount(next, saved) {
|
|
37
44
|
if (agents.has(next.id))
|
|
38
45
|
return;
|
|
39
46
|
const needs = createNeedSet(defs);
|
|
40
47
|
needs.restore(saved === undefined ? { values: { ...next.needs }, rates: {}, modifiers: {} } : saved.needs);
|
|
41
|
-
const brain =
|
|
42
|
-
needs,
|
|
43
|
-
options: options.options ?? [],
|
|
44
|
-
...(options.interrupts === undefined ? {} : { interrupts: options.interrupts }),
|
|
45
|
-
});
|
|
48
|
+
const brain = makeBrain(needs);
|
|
46
49
|
if (saved !== undefined)
|
|
47
50
|
brain.restore(saved.brain);
|
|
48
51
|
const goal = saved?.goal ?? next.goal;
|
|
@@ -120,25 +123,39 @@ export function createNearAgents(options) {
|
|
|
120
123
|
brain: agent.brain.snapshot(),
|
|
121
124
|
});
|
|
122
125
|
}
|
|
123
|
-
return { agents: out };
|
|
126
|
+
return { agents: out, crowd: crowd.snapshot() };
|
|
124
127
|
},
|
|
125
128
|
restore(next) {
|
|
126
|
-
for (const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
for (const agent of agents.values())
|
|
130
|
+
agent.brain.dispose();
|
|
131
|
+
agents.clear();
|
|
132
|
+
// The crowd's own snapshot carries each agent's route, so a restored agent walks the corridor it
|
|
133
|
+
// was on rather than a fresh plan from its current spot.
|
|
134
|
+
crowd.restore(next.crowd);
|
|
131
135
|
for (const saved of next.agents) {
|
|
132
|
-
|
|
136
|
+
const needs = createNeedSet(defs);
|
|
137
|
+
needs.restore(saved.needs);
|
|
138
|
+
const brain = makeBrain(needs);
|
|
139
|
+
brain.restore(saved.brain);
|
|
140
|
+
agents.set(saved.id, {
|
|
133
141
|
id: saved.id,
|
|
134
|
-
|
|
142
|
+
needs,
|
|
143
|
+
brain,
|
|
144
|
+
goal: saved.goal,
|
|
135
145
|
place: saved.place,
|
|
136
146
|
activity: saved.activity,
|
|
137
|
-
needs: saved.needs.values,
|
|
138
|
-
goal: saved.goal,
|
|
139
|
-
stop: 0,
|
|
140
147
|
travelling: saved.travelling,
|
|
141
|
-
|
|
148
|
+
spawned: {
|
|
149
|
+
id: saved.id,
|
|
150
|
+
position: saved.position,
|
|
151
|
+
place: saved.place,
|
|
152
|
+
activity: saved.activity,
|
|
153
|
+
needs: saved.needs.values,
|
|
154
|
+
goal: saved.goal,
|
|
155
|
+
stop: 0,
|
|
156
|
+
travelling: saved.travelling,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
142
159
|
}
|
|
143
160
|
},
|
|
144
161
|
dispose() {
|
package/dist/sim/offScreen.d.ts
CHANGED
|
@@ -119,7 +119,8 @@ export interface OffScreenSnapshot {
|
|
|
119
119
|
travelling: boolean;
|
|
120
120
|
pinned: boolean;
|
|
121
121
|
updatedAt: number;
|
|
122
|
-
|
|
122
|
+
/** Next cheap-state refresh; `null` for a resident that never goes stale (no routine). */
|
|
123
|
+
staleAt: number | null;
|
|
123
124
|
needs: Record<string, number>;
|
|
124
125
|
}[];
|
|
125
126
|
}
|
package/dist/sim/offScreen.js
CHANGED
|
@@ -89,7 +89,18 @@ export function createOffScreenWorld(options) {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
-
|
|
92
|
+
const next = resident.routine.nextChangeAfter(resident.updatedAt);
|
|
93
|
+
if (next > resident.updatedAt) {
|
|
94
|
+
resident.staleAt = next;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// A schedule that cannot name a later change must not spin this catch-up loop; hold the
|
|
98
|
+
// phase, decay to `now` and try once more a cycle later.
|
|
99
|
+
resident.needs = decayMeters(resident.needs, defs, now - resident.updatedAt);
|
|
100
|
+
resident.updatedAt = now;
|
|
101
|
+
resident.staleAt = now + resident.routine.cycleSeconds;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
/** Keep a near resident's routine phase current and walk it to the next place when the clock moves it. */
|
|
@@ -380,7 +391,7 @@ export function createOffScreenWorld(options) {
|
|
|
380
391
|
travelling: resident.travelling,
|
|
381
392
|
pinned: resident.pinned,
|
|
382
393
|
updatedAt: resident.updatedAt,
|
|
383
|
-
staleAt: resident.staleAt,
|
|
394
|
+
staleAt: Number.isFinite(resident.staleAt) ? resident.staleAt : null,
|
|
384
395
|
needs: { ...(live ?? resident.needs) },
|
|
385
396
|
};
|
|
386
397
|
});
|
|
@@ -418,7 +429,7 @@ export function createOffScreenWorld(options) {
|
|
|
418
429
|
travelling: saved.travelling,
|
|
419
430
|
pinned: saved.pinned,
|
|
420
431
|
updatedAt: saved.updatedAt,
|
|
421
|
-
staleAt: saved.staleAt,
|
|
432
|
+
staleAt: saved.staleAt === null ? Infinity : saved.staleAt,
|
|
422
433
|
needs: { ...saved.needs },
|
|
423
434
|
distance: Infinity,
|
|
424
435
|
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { Vec3 } from "../world/geometry.js";
|
|
2
|
+
/** One visitor on site: the id its body, its needs and its money are keyed by. */
|
|
3
|
+
export interface Visitor {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
/** Game-second it arrived; a restored visitor keeps the original. */
|
|
6
|
+
readonly admittedAt: number;
|
|
7
|
+
}
|
|
8
|
+
/** What a flow policy reads to decide how many arrive and when one has had enough. */
|
|
9
|
+
export interface VisitorFlow {
|
|
10
|
+
/** Visitors on site this tick — the member count, never a tally kept beside it. */
|
|
11
|
+
readonly onSite: number;
|
|
12
|
+
/** Most visitors the gates hold this tick, after resolving {@link VisitorPolicy.capacity}. */
|
|
13
|
+
readonly capacity: number;
|
|
14
|
+
/** Arrivals since {@link VisitorPopulation.rollDay}. */
|
|
15
|
+
readonly arrivalsToday: number;
|
|
16
|
+
readonly departuresToday: number;
|
|
17
|
+
readonly now: number;
|
|
18
|
+
}
|
|
19
|
+
/** The game's world behind every counted visitor: it creates and removes the body. */
|
|
20
|
+
export interface VisitorWorld {
|
|
21
|
+
/**
|
|
22
|
+
* Create one visitor's body and its game-side record, and return where the body now stands.
|
|
23
|
+
* `null` means the world refused it — no room, no walkable spot — and a refused body is never
|
|
24
|
+
* counted as on site or as an arrival.
|
|
25
|
+
*/
|
|
26
|
+
readonly admit: (visitor: Visitor) => Vec3 | null;
|
|
27
|
+
/** Remove one visitor's body and record. Called for every departure, refusal and reset. */
|
|
28
|
+
readonly release: (visitor: Visitor) => void;
|
|
29
|
+
/**
|
|
30
|
+
* True when the world currently holds a body for this id. `adopt` and `restore` refuse an id the
|
|
31
|
+
* world does not hold, so the count can never name a body that is not on the ground.
|
|
32
|
+
*/
|
|
33
|
+
readonly has: (id: string) => boolean;
|
|
34
|
+
}
|
|
35
|
+
/** The game's flow policy: what draws visitors in, how many fit, and when one has had enough. */
|
|
36
|
+
export interface VisitorPolicy {
|
|
37
|
+
/** Most visitors on site at once; a function lets the cap follow a rating, a level or a build. */
|
|
38
|
+
readonly capacity: number | ((now: number) => number);
|
|
39
|
+
/** Visitors arriving per second at this instant; `0` with the gates shut. */
|
|
40
|
+
readonly arrivalRate: (flow: VisitorFlow) => number;
|
|
41
|
+
/** True when this visitor should leave now. Omit for a population that only leaves when told. */
|
|
42
|
+
readonly shouldLeave?: (visitor: Visitor, flow: VisitorFlow) => boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Flow policy fields a scenario can change in play. */
|
|
45
|
+
export interface VisitorPolicyPatch {
|
|
46
|
+
readonly capacity?: number | ((now: number) => number);
|
|
47
|
+
readonly arrivalRate?: (flow: VisitorFlow) => number;
|
|
48
|
+
/** `null` removes the leaving rule, so visitors stay until {@link VisitorPopulation.leave}. */
|
|
49
|
+
readonly shouldLeave?: ((visitor: Visitor, flow: VisitorFlow) => boolean) | null;
|
|
50
|
+
readonly arrivalBudget?: number;
|
|
51
|
+
/** `null` counts every admitted body as on site. */
|
|
52
|
+
readonly inside?: ((position: Vec3) => boolean) | null;
|
|
53
|
+
}
|
|
54
|
+
/** Counters a HUD, a probe or a test reads. The on-site figure is the visitor list's length. */
|
|
55
|
+
export interface VisitorPopulationStats {
|
|
56
|
+
onSite: number;
|
|
57
|
+
capacity: number;
|
|
58
|
+
arrivalsToday: number;
|
|
59
|
+
departuresToday: number;
|
|
60
|
+
/** Highest on-site count since the last {@link VisitorPopulation.rollDay}. */
|
|
61
|
+
peakToday: number;
|
|
62
|
+
/** Bodies the world or the bounds check refused. Never counted as arrivals. */
|
|
63
|
+
refused: number;
|
|
64
|
+
/** Arrival credit not yet spent. */
|
|
65
|
+
pending: number;
|
|
66
|
+
}
|
|
67
|
+
/** How a body the game made itself joins the population. */
|
|
68
|
+
export interface VisitorAdoption {
|
|
69
|
+
/** Game-second it arrived; defaults to the current tick's clock. */
|
|
70
|
+
readonly admittedAt?: number;
|
|
71
|
+
/** True for a body that is a new arrival today; false for one a save or a seed restored. */
|
|
72
|
+
readonly arrival?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/** Serializable state. No on-site count is stored: it is the restored visitor list. */
|
|
75
|
+
export interface VisitorPopulationSnapshot {
|
|
76
|
+
visitors: {
|
|
77
|
+
id: string;
|
|
78
|
+
admittedAt: number;
|
|
79
|
+
}[];
|
|
80
|
+
arrivalsToday: number;
|
|
81
|
+
departuresToday: number;
|
|
82
|
+
peakToday: number;
|
|
83
|
+
pending: number;
|
|
84
|
+
/** Next id suffix, so a restored save does not reuse an id. */
|
|
85
|
+
sequence: number;
|
|
86
|
+
}
|
|
87
|
+
/** Options for {@link createVisitorPopulation}. */
|
|
88
|
+
export interface VisitorPopulationOptions {
|
|
89
|
+
readonly world: VisitorWorld;
|
|
90
|
+
readonly policy: VisitorPolicy;
|
|
91
|
+
/** First half of every generated id. Default `"visitor#"`. */
|
|
92
|
+
readonly idPrefix?: string;
|
|
93
|
+
/** First suffix used after `idPrefix`. Default 1. */
|
|
94
|
+
readonly sequenceStart?: number;
|
|
95
|
+
/** Most bodies one tick may admit, so a long frame cannot flood the park. Default 4. */
|
|
96
|
+
readonly arrivalBudget?: number;
|
|
97
|
+
/** A body must stand here to count as on site; omit to count every admitted body. */
|
|
98
|
+
readonly inside?: (position: Vec3) => boolean;
|
|
99
|
+
/** Register a devtools probe under this name so the overlay shows the crowd and the day. */
|
|
100
|
+
readonly devtoolsName?: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The crowd a scene reports. Every visitor is a member with a body the game's world made, so the
|
|
104
|
+
* on-site figure is the member count and the daily figure is the sum of admissions the world
|
|
105
|
+
* accepted — there is no second tally to drift from what is on the ground. Arrivals, capacity and
|
|
106
|
+
* leaving are the game's policy; ids, day rollover and counting are this seam's.
|
|
107
|
+
*/
|
|
108
|
+
export interface VisitorPopulation {
|
|
109
|
+
/** Run the flow for `dt` seconds: leaving first, then arrivals within the budget and the cap. */
|
|
110
|
+
tick(dt: number, now: number): void;
|
|
111
|
+
/** Visitors on site. Reads the member count — no scan of the world, safe for a per-frame HUD. */
|
|
112
|
+
onSite(): number;
|
|
113
|
+
arrivalsToday(): number;
|
|
114
|
+
departuresToday(): number;
|
|
115
|
+
peakToday(): number;
|
|
116
|
+
/** Ids on site, arrival order. Allocates — HUD lists and tests. */
|
|
117
|
+
ids(): readonly string[];
|
|
118
|
+
has(id: string): boolean;
|
|
119
|
+
visitor(id: string): Visitor | null;
|
|
120
|
+
/** Send one visitor home now (it reached the exit, or the game evicted it). */
|
|
121
|
+
leave(id: string): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Take over a body the game created itself — one restored from a save, seeded at park open, or
|
|
124
|
+
* scripted. It is on site from now on; pass `arrival: true` when it is also a new arrival today.
|
|
125
|
+
*/
|
|
126
|
+
adopt(id: string, adoption?: VisitorAdoption): boolean;
|
|
127
|
+
/** Close the day: arrivals, departures and peak reset. The crowd stays where it stands. */
|
|
128
|
+
rollDay(): void;
|
|
129
|
+
retune(patch: VisitorPolicyPatch): void;
|
|
130
|
+
stats(): VisitorPopulationStats;
|
|
131
|
+
snapshot(): VisitorPopulationSnapshot;
|
|
132
|
+
/**
|
|
133
|
+
* Replace the members with the saved ones. Only visitors the world already holds come back, so
|
|
134
|
+
* restore the bodies first (or `adopt` each one as it spawns); the rest count as refused.
|
|
135
|
+
*/
|
|
136
|
+
restore(next: VisitorPopulationSnapshot): void;
|
|
137
|
+
/** Drop the devtools probe registered by `devtoolsName`. Safe to call twice. */
|
|
138
|
+
dispose(): void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create the population a scene reports, with the game owning arrival and leaving policy. A
|
|
142
|
+
* visitor only exists once the world has made its body, so the HUD's count and the bodies on
|
|
143
|
+
* screen are the same list: `admit` returning `null` is a refused arrival, not a number.
|
|
144
|
+
*
|
|
145
|
+
* @capability visitor-population a crowd whose on-site and daily counts are the bodies the game made
|
|
146
|
+
*/
|
|
147
|
+
export declare function createVisitorPopulation(options: VisitorPopulationOptions): VisitorPopulation;
|