@game_engine/physics-adapter 0.1.0-alpha

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/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # @game_engine/physics-adapter
2
+
3
+ Engine-neutral physics adapter contracts for Agent Engine.
4
+
5
+ This package defines the boundary between Agent Engine ECS/source data and optional physics backends. It does not include a physics engine and must stay free of backend-specific dependencies.
6
+
7
+ Adapter inputs are plain entities with `Transform`, `Velocity`, `Collider`, `RigidBody`, and `PhysicsMaterial` components. Adapter outputs are deterministic structured snapshots, contacts, grounding hints, and diagnostics.
8
+
9
+ Use `comparePhysicsStepResults` when comparing optional backends. It summarizes body ids, static body positions, contact pairs, and diagnostic codes so tests can compare adapter health without requiring identical solver floating-point output.
10
+
11
+ The current Rapier vs Planck evaluation is documented in
12
+ [`docs/physics-adapter-evaluation.md`](../../docs/physics-adapter-evaluation.md).
13
+
14
+ Default headless runtime behavior is still owned by `@game_engine/core` and its deterministic `KinematicPhysics` placeholder. Real physics adapters must be explicitly constructed by tests or experiments.
@@ -0,0 +1,108 @@
1
+ import { EntityId, ComponentName, ComponentValue, Vector2, Transform, Velocity, Collider, RigidBody, PhysicsMaterial, World } from '@game_engine/core';
2
+
3
+ type PhysicsAdapterSeverity = "warning" | "error";
4
+ type PhysicsBodyType = "static" | "dynamic" | "kinematic";
5
+ interface PhysicsAdapterDiagnostic {
6
+ severity: PhysicsAdapterSeverity;
7
+ code: string;
8
+ message: string;
9
+ entityId?: EntityId;
10
+ path?: string;
11
+ suggestion?: string;
12
+ }
13
+ type PhysicsAdapterDiagnostics = PhysicsAdapterDiagnostic[];
14
+ interface PhysicsAdapterEntity {
15
+ id: EntityId;
16
+ components: Record<ComponentName, ComponentValue>;
17
+ }
18
+ interface PhysicsAdapterWorldInput {
19
+ entities: PhysicsAdapterEntity[];
20
+ }
21
+ interface PhysicsAdapterCreateOptions {
22
+ gravity?: Vector2;
23
+ }
24
+ interface PhysicsStepOptions {
25
+ frame?: number;
26
+ }
27
+ interface PhysicsContactSnapshot {
28
+ entityId: EntityId;
29
+ otherEntityId: EntityId;
30
+ grounded?: boolean;
31
+ }
32
+ interface PhysicsGroundingSnapshot {
33
+ grounded: boolean;
34
+ groundEntityId?: EntityId;
35
+ }
36
+ interface PhysicsBodySnapshot {
37
+ entityId: EntityId;
38
+ bodyType: PhysicsBodyType;
39
+ transform?: Pick<Transform, "position" | "rotation">;
40
+ velocity?: Velocity;
41
+ grounded?: PhysicsGroundingSnapshot;
42
+ contacts: PhysicsContactSnapshot[];
43
+ }
44
+ interface PhysicsStepResult {
45
+ ok: boolean;
46
+ frame: number;
47
+ deltaSeconds: number;
48
+ bodies: PhysicsBodySnapshot[];
49
+ contacts: PhysicsContactSnapshot[];
50
+ diagnostics: PhysicsAdapterDiagnostics;
51
+ }
52
+ interface PhysicsAdapterWorld {
53
+ step(deltaSeconds: number, options?: PhysicsStepOptions): PhysicsStepResult;
54
+ snapshot(options?: PhysicsStepOptions): PhysicsStepResult;
55
+ dispose?(): void;
56
+ }
57
+ interface PhysicsAdapter {
58
+ name: string;
59
+ createWorld(input: PhysicsAdapterWorldInput, options?: PhysicsAdapterCreateOptions): PhysicsAdapterWorld | Promise<PhysicsAdapterWorld>;
60
+ }
61
+ interface PhysicsComponents {
62
+ transform?: Transform;
63
+ velocity?: Velocity;
64
+ collider?: Collider;
65
+ rigidBody?: RigidBody;
66
+ physicsMaterial?: PhysicsMaterial;
67
+ }
68
+ interface PhysicsBackendComparisonInput {
69
+ backend: string;
70
+ result: PhysicsStepResult;
71
+ }
72
+ interface PhysicsStaticBodySummary {
73
+ entityId: EntityId;
74
+ position?: Vector2;
75
+ }
76
+ interface PhysicsBackendResultSummary {
77
+ backend: string;
78
+ ok: boolean;
79
+ bodyCount: number;
80
+ bodyIds: EntityId[];
81
+ staticBodies: PhysicsStaticBodySummary[];
82
+ diagnosticCodes: string[];
83
+ contactPairs: string[];
84
+ }
85
+ interface PhysicsBackendComparison {
86
+ summaries: PhysicsBackendResultSummary[];
87
+ sameBodyIds: boolean;
88
+ sameStaticBodyPositions: boolean;
89
+ diagnosticsShapeCompatible: boolean;
90
+ }
91
+ declare function createPhysicsAdapterInputFromWorld(world: World): PhysicsAdapterWorldInput;
92
+ declare function clonePhysicsAdapterInput(input: PhysicsAdapterWorldInput): PhysicsAdapterWorldInput;
93
+ declare function readPhysicsComponents(entity: PhysicsAdapterEntity): PhysicsComponents;
94
+ declare function resolvePhysicsBodyType(components: PhysicsComponents): PhysicsBodyType;
95
+ declare function sortPhysicsBodySnapshots(bodies: PhysicsBodySnapshot[]): PhysicsBodySnapshot[];
96
+ declare function sortPhysicsContacts(contacts: PhysicsContactSnapshot[]): PhysicsContactSnapshot[];
97
+ declare function createPhysicsStepResult(fields: Omit<PhysicsStepResult, "ok" | "bodies" | "contacts" | "diagnostics"> & {
98
+ bodies?: PhysicsBodySnapshot[];
99
+ contacts?: PhysicsContactSnapshot[];
100
+ diagnostics?: PhysicsAdapterDiagnostics;
101
+ }): PhysicsStepResult;
102
+ declare function createPhysicsDiagnostic(diagnostic: PhysicsAdapterDiagnostic): PhysicsAdapterDiagnostic;
103
+ declare function summarizePhysicsStepResult(backend: string, result: PhysicsStepResult): PhysicsBackendResultSummary;
104
+ declare function comparePhysicsStepResults(inputs: PhysicsBackendComparisonInput[]): PhysicsBackendComparison;
105
+ declare function roundDeterministic(value: number): number;
106
+ declare function roundVector2(value: Vector2): Vector2;
107
+
108
+ export { type PhysicsAdapter, type PhysicsAdapterCreateOptions, type PhysicsAdapterDiagnostic, type PhysicsAdapterDiagnostics, type PhysicsAdapterEntity, type PhysicsAdapterSeverity, type PhysicsAdapterWorld, type PhysicsAdapterWorldInput, type PhysicsBackendComparison, type PhysicsBackendComparisonInput, type PhysicsBackendResultSummary, type PhysicsBodySnapshot, type PhysicsBodyType, type PhysicsComponents, type PhysicsContactSnapshot, type PhysicsGroundingSnapshot, type PhysicsStaticBodySummary, type PhysicsStepOptions, type PhysicsStepResult, clonePhysicsAdapterInput, comparePhysicsStepResults, createPhysicsAdapterInputFromWorld, createPhysicsDiagnostic, createPhysicsStepResult, readPhysicsComponents, resolvePhysicsBodyType, roundDeterministic, roundVector2, sortPhysicsBodySnapshots, sortPhysicsContacts, summarizePhysicsStepResult };
package/dist/index.js ADDED
@@ -0,0 +1,204 @@
1
+ // src/index.ts
2
+ function createPhysicsAdapterInputFromWorld(world) {
3
+ return {
4
+ entities: world.listEntities().map((entity) => cloneAdapterEntity(entity))
5
+ };
6
+ }
7
+ function clonePhysicsAdapterInput(input) {
8
+ return {
9
+ entities: input.entities.map((entity) => cloneAdapterEntity(entity))
10
+ };
11
+ }
12
+ function readPhysicsComponents(entity) {
13
+ return {
14
+ transform: readTransform(entity.components.Transform),
15
+ velocity: readVelocity(entity.components.Velocity),
16
+ collider: readCollider(entity.components.Collider),
17
+ rigidBody: readRigidBody(entity.components.RigidBody),
18
+ physicsMaterial: readPhysicsMaterial(entity.components.PhysicsMaterial)
19
+ };
20
+ }
21
+ function resolvePhysicsBodyType(components) {
22
+ if (components.rigidBody) {
23
+ return components.rigidBody.type;
24
+ }
25
+ return components.collider?.static ? "static" : "dynamic";
26
+ }
27
+ function sortPhysicsBodySnapshots(bodies) {
28
+ return [...bodies].sort((left, right) => left.entityId.localeCompare(right.entityId));
29
+ }
30
+ function sortPhysicsContacts(contacts) {
31
+ return [...contacts].sort((left, right) => {
32
+ const entityOrder = left.entityId.localeCompare(right.entityId);
33
+ return entityOrder !== 0 ? entityOrder : left.otherEntityId.localeCompare(right.otherEntityId);
34
+ });
35
+ }
36
+ function createPhysicsStepResult(fields) {
37
+ const bodies = sortPhysicsBodySnapshots(fields.bodies ?? []);
38
+ const contacts = sortPhysicsContacts(fields.contacts ?? []);
39
+ const diagnostics = sortDiagnostics(fields.diagnostics ?? []);
40
+ return {
41
+ ok: !diagnostics.some((diagnostic) => diagnostic.severity === "error"),
42
+ frame: fields.frame,
43
+ deltaSeconds: roundDeterministic(fields.deltaSeconds),
44
+ bodies,
45
+ contacts,
46
+ diagnostics
47
+ };
48
+ }
49
+ function createPhysicsDiagnostic(diagnostic) {
50
+ return { ...diagnostic };
51
+ }
52
+ function summarizePhysicsStepResult(backend, result) {
53
+ return {
54
+ backend,
55
+ ok: result.ok,
56
+ bodyCount: result.bodies.length,
57
+ bodyIds: result.bodies.map((body) => body.entityId).sort(),
58
+ staticBodies: result.bodies.filter((body) => body.bodyType === "static").map((body) => ({
59
+ entityId: body.entityId,
60
+ ...body.transform?.position ? { position: roundVector2(body.transform.position) } : {}
61
+ })).sort((left, right) => left.entityId.localeCompare(right.entityId)),
62
+ diagnosticCodes: result.diagnostics.map((diagnostic) => diagnostic.code).sort(),
63
+ contactPairs: result.contacts.map((contact) => `${contact.entityId}->${contact.otherEntityId}`).sort()
64
+ };
65
+ }
66
+ function comparePhysicsStepResults(inputs) {
67
+ const summaries = inputs.map((input) => summarizePhysicsStepResult(input.backend, input.result)).sort((left, right) => left.backend.localeCompare(right.backend));
68
+ const [baseline] = summaries;
69
+ return {
70
+ summaries,
71
+ sameBodyIds: baseline ? summaries.every((summary) => arraysEqual(summary.bodyIds, baseline.bodyIds)) : true,
72
+ sameStaticBodyPositions: baseline ? summaries.every((summary) => staticBodySummariesEqual(summary.staticBodies, baseline.staticBodies)) : true,
73
+ diagnosticsShapeCompatible: baseline ? summaries.every((summary) => arraysEqual(summary.diagnosticCodes, baseline.diagnosticCodes)) : true
74
+ };
75
+ }
76
+ function roundDeterministic(value) {
77
+ const rounded = Number(value.toFixed(12));
78
+ const nearestInteger = Math.round(rounded);
79
+ return Math.abs(rounded - nearestInteger) < 1e-9 ? nearestInteger : rounded;
80
+ }
81
+ function roundVector2(value) {
82
+ return [roundDeterministic(value[0]), roundDeterministic(value[1])];
83
+ }
84
+ function cloneAdapterEntity(entity) {
85
+ return {
86
+ id: entity.id,
87
+ components: cloneComponents(entity.components)
88
+ };
89
+ }
90
+ function cloneComponents(components) {
91
+ return Object.fromEntries(Object.entries(components).map(([name, value]) => [name, cloneValue(value)]));
92
+ }
93
+ function cloneValue(value) {
94
+ if (value === void 0) {
95
+ return void 0;
96
+ }
97
+ return JSON.parse(JSON.stringify(value));
98
+ }
99
+ function sortDiagnostics(diagnostics) {
100
+ return [...diagnostics].sort((left, right) => {
101
+ const entityOrder = (left.entityId ?? "").localeCompare(right.entityId ?? "");
102
+ if (entityOrder !== 0) {
103
+ return entityOrder;
104
+ }
105
+ const pathOrder = (left.path ?? "").localeCompare(right.path ?? "");
106
+ return pathOrder !== 0 ? pathOrder : left.code.localeCompare(right.code);
107
+ });
108
+ }
109
+ function readTransform(value) {
110
+ if (!isRecord(value)) {
111
+ return void 0;
112
+ }
113
+ const position = readVector2(value.position);
114
+ const scale = readVector2(value.scale);
115
+ const rotation = typeof value.rotation === "number" && Number.isFinite(value.rotation) ? value.rotation : void 0;
116
+ return position && scale && rotation !== void 0 ? {
117
+ position,
118
+ rotation,
119
+ scale
120
+ } : void 0;
121
+ }
122
+ function readVelocity(value) {
123
+ if (!isRecord(value)) {
124
+ return void 0;
125
+ }
126
+ const linear = readVector2(value.linear);
127
+ return linear ? { linear } : void 0;
128
+ }
129
+ function readCollider(value) {
130
+ if (!isRecord(value) || value.shape !== "box" || typeof value.static !== "boolean") {
131
+ return void 0;
132
+ }
133
+ const size = readVector2(value.size);
134
+ if (!size) {
135
+ return void 0;
136
+ }
137
+ return {
138
+ shape: "box",
139
+ size,
140
+ static: value.static,
141
+ ...typeof value.sensor === "boolean" ? { sensor: value.sensor } : {}
142
+ };
143
+ }
144
+ function readRigidBody(value) {
145
+ if (!isRecord(value) || !["static", "dynamic", "kinematic"].includes(String(value.type))) {
146
+ return void 0;
147
+ }
148
+ return {
149
+ type: value.type,
150
+ ...typeof value.gravityScale === "number" ? { gravityScale: value.gravityScale } : {},
151
+ ...typeof value.lockedRotation === "boolean" ? { lockedRotation: value.lockedRotation } : {}
152
+ };
153
+ }
154
+ function readPhysicsMaterial(value) {
155
+ if (!isRecord(value) || typeof value.friction !== "number" || typeof value.restitution !== "number") {
156
+ return void 0;
157
+ }
158
+ return {
159
+ friction: value.friction,
160
+ restitution: value.restitution
161
+ };
162
+ }
163
+ function readVector2(value) {
164
+ if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "number" || typeof value[1] !== "number" || !Number.isFinite(value[0]) || !Number.isFinite(value[1])) {
165
+ return void 0;
166
+ }
167
+ return [value[0], value[1]];
168
+ }
169
+ function isRecord(value) {
170
+ return typeof value === "object" && value !== null && !Array.isArray(value);
171
+ }
172
+ function arraysEqual(left, right) {
173
+ return left.length === right.length && left.every((value, index) => value === right[index]);
174
+ }
175
+ function staticBodySummariesEqual(left, right) {
176
+ if (left.length !== right.length) {
177
+ return false;
178
+ }
179
+ return left.every((leftSummary, index) => {
180
+ const rightSummary = right[index];
181
+ if (!rightSummary || leftSummary.entityId !== rightSummary.entityId) {
182
+ return false;
183
+ }
184
+ if (!leftSummary.position || !rightSummary.position) {
185
+ return leftSummary.position === rightSummary.position;
186
+ }
187
+ return leftSummary.position[0] === rightSummary.position[0] && leftSummary.position[1] === rightSummary.position[1];
188
+ });
189
+ }
190
+ export {
191
+ clonePhysicsAdapterInput,
192
+ comparePhysicsStepResults,
193
+ createPhysicsAdapterInputFromWorld,
194
+ createPhysicsDiagnostic,
195
+ createPhysicsStepResult,
196
+ readPhysicsComponents,
197
+ resolvePhysicsBodyType,
198
+ roundDeterministic,
199
+ roundVector2,
200
+ sortPhysicsBodySnapshots,
201
+ sortPhysicsContacts,
202
+ summarizePhysicsStepResult
203
+ };
204
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n Collider,\n ComponentName,\n ComponentValue,\n EntityId,\n EntityRecord,\n PhysicsMaterial,\n RigidBody,\n Transform,\n Vector2,\n Velocity,\n World\n} from \"@game_engine/core\";\n\nexport type PhysicsAdapterSeverity = \"warning\" | \"error\";\nexport type PhysicsBodyType = \"static\" | \"dynamic\" | \"kinematic\";\n\nexport interface PhysicsAdapterDiagnostic {\n severity: PhysicsAdapterSeverity;\n code: string;\n message: string;\n entityId?: EntityId;\n path?: string;\n suggestion?: string;\n}\n\nexport type PhysicsAdapterDiagnostics = PhysicsAdapterDiagnostic[];\n\nexport interface PhysicsAdapterEntity {\n id: EntityId;\n components: Record<ComponentName, ComponentValue>;\n}\n\nexport interface PhysicsAdapterWorldInput {\n entities: PhysicsAdapterEntity[];\n}\n\nexport interface PhysicsAdapterCreateOptions {\n gravity?: Vector2;\n}\n\nexport interface PhysicsStepOptions {\n frame?: number;\n}\n\nexport interface PhysicsContactSnapshot {\n entityId: EntityId;\n otherEntityId: EntityId;\n grounded?: boolean;\n}\n\nexport interface PhysicsGroundingSnapshot {\n grounded: boolean;\n groundEntityId?: EntityId;\n}\n\nexport interface PhysicsBodySnapshot {\n entityId: EntityId;\n bodyType: PhysicsBodyType;\n transform?: Pick<Transform, \"position\" | \"rotation\">;\n velocity?: Velocity;\n grounded?: PhysicsGroundingSnapshot;\n contacts: PhysicsContactSnapshot[];\n}\n\nexport interface PhysicsStepResult {\n ok: boolean;\n frame: number;\n deltaSeconds: number;\n bodies: PhysicsBodySnapshot[];\n contacts: PhysicsContactSnapshot[];\n diagnostics: PhysicsAdapterDiagnostics;\n}\n\nexport interface PhysicsAdapterWorld {\n step(deltaSeconds: number, options?: PhysicsStepOptions): PhysicsStepResult;\n snapshot(options?: PhysicsStepOptions): PhysicsStepResult;\n dispose?(): void;\n}\n\nexport interface PhysicsAdapter {\n name: string;\n createWorld(\n input: PhysicsAdapterWorldInput,\n options?: PhysicsAdapterCreateOptions\n ): PhysicsAdapterWorld | Promise<PhysicsAdapterWorld>;\n}\n\nexport interface PhysicsComponents {\n transform?: Transform;\n velocity?: Velocity;\n collider?: Collider;\n rigidBody?: RigidBody;\n physicsMaterial?: PhysicsMaterial;\n}\n\nexport interface PhysicsBackendComparisonInput {\n backend: string;\n result: PhysicsStepResult;\n}\n\nexport interface PhysicsStaticBodySummary {\n entityId: EntityId;\n position?: Vector2;\n}\n\nexport interface PhysicsBackendResultSummary {\n backend: string;\n ok: boolean;\n bodyCount: number;\n bodyIds: EntityId[];\n staticBodies: PhysicsStaticBodySummary[];\n diagnosticCodes: string[];\n contactPairs: string[];\n}\n\nexport interface PhysicsBackendComparison {\n summaries: PhysicsBackendResultSummary[];\n sameBodyIds: boolean;\n sameStaticBodyPositions: boolean;\n diagnosticsShapeCompatible: boolean;\n}\n\nexport function createPhysicsAdapterInputFromWorld(world: World): PhysicsAdapterWorldInput {\n return {\n entities: world.listEntities().map((entity) => cloneAdapterEntity(entity))\n };\n}\n\nexport function clonePhysicsAdapterInput(input: PhysicsAdapterWorldInput): PhysicsAdapterWorldInput {\n return {\n entities: input.entities.map((entity) => cloneAdapterEntity(entity))\n };\n}\n\nexport function readPhysicsComponents(entity: PhysicsAdapterEntity): PhysicsComponents {\n return {\n transform: readTransform(entity.components.Transform),\n velocity: readVelocity(entity.components.Velocity),\n collider: readCollider(entity.components.Collider),\n rigidBody: readRigidBody(entity.components.RigidBody),\n physicsMaterial: readPhysicsMaterial(entity.components.PhysicsMaterial)\n };\n}\n\nexport function resolvePhysicsBodyType(components: PhysicsComponents): PhysicsBodyType {\n if (components.rigidBody) {\n return components.rigidBody.type;\n }\n\n return components.collider?.static ? \"static\" : \"dynamic\";\n}\n\nexport function sortPhysicsBodySnapshots(bodies: PhysicsBodySnapshot[]): PhysicsBodySnapshot[] {\n return [...bodies].sort((left, right) => left.entityId.localeCompare(right.entityId));\n}\n\nexport function sortPhysicsContacts(contacts: PhysicsContactSnapshot[]): PhysicsContactSnapshot[] {\n return [...contacts].sort((left, right) => {\n const entityOrder = left.entityId.localeCompare(right.entityId);\n return entityOrder !== 0 ? entityOrder : left.otherEntityId.localeCompare(right.otherEntityId);\n });\n}\n\nexport function createPhysicsStepResult(\n fields: Omit<PhysicsStepResult, \"ok\" | \"bodies\" | \"contacts\" | \"diagnostics\"> & {\n bodies?: PhysicsBodySnapshot[];\n contacts?: PhysicsContactSnapshot[];\n diagnostics?: PhysicsAdapterDiagnostics;\n }\n): PhysicsStepResult {\n const bodies = sortPhysicsBodySnapshots(fields.bodies ?? []);\n const contacts = sortPhysicsContacts(fields.contacts ?? []);\n const diagnostics = sortDiagnostics(fields.diagnostics ?? []);\n\n return {\n ok: !diagnostics.some((diagnostic) => diagnostic.severity === \"error\"),\n frame: fields.frame,\n deltaSeconds: roundDeterministic(fields.deltaSeconds),\n bodies,\n contacts,\n diagnostics\n };\n}\n\nexport function createPhysicsDiagnostic(\n diagnostic: PhysicsAdapterDiagnostic\n): PhysicsAdapterDiagnostic {\n return { ...diagnostic };\n}\n\nexport function summarizePhysicsStepResult(\n backend: string,\n result: PhysicsStepResult\n): PhysicsBackendResultSummary {\n return {\n backend,\n ok: result.ok,\n bodyCount: result.bodies.length,\n bodyIds: result.bodies.map((body) => body.entityId).sort(),\n staticBodies: result.bodies\n .filter((body) => body.bodyType === \"static\")\n .map((body) => ({\n entityId: body.entityId,\n ...(body.transform?.position ? { position: roundVector2(body.transform.position) } : {})\n }))\n .sort((left, right) => left.entityId.localeCompare(right.entityId)),\n diagnosticCodes: result.diagnostics.map((diagnostic) => diagnostic.code).sort(),\n contactPairs: result.contacts\n .map((contact) => `${contact.entityId}->${contact.otherEntityId}`)\n .sort()\n };\n}\n\nexport function comparePhysicsStepResults(\n inputs: PhysicsBackendComparisonInput[]\n): PhysicsBackendComparison {\n const summaries = inputs\n .map((input) => summarizePhysicsStepResult(input.backend, input.result))\n .sort((left, right) => left.backend.localeCompare(right.backend));\n const [baseline] = summaries;\n\n return {\n summaries,\n sameBodyIds: baseline ? summaries.every((summary) => arraysEqual(summary.bodyIds, baseline.bodyIds)) : true,\n sameStaticBodyPositions: baseline\n ? summaries.every((summary) => staticBodySummariesEqual(summary.staticBodies, baseline.staticBodies))\n : true,\n diagnosticsShapeCompatible: baseline\n ? summaries.every((summary) => arraysEqual(summary.diagnosticCodes, baseline.diagnosticCodes))\n : true\n };\n}\n\nexport function roundDeterministic(value: number): number {\n const rounded = Number(value.toFixed(12));\n const nearestInteger = Math.round(rounded);\n return Math.abs(rounded - nearestInteger) < 1e-9 ? nearestInteger : rounded;\n}\n\nexport function roundVector2(value: Vector2): Vector2 {\n return [roundDeterministic(value[0]), roundDeterministic(value[1])];\n}\n\nfunction cloneAdapterEntity(entity: EntityRecord | PhysicsAdapterEntity): PhysicsAdapterEntity {\n return {\n id: entity.id,\n components: cloneComponents(entity.components)\n };\n}\n\nfunction cloneComponents(components: Record<ComponentName, ComponentValue>): Record<ComponentName, ComponentValue> {\n return Object.fromEntries(Object.entries(components).map(([name, value]) => [name, cloneValue(value)]));\n}\n\nfunction cloneValue(value: ComponentValue): ComponentValue {\n if (value === undefined) {\n return undefined;\n }\n\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction sortDiagnostics(diagnostics: PhysicsAdapterDiagnostics): PhysicsAdapterDiagnostics {\n return [...diagnostics].sort((left, right) => {\n const entityOrder = (left.entityId ?? \"\").localeCompare(right.entityId ?? \"\");\n if (entityOrder !== 0) {\n return entityOrder;\n }\n\n const pathOrder = (left.path ?? \"\").localeCompare(right.path ?? \"\");\n return pathOrder !== 0 ? pathOrder : left.code.localeCompare(right.code);\n });\n}\n\nfunction readTransform(value: ComponentValue): Transform | undefined {\n if (!isRecord(value)) {\n return undefined;\n }\n\n const position = readVector2(value.position);\n const scale = readVector2(value.scale);\n const rotation = typeof value.rotation === \"number\" && Number.isFinite(value.rotation) ? value.rotation : undefined;\n\n return position && scale && rotation !== undefined\n ? {\n position,\n rotation,\n scale\n }\n : undefined;\n}\n\nfunction readVelocity(value: ComponentValue): Velocity | undefined {\n if (!isRecord(value)) {\n return undefined;\n }\n\n const linear = readVector2(value.linear);\n return linear ? { linear } : undefined;\n}\n\nfunction readCollider(value: ComponentValue): Collider | undefined {\n if (!isRecord(value) || value.shape !== \"box\" || typeof value.static !== \"boolean\") {\n return undefined;\n }\n\n const size = readVector2(value.size);\n if (!size) {\n return undefined;\n }\n\n return {\n shape: \"box\",\n size,\n static: value.static,\n ...(typeof value.sensor === \"boolean\" ? { sensor: value.sensor } : {})\n };\n}\n\nfunction readRigidBody(value: ComponentValue): RigidBody | undefined {\n if (!isRecord(value) || ![\"static\", \"dynamic\", \"kinematic\"].includes(String(value.type))) {\n return undefined;\n }\n\n return {\n type: value.type as RigidBody[\"type\"],\n ...(typeof value.gravityScale === \"number\" ? { gravityScale: value.gravityScale } : {}),\n ...(typeof value.lockedRotation === \"boolean\" ? { lockedRotation: value.lockedRotation } : {})\n };\n}\n\nfunction readPhysicsMaterial(value: ComponentValue): PhysicsMaterial | undefined {\n if (!isRecord(value) || typeof value.friction !== \"number\" || typeof value.restitution !== \"number\") {\n return undefined;\n }\n\n return {\n friction: value.friction,\n restitution: value.restitution\n };\n}\n\nfunction readVector2(value: unknown): Vector2 | undefined {\n if (\n !Array.isArray(value) ||\n value.length !== 2 ||\n typeof value[0] !== \"number\" ||\n typeof value[1] !== \"number\" ||\n !Number.isFinite(value[0]) ||\n !Number.isFinite(value[1])\n ) {\n return undefined;\n }\n\n return [value[0], value[1]];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction arraysEqual(left: readonly string[], right: readonly string[]): boolean {\n return left.length === right.length && left.every((value, index) => value === right[index]);\n}\n\nfunction staticBodySummariesEqual(\n left: readonly PhysicsStaticBodySummary[],\n right: readonly PhysicsStaticBodySummary[]\n): boolean {\n if (left.length !== right.length) {\n return false;\n }\n\n return left.every((leftSummary, index) => {\n const rightSummary = right[index];\n if (!rightSummary || leftSummary.entityId !== rightSummary.entityId) {\n return false;\n }\n\n if (!leftSummary.position || !rightSummary.position) {\n return leftSummary.position === rightSummary.position;\n }\n\n return leftSummary.position[0] === rightSummary.position[0] && leftSummary.position[1] === rightSummary.position[1];\n });\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@game_engine/physics-adapter",
3
+ "version": "0.1.0-alpha",
4
+ "description": "Backend-neutral physics adapter contracts for Agent Engine experiments.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/rktkdduq01/game_engine.git",
9
+ "directory": "packages/physics-adapter"
10
+ },
11
+ "keywords": [
12
+ "agent-engine",
13
+ "game-engine",
14
+ "ai-agent",
15
+ "physics",
16
+ "adapter"
17
+ ],
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "build": "tsup src/index.ts --format esm --dts --sourcemap --clean"
35
+ },
36
+ "dependencies": {
37
+ "@game_engine/core": "workspace:*"
38
+ },
39
+ "devDependencies": {
40
+ "tsup": "^8.3.5",
41
+ "typescript": "^5.7.2",
42
+ "vitest": "^2.1.8"
43
+ }
44
+ }