@genex-ai/cli-demo 0.80.0-dev.211 → 0.80.2-dev.213
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/dist/index.js
CHANGED
|
@@ -1109,7 +1109,7 @@ async function writeProject(meta, cwd = process.cwd()) {
|
|
|
1109
1109
|
import fs7 from "fs/promises";
|
|
1110
1110
|
import path7 from "path";
|
|
1111
1111
|
var DEFAULT_DASHBOARD_ORIGIN = new URL(DEFAULT_AUTH_URL).origin;
|
|
1112
|
-
function renderGenexConfig() {
|
|
1112
|
+
function renderGenexConfig(slug) {
|
|
1113
1113
|
return `// src/genex.config.ts \u2014 written by \`genex init\`. DO NOT hardcode URLs here.
|
|
1114
1114
|
//
|
|
1115
1115
|
// Build once, run anywhere. The games host injects the serving environment into
|
|
@@ -1117,14 +1117,21 @@ function renderGenexConfig() {
|
|
|
1117
1117
|
// bundle), so ONE build is correct on every stand \u2014 promoting a game is a copy,
|
|
1118
1118
|
// never a rebuild, and an immutable bundle can never pin the wrong environment.
|
|
1119
1119
|
// Vite env still overrides, but is loaded ONLY by \`npm run dev\`:
|
|
1120
|
-
// .env -> VITE_GENEX_SLUG (
|
|
1120
|
+
// .env -> VITE_GENEX_SLUG (local override for \`npm run dev\`)
|
|
1121
1121
|
// .env.development.local -> local-stack URL overrides (dev mode ONLY; gitignored)
|
|
1122
|
+
//
|
|
1123
|
+
// The slug is ALSO baked as a literal below, on purpose: \`genex preview/publish\`
|
|
1124
|
+
// excludes .env from the source push (it's treated as a secret file), so a clone of
|
|
1125
|
+
// the published repo has no .env. Reading the slug from env ALONE would then build
|
|
1126
|
+
// \`slug: undefined\` -> the guest-session call posts {} -> 400 -> guests can't play a
|
|
1127
|
+
// remixed game. The literal keeps a bare clone rebuilding into a working game; the
|
|
1128
|
+
// env var still wins locally.
|
|
1122
1129
|
const injected: { apiUrl?: string; dashboardOrigins?: string[] } =
|
|
1123
1130
|
(typeof window !== "undefined" && (window as { __GENEX__?: unknown }).__GENEX__) as
|
|
1124
1131
|
| { apiUrl?: string; dashboardOrigins?: string[] }
|
|
1125
1132
|
| undefined ?? {};
|
|
1126
1133
|
export const GENEX = {
|
|
1127
|
-
slug: import.meta.env.VITE_GENEX_SLUG as string,
|
|
1134
|
+
slug: (import.meta.env.VITE_GENEX_SLUG as string | undefined) ?? ${JSON.stringify(slug)},
|
|
1128
1135
|
apiUrl:
|
|
1129
1136
|
(import.meta.env.VITE_GENEX_API_URL as string | undefined) ??
|
|
1130
1137
|
injected.apiUrl ??
|
|
@@ -1170,7 +1177,7 @@ async function writeIfAbsent(file, content, log) {
|
|
|
1170
1177
|
}
|
|
1171
1178
|
async function writeGameConfigFiles(meta, log, cwd = process.cwd()) {
|
|
1172
1179
|
await fs7.mkdir(path7.join(cwd, "src"), { recursive: true });
|
|
1173
|
-
await writeIfAbsent(path7.join(cwd, "src", "genex.config.ts"), renderGenexConfig(), log);
|
|
1180
|
+
await writeIfAbsent(path7.join(cwd, "src", "genex.config.ts"), renderGenexConfig(meta.slug), log);
|
|
1174
1181
|
await writeIfAbsent(path7.join(cwd, ".env"), renderSlugEnv(meta.slug), log);
|
|
1175
1182
|
const overrides = renderDevOverrides(meta);
|
|
1176
1183
|
if (overrides) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "0.80.
|
|
3
|
+
"version": "0.80.2-dev.213",
|
|
4
4
|
"description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -88,6 +88,36 @@ type ResolvedMovementInput = {
|
|
|
88
88
|
/** Crouch input interpretation — see {@link CharacterControllerOptions.crouchMode}. */
|
|
89
89
|
export type CrouchMode = "toggle" | "hold";
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* The over-the-network snapshot of a character. Publish it every fixed tick with
|
|
93
|
+
* `room.me.set(character.netState())`; apply a remote's smoothed copy with
|
|
94
|
+
* `applyNetState(remoteObject, players.get(id).state)`.
|
|
95
|
+
*
|
|
96
|
+
* `y` is the SETTLED body height while grounded: the float-spring's idle up/down
|
|
97
|
+
* oscillation is removed, so a standing remote player does NOT bob in place.
|
|
98
|
+
* Publish this — never `currPos`, which is the raw physics capsule and bobs on
|
|
99
|
+
* its suspension spring. Numbers are rounded to ~cm; rotation is a 4-number
|
|
100
|
+
* quaternion (never a scalar yaw — that lerps the long way across ±π). The six
|
|
101
|
+
* booleans drive a remote's CharacterAnimations.
|
|
102
|
+
*/
|
|
103
|
+
export interface NetState {
|
|
104
|
+
x: number;
|
|
105
|
+
y: number;
|
|
106
|
+
z: number;
|
|
107
|
+
q: [number, number, number, number];
|
|
108
|
+
onGround: boolean;
|
|
109
|
+
falling: boolean;
|
|
110
|
+
moving: boolean;
|
|
111
|
+
running: boolean;
|
|
112
|
+
jumping: boolean;
|
|
113
|
+
crouching: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Round a networked number to ~cm precision (raw floats serialize as 17-digit JSON). */
|
|
117
|
+
function roundNet(v: number): number {
|
|
118
|
+
return Math.round(v * 100) / 100;
|
|
119
|
+
}
|
|
120
|
+
|
|
91
121
|
export interface CharacterRecoveryEvent {
|
|
92
122
|
reason: "non-finite" | "out-of-bounds";
|
|
93
123
|
position: { x: number; y: number; z: number };
|
|
@@ -484,6 +514,8 @@ export class CharacterController {
|
|
|
484
514
|
private slideFrictionCoef = 0;
|
|
485
515
|
private standingPointFriction = 0;
|
|
486
516
|
private readonly standingPoint = new THREE.Vector3();
|
|
517
|
+
/** Scratch for {@link netPos} — the float-oscillation-free networked position. */
|
|
518
|
+
private readonly _netPos = new THREE.Vector3();
|
|
487
519
|
private readonly characterMassImpulse = new THREE.Vector3();
|
|
488
520
|
private readonly movingObjectPosition = new THREE.Vector3();
|
|
489
521
|
private readonly movingObjectVelocity = new THREE.Vector3();
|
|
@@ -648,6 +680,29 @@ export class CharacterController {
|
|
|
648
680
|
get currPos(): THREE.Vector3 {
|
|
649
681
|
return this.currentPos;
|
|
650
682
|
}
|
|
683
|
+
/**
|
|
684
|
+
* Body position to PUBLISH over the network. Identical to {@link currPos} while
|
|
685
|
+
* airborne (jump/fall arcs must survive), but while grounded the up-axis
|
|
686
|
+
* component is snapped to the SETTLED float height above the ground — removing
|
|
687
|
+
* the suspension-spring idle oscillation that otherwise makes a standing remote
|
|
688
|
+
* player bob up and down. Publish this (via {@link netState}), never `currPos`.
|
|
689
|
+
* Live vector — copy it if you keep it. Follows custom gravity (up = bodyYAxis).
|
|
690
|
+
*/
|
|
691
|
+
get netPos(): THREE.Vector3 {
|
|
692
|
+
this._netPos.copy(this.currentPos);
|
|
693
|
+
if (this._isOnGround) {
|
|
694
|
+
// currPos bobs because the float spring holds the capsule a VARYING gap
|
|
695
|
+
// above the ground each step. standingPoint is the STATIC ground contact;
|
|
696
|
+
// the settled center→ground gap (along up) is groundFloatingDistance −
|
|
697
|
+
// rayOriginOffset. Shift the up component by (settled − current) so the
|
|
698
|
+
// published height is the settled one, not the oscillating one.
|
|
699
|
+
const gapNow =
|
|
700
|
+
this._netPos.dot(this.characterYAxis) - this.standingPoint.dot(this.characterYAxis);
|
|
701
|
+
const gapSettled = this.groundFloatingDistance - this.rayOriginOffset;
|
|
702
|
+
this._netPos.addScaledVector(this.characterYAxis, gapSettled - gapNow);
|
|
703
|
+
}
|
|
704
|
+
return this._netPos;
|
|
705
|
+
}
|
|
651
706
|
/** Body rotation (this step). Live quaternion. */
|
|
652
707
|
get currQuat(): THREE.Quaternion {
|
|
653
708
|
return this.currentQuat;
|
|
@@ -799,6 +854,32 @@ export class CharacterController {
|
|
|
799
854
|
// Public methods
|
|
800
855
|
// ────────────────────────────────────────────────────────────────────────
|
|
801
856
|
|
|
857
|
+
/**
|
|
858
|
+
* The snapshot to PUBLISH for multiplayer, in one call:
|
|
859
|
+
* `room.me.set(character.netState())` — on the fixed 10–20 Hz tick, never per
|
|
860
|
+
* frame. Position is {@link netPos} (float-oscillation-free, so remotes never
|
|
861
|
+
* bob), rotation is a 4-number quaternion, plus the six animation booleans a
|
|
862
|
+
* remote feeds to its own CharacterAnimations. Numbers are rounded to ~cm to
|
|
863
|
+
* keep the wire small. This is the ONLY position you should network — reaching
|
|
864
|
+
* for `currPos` here is the classic "standing remote player bobs" bug.
|
|
865
|
+
*/
|
|
866
|
+
netState(): NetState {
|
|
867
|
+
const p = this.netPos;
|
|
868
|
+
const q = this.currentQuat;
|
|
869
|
+
return {
|
|
870
|
+
x: roundNet(p.x),
|
|
871
|
+
y: roundNet(p.y),
|
|
872
|
+
z: roundNet(p.z),
|
|
873
|
+
q: [roundNet(q.x), roundNet(q.y), roundNet(q.z), roundNet(q.w)],
|
|
874
|
+
onGround: this.isOnGround,
|
|
875
|
+
falling: this.isFalling,
|
|
876
|
+
moving: this.isMoving,
|
|
877
|
+
running: this.runActive,
|
|
878
|
+
jumping: this.jumpActive,
|
|
879
|
+
crouching: this.crouchActive,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
802
883
|
/**
|
|
803
884
|
* Merge movement intents into the input state. Only fields you pass are
|
|
804
885
|
* changed, so different input sources (keyboard, joystick, buttons) can each
|
|
@@ -1927,3 +2008,19 @@ export class CharacterController {
|
|
|
1927
2008
|
d.velocityArrow.setLength(this._relativeVel.length() / this.targetMoveSpeed(this._runActive));
|
|
1928
2009
|
}
|
|
1929
2010
|
}
|
|
2011
|
+
|
|
2012
|
+
/**
|
|
2013
|
+
* Apply a remote player's networked snapshot to their VISUAL object (a plain
|
|
2014
|
+
* Object3D — never a CharacterController; remotes are interpolated visuals, not
|
|
2015
|
+
* simulated). Read the SMOOTHED copy the SDK exposes so motion glides:
|
|
2016
|
+
* `applyNetState(remoteObject, players.get(id).state)`. The six booleans
|
|
2017
|
+
* (`s.onGround`, `s.falling`, …) feed that remote's CharacterAnimations
|
|
2018
|
+
* separately — see the character-controller skill's multiplayer rule.
|
|
2019
|
+
*/
|
|
2020
|
+
export function applyNetState(target: THREE.Object3D, s: NetState): void {
|
|
2021
|
+
target.position.set(s.x, s.y, s.z);
|
|
2022
|
+
// normalize: netState() rounds each quaternion component to ~cm precision, so the 4-tuple is
|
|
2023
|
+
// almost never exactly unit — Three.js bakes a non-unit quaternion straight into the matrix as a
|
|
2024
|
+
// small (~0.4%) scale that shifts as the remote turns. One call keeps remotes at true scale.
|
|
2025
|
+
target.quaternion.set(s.q[0], s.q[1], s.q[2], s.q[3]).normalize();
|
|
2026
|
+
}
|
|
@@ -318,11 +318,16 @@ it. Simulating remote players' physics locally guarantees divergence — every
|
|
|
318
318
|
client would compute a different world. Never render every remote with your
|
|
319
319
|
own avatar file: players picked their looks, show them.
|
|
320
320
|
|
|
321
|
-
- Publish
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
`
|
|
325
|
-
|
|
321
|
+
- **Publish `character.netState()`** on the fixed 10–20 Hz tick (never per frame):
|
|
322
|
+
`room.me.set(character.netState())`. It bundles the network-safe position, a four-number
|
|
323
|
+
quaternion, and the six animation booleans in one call. It uses `netPos`, **not** `currPos` —
|
|
324
|
+
`currPos` is the raw physics capsule whose Y bobs on the float-suspension spring, so publishing
|
|
325
|
+
it makes a *standing* remote player visibly bob up and down. `netState()` snaps the grounded Y to
|
|
326
|
+
the settled height (raw while airborne, so jumps still arc). Never reduce rotation to a scalar yaw.
|
|
327
|
+
- On each remote, apply the SDK's **smoothed** copy with
|
|
328
|
+
`applyNetState(remoteObject, players.get(id).state)` (sets position + rotation), and feed the same
|
|
329
|
+
six booleans (`onGround`, `falling`, `moving`, `running`, `jumping`, `crouching`) to that remote's
|
|
330
|
+
`CharacterAnimations` — see the animations reference. Never publish `currPos` directly.
|
|
326
331
|
- Only the owning client runs `MotionActionDriver`. Remotes play the same
|
|
327
332
|
one-shot event while following smoothed owner-authored position/rotation;
|
|
328
333
|
their animation mixer never moves them through the world.
|
|
@@ -620,6 +620,12 @@ than silently disappearing. The budget math that matters:
|
|
|
620
620
|
`players.get(id).state` directly (already smoothed); every object from `objects.get(id).state`.
|
|
621
621
|
4. **Create-or-reuse one mesh per id**; remove a player's mesh on `'leave'`.
|
|
622
622
|
|
|
623
|
+
> **Physics character?** If your player is the `genex controller character` capsule, do NOT
|
|
624
|
+
> hand-build the state from the body position — publish `room.me.set(character.netState())` and
|
|
625
|
+
> apply remotes with `applyNetState(mesh, players.get(id).state)`. The controller's raw `currPos.y`
|
|
626
|
+
> bobs on its float-suspension spring; `netState()` publishes a settled ground Y so a standing remote
|
|
627
|
+
> doesn't bob. See `$genex-threejs-character-controller` → "Multiplayer rule".
|
|
628
|
+
|
|
623
629
|
## Rotation: sync a quaternion, not an angle
|
|
624
630
|
|
|
625
631
|
Send rotation as a 4-number quaternion `q: mesh.quaternion.toArray()`; on the remote do
|