@jgengine/react 0.5.0 → 0.6.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 +24 -0
- package/dist/hooks.d.ts +4 -0
- package/dist/hooks.js +5 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/liveBind.d.ts +20 -0
- package/dist/liveBind.js +38 -0
- package/package.json +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,30 @@ the latest and surface the migration steps.
|
|
|
15
15
|
|
|
16
16
|
_Nothing yet._
|
|
17
17
|
|
|
18
|
+
## 0.6.0
|
|
19
|
+
|
|
20
|
+
An additive release: **every 0.5.0 API is unchanged**, so upgrading is only a
|
|
21
|
+
version bump. The headline is a whole outdoor-world layer — composable
|
|
22
|
+
`environment(...)` descriptors, renderer-free query primitives so gameplay reads
|
|
23
|
+
the same world the shell renders, a standalone rigid-body physics sim, and the
|
|
24
|
+
`@jgengine/shell` renderers that draw it all.
|
|
25
|
+
|
|
26
|
+
### Migrate
|
|
27
|
+
|
|
28
|
+
- Bump every `@jgengine/*` dependency to `^0.6.0` (the eight packages version in lockstep).
|
|
29
|
+
- No code change is required — 0.6.0 adds surface, it doesn't move or remove any.
|
|
30
|
+
- Optional: describe an outdoor scene once with `environment({ terrain, weather, vegetation, water, structures })` from `@jgengine/core/world/features`. `@jgengine/shell`'s `EnvironmentScene` renders it (terrain, rain/snow, grass, ocean, buildings), and gameplay reads the same world through the renderer-free primitives — `resolveTerrainField(...)` for ground-snap/collision, `windField(...)` for sway/sailing, `waterSurface(...)` for buoyancy, `scatter(...)` for prop/spawn placement, `buildingIndex(...)` for placement avoidance — no three.js needed.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `@jgengine/core/world` query primitives — pure, renderer-free world sampling so gameplay and rendering read one source of truth. `world/terrain` (`TerrainField`, `noiseField`, `resolveTerrainField`, `resolveGroundStep` for slope-limited movement), `world/wind` (`windField` — one wind source for weather sway, grass, sailing, fire spread), `world/water` (`waterSurface` / `waterSurfaceFromDescriptor` — CPU Gerstner matching the ocean shader, for buoyancy and shorelines), `world/scatter` (`scatter` — seeded, overlap-aware point distribution for vegetation/props/spawns), `world/buildings` (`generateBuilding`, `generateBuildingDistrict`) and `world/buildingIndex` (`buildingIndex` — `at`/`within`/`nearest`/`isInside`/`blockers` over a district).
|
|
35
|
+
- `@jgengine/core/world/features` composable outdoor descriptors — `environment(...)` plus `terrain()`, `rain()`, `snow()`, `grass()`, `ocean()`, `building()`.
|
|
36
|
+
- `@jgengine/core/world/regions` (`createRegionField` — blend content-agnostic biomes by nearest selector, adding `tint`/`water`/`fog`/`speedMultiplier`/opaque `data`; extends `TerrainField` so it ground-snaps too) and `@jgengine/core/world/scatterItems` (`scatterItems` — region-driven content scatter: density per region, grounded, above-water/slope-aware, with `pickWeighted` for weighted rolls).
|
|
37
|
+
- `@jgengine/core/physics/physicsWorld` — standalone fixed-capacity rigid-body sim (SoA buffers, spatial-hash broadphase, sleeping) for many colliding dynamic bodies (piles, debris, stress scenes). Distinct from the `defineGame` `physics: { gravity }` character-controller config.
|
|
38
|
+
- `ctx.time` simulation clock (`@jgengine/core/time/simClock`) — `onTick`'s `dt` is now **game time**, so `rate * dt` decay/regen/AI obeys pause and fast-forward for free without per-system wiring. Configure with `defineGame({ time: { scale?, speeds?, dayLength?, start?, startPaused? } })` (all optional; default is real-time 1:1). `ctx.time` exposes `pause`/`play`/`toggle`/`setSpeed`/`cycleSpeed` + `snapshot()`/`calendar()`; `useGameClock()` binds it in React.
|
|
39
|
+
- `@jgengine/shell` environment/terrain/water/weather/structures renderers — `EnvironmentScene` mounts an `environment()` descriptor as R3F renderers; `GrassField`, `ProceduralGround`, `Ocean`, `RainField`, `SnowField`, `LightningStrike`, `GeneratedBuilding`, and `world/InstancedBodies` (renders `PhysicsWorld` bodies).
|
|
40
|
+
- `@jgengine/react/liveBind` — `useFrameBind` drives a DOM/SVG element from a per-frame value without re-rendering React (HUDs bound to live engine state never re-render or lag).
|
|
41
|
+
|
|
18
42
|
## 0.5.0
|
|
19
43
|
|
|
20
44
|
An additive release: **every 0.4.0 API is unchanged**, so upgrading is only a
|
package/dist/hooks.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { InventorySlot } from "@jgengine/core/inventory/inventoryModel";
|
|
|
8
8
|
import type { StatValue } from "@jgengine/core/scene/entityStats";
|
|
9
9
|
import type { SceneEntity } from "@jgengine/core/scene/entityStore";
|
|
10
10
|
import type { SceneObject } from "@jgengine/core/scene/objectStore";
|
|
11
|
+
import type { ClockSnapshot, SimClock } from "@jgengine/core/time/simClock";
|
|
11
12
|
import { type PositionedPrompt } from "@jgengine/core/interaction/proximityPrompt";
|
|
12
13
|
export declare function useGameStore<T>(selector: (ctx: GameContext) => T): T;
|
|
13
14
|
export declare function useGame(): {
|
|
@@ -41,4 +42,7 @@ export declare function useLeaderboard(stat: string, options: {
|
|
|
41
42
|
}[];
|
|
42
43
|
export declare function useLocalPlayerDead(healthStatId?: string): boolean;
|
|
43
44
|
export declare function localPlayerEntity(ctx: GameContext): SceneEntity | null;
|
|
45
|
+
export declare function useGameClock(): ClockSnapshot & {
|
|
46
|
+
controls: SimClock;
|
|
47
|
+
};
|
|
44
48
|
export declare function useActivePrompt<T extends PositionedPrompt>(prompts?: readonly T[]): T | null;
|
package/dist/hooks.js
CHANGED
|
@@ -64,6 +64,11 @@ export function localPlayerEntity(ctx) {
|
|
|
64
64
|
ctx.scene.entity.list().find((entity) => entity.role === "player") ??
|
|
65
65
|
null);
|
|
66
66
|
}
|
|
67
|
+
export function useGameClock() {
|
|
68
|
+
const ctx = useGameContext();
|
|
69
|
+
useSyncExternalStore(ctx.subscribe, ctx.version, ctx.version);
|
|
70
|
+
return { ...ctx.time.snapshot(), controls: ctx.time };
|
|
71
|
+
}
|
|
67
72
|
export function useActivePrompt(prompts) {
|
|
68
73
|
return useGameStore((ctx) => {
|
|
69
74
|
if (prompts === undefined || prompts.length === 0)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Drive a DOM/SVG element from a per-frame value without re-rendering React.
|
|
4
|
+
* Runs one requestAnimationFrame loop, reads `get()` each frame, and calls
|
|
5
|
+
* `apply(value, element)` so HUDs bound to live engine state (speed, pose)
|
|
6
|
+
* never re-render and never lag. `get`/`apply` may change without restarting.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useFrameBind<T, E extends Element = Element>(ref: {
|
|
9
|
+
current: E | null;
|
|
10
|
+
}, get: () => T, apply: (value: T, element: E) => void): void;
|
|
11
|
+
/**
|
|
12
|
+
* A `<span>` whose text tracks a live value every frame (the 90% case of
|
|
13
|
+
* useFrameBind). `<LiveText get={() => groundSpeed(car) * KMH} format={Math.round} />`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function LiveText({ get, format, className, style, }: {
|
|
16
|
+
get: () => number | string;
|
|
17
|
+
format?: (value: number | string) => string;
|
|
18
|
+
className?: string;
|
|
19
|
+
style?: CSSProperties;
|
|
20
|
+
}): import("react").JSX.Element;
|
package/dist/liveBind.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
/**
|
|
4
|
+
* Drive a DOM/SVG element from a per-frame value without re-rendering React.
|
|
5
|
+
* Runs one requestAnimationFrame loop, reads `get()` each frame, and calls
|
|
6
|
+
* `apply(value, element)` so HUDs bound to live engine state (speed, pose)
|
|
7
|
+
* never re-render and never lag. `get`/`apply` may change without restarting.
|
|
8
|
+
*/
|
|
9
|
+
export function useFrameBind(ref, get, apply) {
|
|
10
|
+
const getRef = useRef(get);
|
|
11
|
+
getRef.current = get;
|
|
12
|
+
const applyRef = useRef(apply);
|
|
13
|
+
applyRef.current = apply;
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
let raf = 0;
|
|
16
|
+
const tick = () => {
|
|
17
|
+
const element = ref.current;
|
|
18
|
+
if (element !== null)
|
|
19
|
+
applyRef.current(getRef.current(), element);
|
|
20
|
+
raf = requestAnimationFrame(tick);
|
|
21
|
+
};
|
|
22
|
+
raf = requestAnimationFrame(tick);
|
|
23
|
+
return () => cancelAnimationFrame(raf);
|
|
24
|
+
}, [ref]);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A `<span>` whose text tracks a live value every frame (the 90% case of
|
|
28
|
+
* useFrameBind). `<LiveText get={() => groundSpeed(car) * KMH} format={Math.round} />`.
|
|
29
|
+
*/
|
|
30
|
+
export function LiveText({ get, format, className, style, }) {
|
|
31
|
+
const ref = useRef(null);
|
|
32
|
+
useFrameBind(ref, get, (value, element) => {
|
|
33
|
+
const text = format !== undefined ? format(value) : String(value);
|
|
34
|
+
if (element.textContent !== text)
|
|
35
|
+
element.textContent = text;
|
|
36
|
+
});
|
|
37
|
+
return _jsx("span", { ref: ref, className: className, style: style });
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "React UI layer for JGengine: GameProvider, hooks, and headless primitives over @jgengine/core.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"CHANGELOG.md"
|
|
16
16
|
],
|
|
17
17
|
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
18
22
|
"./*": {
|
|
19
23
|
"types": "./dist/*.d.ts",
|
|
20
24
|
"default": "./dist/*.js"
|
|
@@ -25,7 +29,7 @@
|
|
|
25
29
|
"check-types": "tsgo --noEmit -p tsconfig.json"
|
|
26
30
|
},
|
|
27
31
|
"dependencies": {
|
|
28
|
-
"@jgengine/core": "^0.
|
|
32
|
+
"@jgengine/core": "^0.6.0"
|
|
29
33
|
},
|
|
30
34
|
"peerDependencies": {
|
|
31
35
|
"react": "^19.0.0"
|