@holoscript/plugin-civil-engineering 2.0.1 → 2.0.2

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/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@holoscript/plugin-civil-engineering",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "main": "src/index.ts",
5
5
  "peerDependencies": {
6
- "@holoscript/core": "8.0.6"
6
+ "@holoscript/core": ">=8.0.0"
7
7
  },
8
8
  "license": "MIT",
9
9
  "scripts": {
10
10
  "test": "vitest run --passWithNoTests",
11
11
  "test:coverage": "vitest run --coverage --passWithNoTests"
12
12
  }
13
- }
13
+ }
@@ -18,9 +18,9 @@ import {
18
18
  } from '../frame2d';
19
19
 
20
20
  // ─── Steel W-section defaults used across tests ────────────────────────────────
21
- const E_GPa = 200; // GPa (structural steel)
22
- const I_m4 = 1e-4; // m⁴ (moderate W-section)
23
- const A_m2 = 5e-3; // m² (moderate W-section area)
21
+ const E_GPa = 200; // GPa (structural steel)
22
+ const I_m4 = 1e-4; // m⁴ (moderate W-section)
23
+ const A_m2 = 5e-3; // m² (moderate W-section area)
24
24
 
25
25
  // ─── Simply-supported beam ─────────────────────────────────────────────────────
26
26
 
@@ -47,15 +47,29 @@ describe('simply-supported beam under point load', () => {
47
47
  { id: 'B', x: 6, y: 0 },
48
48
  ],
49
49
  elements: [
50
- { id: 'e1', fromNodeId: 'A', toNodeId: 'M', elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2 },
51
- { id: 'e2', fromNodeId: 'M', toNodeId: 'B', elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2 },
50
+ {
51
+ id: 'e1',
52
+ fromNodeId: 'A',
53
+ toNodeId: 'M',
54
+ elasticModulusGPa: E_GPa,
55
+ momentOfInertiaM4: I_m4,
56
+ areaM2: A_m2,
57
+ },
58
+ {
59
+ id: 'e2',
60
+ fromNodeId: 'M',
61
+ toNodeId: 'B',
62
+ elasticModulusGPa: E_GPa,
63
+ momentOfInertiaM4: I_m4,
64
+ areaM2: A_m2,
65
+ },
52
66
  ],
53
67
  supports: [
54
- { nodeId: 'A', ux: true, uy: true }, // pin
55
- { nodeId: 'B', uy: true }, // roller
68
+ { nodeId: 'A', ux: true, uy: true }, // pin
69
+ { nodeId: 'B', uy: true }, // roller
56
70
  ],
57
71
  nodalLoads: [
58
- { nodeId: 'M', Fy: -P }, // downward
72
+ { nodeId: 'M', Fy: -P }, // downward
59
73
  ],
60
74
  };
61
75
 
@@ -107,14 +121,19 @@ describe('cantilever beam under tip load', () => {
107
121
  { id: 'B', x: L, y: 0 },
108
122
  ],
109
123
  elements: [
110
- { id: 'e1', fromNodeId: 'A', toNodeId: 'B', elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2 },
124
+ {
125
+ id: 'e1',
126
+ fromNodeId: 'A',
127
+ toNodeId: 'B',
128
+ elasticModulusGPa: E_GPa,
129
+ momentOfInertiaM4: I_m4,
130
+ areaM2: A_m2,
131
+ },
111
132
  ],
112
133
  supports: [
113
134
  { nodeId: 'A', ux: true, uy: true, theta: true }, // fixed
114
135
  ],
115
- nodalLoads: [
116
- { nodeId: 'B', Fy: -P },
117
- ],
136
+ nodalLoads: [{ nodeId: 'B', Fy: -P }],
118
137
  };
119
138
 
120
139
  it('tip deflection matches closed-form PL³/(3EI) within 1%', () => {
@@ -158,14 +177,17 @@ describe('inclined element — coordinate transformation', () => {
158
177
  { id: 'B', x: 3, y: 4 },
159
178
  ],
160
179
  elements: [
161
- { id: 'e1', fromNodeId: 'A', toNodeId: 'B', elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2 },
162
- ],
163
- supports: [
164
- { nodeId: 'A', ux: true, uy: true, theta: true },
165
- ],
166
- nodalLoads: [
167
- { nodeId: 'B', Fx: 100 },
180
+ {
181
+ id: 'e1',
182
+ fromNodeId: 'A',
183
+ toNodeId: 'B',
184
+ elasticModulusGPa: E_GPa,
185
+ momentOfInertiaM4: I_m4,
186
+ areaM2: A_m2,
187
+ },
168
188
  ],
189
+ supports: [{ nodeId: 'A', ux: true, uy: true, theta: true }],
190
+ nodalLoads: [{ nodeId: 'B', Fx: 100 }],
169
191
  };
170
192
 
171
193
  it('converges for inclined element', () => {
@@ -203,8 +225,22 @@ describe('simply-supported beam under UDL', () => {
203
225
  { id: 'B', x: 6, y: 0 },
204
226
  ],
205
227
  elements: [
206
- { id: 'e1', fromNodeId: 'A', toNodeId: 'M', elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2 },
207
- { id: 'e2', fromNodeId: 'M', toNodeId: 'B', elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2 },
228
+ {
229
+ id: 'e1',
230
+ fromNodeId: 'A',
231
+ toNodeId: 'M',
232
+ elasticModulusGPa: E_GPa,
233
+ momentOfInertiaM4: I_m4,
234
+ areaM2: A_m2,
235
+ },
236
+ {
237
+ id: 'e2',
238
+ fromNodeId: 'M',
239
+ toNodeId: 'B',
240
+ elasticModulusGPa: E_GPa,
241
+ momentOfInertiaM4: I_m4,
242
+ areaM2: A_m2,
243
+ },
208
244
  ],
209
245
  supports: [
210
246
  { nodeId: 'A', ux: true, uy: true },
@@ -240,8 +276,20 @@ describe('validateFrame2DModel', () => {
240
276
  it('returns valid for a well-formed model', () => {
241
277
  const model: Frame2DModel = {
242
278
  id: 'valid',
243
- nodes: [{ id: 'A', x: 0, y: 0 }, { id: 'B', x: 5, y: 0 }],
244
- elements: [{ id: 'e1', fromNodeId: 'A', toNodeId: 'B', elasticModulusGPa: 200, momentOfInertiaM4: 1e-4, areaM2: 5e-3 }],
279
+ nodes: [
280
+ { id: 'A', x: 0, y: 0 },
281
+ { id: 'B', x: 5, y: 0 },
282
+ ],
283
+ elements: [
284
+ {
285
+ id: 'e1',
286
+ fromNodeId: 'A',
287
+ toNodeId: 'B',
288
+ elasticModulusGPa: 200,
289
+ momentOfInertiaM4: 1e-4,
290
+ areaM2: 5e-3,
291
+ },
292
+ ],
245
293
  supports: [{ nodeId: 'A', ux: true, uy: true, theta: true }],
246
294
  };
247
295
  const v = validateFrame2DModel(model);
@@ -253,7 +301,16 @@ describe('validateFrame2DModel', () => {
253
301
  const model: Frame2DModel = {
254
302
  id: 'bad-elem',
255
303
  nodes: [{ id: 'A', x: 0, y: 0 }],
256
- elements: [{ id: 'e1', fromNodeId: 'A', toNodeId: 'MISSING', elasticModulusGPa: 200, momentOfInertiaM4: 1e-4, areaM2: 5e-3 }],
304
+ elements: [
305
+ {
306
+ id: 'e1',
307
+ fromNodeId: 'A',
308
+ toNodeId: 'MISSING',
309
+ elasticModulusGPa: 200,
310
+ momentOfInertiaM4: 1e-4,
311
+ areaM2: 5e-3,
312
+ },
313
+ ],
257
314
  supports: [{ nodeId: 'A', ux: true, uy: true, theta: true }],
258
315
  };
259
316
  const v = validateFrame2DModel(model);
@@ -264,8 +321,20 @@ describe('validateFrame2DModel', () => {
264
321
  it('errors when fewer than 3 DOF are restrained', () => {
265
322
  const model: Frame2DModel = {
266
323
  id: 'unstable',
267
- nodes: [{ id: 'A', x: 0, y: 0 }, { id: 'B', x: 5, y: 0 }],
268
- elements: [{ id: 'e1', fromNodeId: 'A', toNodeId: 'B', elasticModulusGPa: 200, momentOfInertiaM4: 1e-4, areaM2: 5e-3 }],
324
+ nodes: [
325
+ { id: 'A', x: 0, y: 0 },
326
+ { id: 'B', x: 5, y: 0 },
327
+ ],
328
+ elements: [
329
+ {
330
+ id: 'e1',
331
+ fromNodeId: 'A',
332
+ toNodeId: 'B',
333
+ elasticModulusGPa: 200,
334
+ momentOfInertiaM4: 1e-4,
335
+ areaM2: 5e-3,
336
+ },
337
+ ],
269
338
  supports: [{ nodeId: 'A', ux: true }], // only 1 DOF
270
339
  };
271
340
  const v = validateFrame2DModel(model);
@@ -276,8 +345,20 @@ describe('validateFrame2DModel', () => {
276
345
  it('solver throws for invalid model', () => {
277
346
  const model: Frame2DModel = {
278
347
  id: 'invalid',
279
- nodes: [{ id: 'A', x: 0, y: 0 }, { id: 'B', x: 5, y: 0 }],
280
- elements: [{ id: 'e1', fromNodeId: 'A', toNodeId: 'B', elasticModulusGPa: 200, momentOfInertiaM4: 1e-4, areaM2: 5e-3 }],
348
+ nodes: [
349
+ { id: 'A', x: 0, y: 0 },
350
+ { id: 'B', x: 5, y: 0 },
351
+ ],
352
+ elements: [
353
+ {
354
+ id: 'e1',
355
+ fromNodeId: 'A',
356
+ toNodeId: 'B',
357
+ elasticModulusGPa: 200,
358
+ momentOfInertiaM4: 1e-4,
359
+ areaM2: 5e-3,
360
+ },
361
+ ],
281
362
  supports: [], // no supports
282
363
  };
283
364
  expect(() => solveFrame2D(model)).toThrow();
@@ -295,14 +376,17 @@ describe('buildFrame2DReceipt', () => {
295
376
  ],
296
377
  elements: [
297
378
  {
298
- id: 'e1', fromNodeId: 'A', toNodeId: 'B',
299
- elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2,
300
- plasticModulusM3: 5e-4, yieldStrengthMPa: 250,
379
+ id: 'e1',
380
+ fromNodeId: 'A',
381
+ toNodeId: 'B',
382
+ elasticModulusGPa: E_GPa,
383
+ momentOfInertiaM4: I_m4,
384
+ areaM2: A_m2,
385
+ plasticModulusM3: 5e-4,
386
+ yieldStrengthMPa: 250,
301
387
  },
302
388
  ],
303
- supports: [
304
- { nodeId: 'A', ux: true, uy: true, theta: true },
305
- ],
389
+ supports: [{ nodeId: 'A', ux: true, uy: true, theta: true }],
306
390
  nodalLoads: [{ nodeId: 'B', Fy: -10 }],
307
391
  };
308
392
 
@@ -59,7 +59,14 @@ const CANTILEVER: Frame2DModel = {
59
59
  { id: 'B', x: L, y: 0 },
60
60
  ],
61
61
  elements: [
62
- { id: 'e1', fromNodeId: 'A', toNodeId: 'B', elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2 },
62
+ {
63
+ id: 'e1',
64
+ fromNodeId: 'A',
65
+ toNodeId: 'B',
66
+ elasticModulusGPa: E_GPa,
67
+ momentOfInertiaM4: I_m4,
68
+ areaM2: A_m2,
69
+ },
63
70
  ],
64
71
  supports: [
65
72
  { nodeId: 'A', ux: true, uy: true, theta: true }, // fixed
@@ -77,7 +84,14 @@ const UNDER_CONSTRAINED: Frame2DModel = {
77
84
  { id: 'B', x: L, y: 0 },
78
85
  ],
79
86
  elements: [
80
- { id: 'e1', fromNodeId: 'A', toNodeId: 'B', elasticModulusGPa: E_GPa, momentOfInertiaM4: I_m4, areaM2: A_m2 },
87
+ {
88
+ id: 'e1',
89
+ fromNodeId: 'A',
90
+ toNodeId: 'B',
91
+ elasticModulusGPa: E_GPa,
92
+ momentOfInertiaM4: I_m4,
93
+ areaM2: A_m2,
94
+ },
81
95
  ],
82
96
  supports: [{ nodeId: 'A', ux: true }], // only 1 DOF restrained
83
97
  nodalLoads: [{ nodeId: 'B', Fy: -P }],
@@ -119,7 +133,12 @@ describe('civil-engineering -> HoloScript runtime integration (dsm_frame_2d)', (
119
133
 
120
134
  // Hand-checked against global static equilibrium of the cantilever:
121
135
  // Ry_A = +P = +50 kN ; |Mz_A| = P·L = 200 kN·m ; Rx_A = 0.
122
- const reactions = summary.reactions as Array<{ nodeId: string; Rx: number; Ry: number; Mz: number }>;
136
+ const reactions = summary.reactions as Array<{
137
+ nodeId: string;
138
+ Rx: number;
139
+ Ry: number;
140
+ Mz: number;
141
+ }>;
123
142
  const rA = reactions.find((r) => r.nodeId === 'A')!;
124
143
  expect(rA.Rx).toBeCloseTo(0, 1);
125
144
  expect(rA.Ry).toBeCloseTo(EXPECTED_RY, 1); // +50 kN
@@ -165,7 +184,11 @@ describe('civil-engineering -> HoloScript runtime integration (dsm_frame_2d)', (
165
184
 
166
185
  const state = runtime.getState() as Record<string, unknown>;
167
186
  const persisted = state['dsm_frame_2d:frame'] as
168
- | { converged?: boolean; nodeCount?: number; reactions?: Array<{ nodeId: string; Ry: number }> }
187
+ | {
188
+ converged?: boolean;
189
+ nodeCount?: number;
190
+ reactions?: Array<{ nodeId: string; Ry: number }>;
191
+ }
169
192
  | undefined;
170
193
  expect(persisted).toBeDefined();
171
194
  expect(persisted?.converged).toBe(true);
package/src/frame2d.ts CHANGED
@@ -241,10 +241,12 @@ export function validateFrame2DModel(model: Frame2DModel): Frame2DValidation {
241
241
  // Minimum stability check: need enough supports to prevent rigid body motion
242
242
  const totalRestrainedDOF = model.supports.reduce(
243
243
  (sum, s) => sum + (s.ux ? 1 : 0) + (s.uy ? 1 : 0) + (s.theta ? 1 : 0),
244
- 0,
244
+ 0
245
245
  );
246
246
  if (totalRestrainedDOF < 3) {
247
- errors.push('insufficient supports: need at least 3 restrained DOF to prevent rigid body motion');
247
+ errors.push(
248
+ 'insufficient supports: need at least 3 restrained DOF to prevent rigid body motion'
249
+ );
248
250
  }
249
251
 
250
252
  return { valid: errors.length === 0, errors, warnings };
@@ -264,9 +266,7 @@ function matMul(A: Matrix, B: Matrix): Matrix {
264
266
  const k = B.length;
265
267
  const C = zeros(m, n);
266
268
  for (let i = 0; i < m; i++)
267
- for (let j = 0; j < n; j++)
268
- for (let l = 0; l < k; l++)
269
- C[i][j] += A[i][l] * B[l][j];
269
+ for (let j = 0; j < n; j++) for (let l = 0; l < k; l++) C[i][j] += A[i][l] * B[l][j];
270
270
  return C;
271
271
  }
272
272
 
@@ -274,9 +274,7 @@ function transpose(A: Matrix): Matrix {
274
274
  const m = A.length;
275
275
  const n = A[0].length;
276
276
  const T = zeros(n, m);
277
- for (let i = 0; i < m; i++)
278
- for (let j = 0; j < n; j++)
279
- T[j][i] = A[i][j];
277
+ for (let i = 0; i < m; i++) for (let j = 0; j < n; j++) T[j][i] = A[i][j];
280
278
  return T;
281
279
  }
282
280
 
@@ -292,9 +290,13 @@ function gaussSolve(A: Matrix, b: number[]): number[] {
292
290
  let bestAbs = Math.abs(a[pivot][pivot]);
293
291
  for (let row = pivot + 1; row < n; row++) {
294
292
  const v = Math.abs(a[row][pivot]);
295
- if (v > bestAbs) { bestAbs = v; bestRow = row; }
293
+ if (v > bestAbs) {
294
+ bestAbs = v;
295
+ bestRow = row;
296
+ }
296
297
  }
297
- if (bestAbs < 1e-14) throw new Error('[frame2d] singular stiffness matrix — check boundary conditions');
298
+ if (bestAbs < 1e-14)
299
+ throw new Error('[frame2d] singular stiffness matrix — check boundary conditions');
298
300
  if (bestRow !== pivot) {
299
301
  [a[pivot], a[bestRow]] = [a[bestRow], a[pivot]];
300
302
  [rhs[pivot], rhs[bestRow]] = [rhs[bestRow], rhs[pivot]];
@@ -333,12 +335,12 @@ function localStiffness(E: number, I: number, A: number, L: number): Matrix {
333
335
  const L3 = L * L * L;
334
336
 
335
337
  return [
336
- [ EA/L, 0, 0, -EA/L, 0, 0 ],
337
- [ 0, 12*EI/L3, 6*EI/L2, 0, -12*EI/L3, 6*EI/L2 ],
338
- [ 0, 6*EI/L2, 4*EI/L, 0, -6*EI/L2, 2*EI/L ],
339
- [-EA/L, 0, 0, EA/L, 0, 0 ],
340
- [ 0, -12*EI/L3, -6*EI/L2, 0, 12*EI/L3, -6*EI/L2 ],
341
- [ 0, 6*EI/L2, 2*EI/L, 0, -6*EI/L2, 4*EI/L ],
338
+ [EA / L, 0, 0, -EA / L, 0, 0],
339
+ [0, (12 * EI) / L3, (6 * EI) / L2, 0, (-12 * EI) / L3, (6 * EI) / L2],
340
+ [0, (6 * EI) / L2, (4 * EI) / L, 0, (-6 * EI) / L2, (2 * EI) / L],
341
+ [-EA / L, 0, 0, EA / L, 0, 0],
342
+ [0, (-12 * EI) / L3, (-6 * EI) / L2, 0, (12 * EI) / L3, (-6 * EI) / L2],
343
+ [0, (6 * EI) / L2, (2 * EI) / L, 0, (-6 * EI) / L2, (4 * EI) / L],
342
344
  ];
343
345
  }
344
346
 
@@ -349,12 +351,12 @@ function localStiffness(E: number, I: number, A: number, L: number): Matrix {
349
351
  */
350
352
  function transformMatrix(c: number, s: number): Matrix {
351
353
  return [
352
- [ c, s, 0, 0, 0, 0 ],
353
- [-s, c, 0, 0, 0, 0 ],
354
- [ 0, 0, 1, 0, 0, 0 ],
355
- [ 0, 0, 0, c, s, 0 ],
356
- [ 0, 0, 0, -s, c, 0 ],
357
- [ 0, 0, 0, 0, 0, 1 ],
354
+ [c, s, 0, 0, 0, 0],
355
+ [-s, c, 0, 0, 0, 0],
356
+ [0, 0, 1, 0, 0, 0],
357
+ [0, 0, 0, c, s, 0],
358
+ [0, 0, 0, -s, c, 0],
359
+ [0, 0, 0, 0, 0, 1],
358
360
  ];
359
361
  }
360
362
 
@@ -389,7 +391,7 @@ function equivalentNodalForces(
389
391
  dl: DistributedLoad,
390
392
  elem: BeamElement,
391
393
  fromNode: Node2D,
392
- toNode: Node2D,
394
+ toNode: Node2D
393
395
  ): number[] /* 6-element global force vector */ {
394
396
  const dx = toNode.x - fromNode.x;
395
397
  const dy = toNode.y - fromNode.y;
@@ -429,14 +431,12 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
429
431
  const K = zeros(nDOF, nDOF);
430
432
  for (const elem of model.elements) {
431
433
  const fromNode = nodeById.get(elem.fromNodeId)!;
432
- const toNode = nodeById.get(elem.toNodeId)!;
434
+ const toNode = nodeById.get(elem.toNodeId)!;
433
435
  const Ke = globalElementStiffness(elem, fromNode, toNode);
434
436
  const i0 = nodeIndex.get(elem.fromNodeId)! * 3;
435
437
  const i1 = nodeIndex.get(elem.toNodeId)! * 3;
436
- const dofs = [i0, i0+1, i0+2, i1, i1+1, i1+2];
437
- for (let r = 0; r < 6; r++)
438
- for (let c_ = 0; c_ < 6; c_++)
439
- K[dofs[r]][dofs[c_]] += Ke[r][c_];
438
+ const dofs = [i0, i0 + 1, i0 + 2, i1, i1 + 1, i1 + 2];
439
+ for (let r = 0; r < 6; r++) for (let c_ = 0; c_ < 6; c_++) K[dofs[r]][dofs[c_]] += Ke[r][c_];
440
440
  }
441
441
 
442
442
  // Assemble global force vector
@@ -444,7 +444,7 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
444
444
 
445
445
  for (const load of model.nodalLoads ?? []) {
446
446
  const idx = nodeIndex.get(load.nodeId)! * 3;
447
- F[idx] += load.Fx ?? 0;
447
+ F[idx] += load.Fx ?? 0;
448
448
  F[idx + 1] += load.Fy ?? 0;
449
449
  F[idx + 2] += load.Mz ?? 0;
450
450
  }
@@ -452,11 +452,11 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
452
452
  for (const dl of model.distributedLoads ?? []) {
453
453
  const elem = elemById.get(dl.elementId)!;
454
454
  const fromNode = nodeById.get(elem.fromNodeId)!;
455
- const toNode = nodeById.get(elem.toNodeId)!;
455
+ const toNode = nodeById.get(elem.toNodeId)!;
456
456
  const f_global = equivalentNodalForces(dl, elem, fromNode, toNode);
457
457
  const i0 = nodeIndex.get(elem.fromNodeId)! * 3;
458
458
  const i1 = nodeIndex.get(elem.toNodeId)! * 3;
459
- const dofs = [i0, i0+1, i0+2, i1, i1+1, i1+2];
459
+ const dofs = [i0, i0 + 1, i0 + 2, i1, i1 + 1, i1 + 2];
460
460
  for (let r = 0; r < 6; r++) F[dofs[r]] += f_global[r];
461
461
  }
462
462
 
@@ -466,9 +466,18 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
466
466
  const restraints: boolean[] = new Array<boolean>(nDOF).fill(false);
467
467
  for (const support of model.supports) {
468
468
  const idx = nodeIndex.get(support.nodeId)! * 3;
469
- if (support.ux) { K[idx][idx] += PENALTY; restraints[idx] = true; }
470
- if (support.uy) { K[idx+1][idx+1] += PENALTY; restraints[idx+1] = true; }
471
- if (support.theta) { K[idx+2][idx+2] += PENALTY; restraints[idx+2] = true; }
469
+ if (support.ux) {
470
+ K[idx][idx] += PENALTY;
471
+ restraints[idx] = true;
472
+ }
473
+ if (support.uy) {
474
+ K[idx + 1][idx + 1] += PENALTY;
475
+ restraints[idx + 1] = true;
476
+ }
477
+ if (support.theta) {
478
+ K[idx + 2][idx + 2] += PENALTY;
479
+ restraints[idx + 2] = true;
480
+ }
472
481
  }
473
482
 
474
483
  // Solve K * u = F
@@ -479,8 +488,8 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
479
488
  const idx = nodeIndex.get(node.id)! * 3;
480
489
  return {
481
490
  nodeId: node.id,
482
- ux: u[idx],
483
- uy: u[idx + 1],
491
+ ux: u[idx],
492
+ uy: u[idx + 1],
484
493
  theta: u[idx + 2],
485
494
  };
486
495
  });
@@ -490,14 +499,13 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
490
499
  const K_clean = zeros(nDOF, nDOF);
491
500
  for (const elem of model.elements) {
492
501
  const fromNode = nodeById.get(elem.fromNodeId)!;
493
- const toNode = nodeById.get(elem.toNodeId)!;
502
+ const toNode = nodeById.get(elem.toNodeId)!;
494
503
  const Ke = globalElementStiffness(elem, fromNode, toNode);
495
504
  const i0 = nodeIndex.get(elem.fromNodeId)! * 3;
496
505
  const i1 = nodeIndex.get(elem.toNodeId)! * 3;
497
- const dofs = [i0, i0+1, i0+2, i1, i1+1, i1+2];
506
+ const dofs = [i0, i0 + 1, i0 + 2, i1, i1 + 1, i1 + 2];
498
507
  for (let r = 0; r < 6; r++)
499
- for (let c_ = 0; c_ < 6; c_++)
500
- K_clean[dofs[r]][dofs[c_]] += Ke[r][c_];
508
+ for (let c_ = 0; c_ < 6; c_++) K_clean[dofs[r]][dofs[c_]] += Ke[r][c_];
501
509
  }
502
510
 
503
511
  const reactions: SupportReaction[] = model.supports.map((support) => {
@@ -505,8 +513,8 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
505
513
  const Ku_row = (row: number) => K_clean[row].reduce((s, kij, j) => s + kij * u[j], 0);
506
514
  return {
507
515
  nodeId: support.nodeId,
508
- Rx: support.ux ? Ku_row(idx) - F[idx] : 0,
509
- Ry: support.uy ? Ku_row(idx + 1) - F[idx + 1] : 0,
516
+ Rx: support.ux ? Ku_row(idx) - F[idx] : 0,
517
+ Ry: support.uy ? Ku_row(idx + 1) - F[idx + 1] : 0,
510
518
  Mz: support.theta ? Ku_row(idx + 2) - F[idx + 2] : 0,
511
519
  };
512
520
  });
@@ -514,7 +522,7 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
514
522
  // Recover element internal forces
515
523
  const elementForces: ElementForces[] = model.elements.map((elem) => {
516
524
  const fromNode = nodeById.get(elem.fromNodeId)!;
517
- const toNode = nodeById.get(elem.toNodeId)!;
525
+ const toNode = nodeById.get(elem.toNodeId)!;
518
526
  const dx = toNode.x - fromNode.x;
519
527
  const dy = toNode.y - fromNode.y;
520
528
  const L = Math.sqrt(dx * dx + dy * dy);
@@ -526,11 +534,13 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
526
534
 
527
535
  const i0 = nodeIndex.get(elem.fromNodeId)! * 3;
528
536
  const i1 = nodeIndex.get(elem.toNodeId)! * 3;
529
- const u_global = [u[i0], u[i0+1], u[i0+2], u[i1], u[i1+1], u[i1+2]];
537
+ const u_global = [u[i0], u[i0 + 1], u[i0 + 2], u[i1], u[i1 + 1], u[i1 + 2]];
530
538
 
531
539
  // Transform to local: u_local = T * u_global
532
540
  const T = transformMatrix(c, s);
533
- const u_local = u_global.map((_, i) => T[i].reduce((sum, tij, j) => sum + tij * u_global[j], 0));
541
+ const u_local = u_global.map((_, i) =>
542
+ T[i].reduce((sum, tij, j) => sum + tij * u_global[j], 0)
543
+ );
534
544
 
535
545
  const k_local = localStiffness(E, I, A, L);
536
546
  // f_local = k_local * u_local (internal forces in local frame)
@@ -544,7 +554,14 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
544
554
  const dl = (model.distributedLoads ?? []).find((d) => d.elementId === elem.id);
545
555
  let f_local_adj = [...f_local];
546
556
  if (dl) {
547
- const f_fef_local = [0, (dl.w * L) / 2, (dl.w * L * L) / 12, 0, (dl.w * L) / 2, -(dl.w * L * L) / 12];
557
+ const f_fef_local = [
558
+ 0,
559
+ (dl.w * L) / 2,
560
+ (dl.w * L * L) / 12,
561
+ 0,
562
+ (dl.w * L) / 2,
563
+ -(dl.w * L * L) / 12,
564
+ ];
548
565
  f_local_adj = f_local.map((v, i) => v + f_fef_local[i]);
549
566
  }
550
567
 
@@ -558,18 +575,18 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
558
575
 
559
576
  return {
560
577
  elementId: elem.id,
561
- N_start: -f_local_adj[0], // sign: tension positive convention
562
- V_start: -f_local_adj[1],
563
- M_start: f_local_adj[2],
564
- N_end: f_local_adj[3],
565
- V_end: f_local_adj[4],
566
- M_end: f_local_adj[5],
578
+ N_start: -f_local_adj[0], // sign: tension positive convention
579
+ V_start: -f_local_adj[1],
580
+ M_start: f_local_adj[2],
581
+ N_end: f_local_adj[3],
582
+ V_end: f_local_adj[4],
583
+ M_end: f_local_adj[5],
567
584
  utilisationRatio,
568
585
  };
569
586
  });
570
587
 
571
588
  const maxDisplacementM = Math.max(
572
- ...nodeDisplacements.map((d) => Math.sqrt(d.ux ** 2 + d.uy ** 2)),
589
+ ...nodeDisplacements.map((d) => Math.sqrt(d.ux ** 2 + d.uy ** 2))
573
590
  );
574
591
  const maxUtilisationRatio = Math.max(0, ...elementForces.map((ef) => ef.utilisationRatio));
575
592
 
@@ -590,7 +607,7 @@ export function solveFrame2D(model: Frame2DModel): Frame2DResult {
590
607
  export function buildFrame2DReceipt(
591
608
  model: Frame2DModel,
592
609
  result: Frame2DResult,
593
- options: Frame2DReceiptOptions = {},
610
+ options: Frame2DReceiptOptions = {}
594
611
  ): Frame2DReceipt {
595
612
  const violations: Array<{ criterion: string; message: string }> = [];
596
613
 
package/src/index.ts CHANGED
@@ -1,7 +1,20 @@
1
1
  export * from './frame2d';
2
- export { createStructuralAnalysisHandler, type StructuralAnalysisConfig, type StructureType, type MaterialType } from './traits/StructuralAnalysisTrait';
3
- export { createLoadBearingHandler, type LoadBearingConfig, type LoadCase, type LoadType } from './traits/LoadBearingTrait';
4
- export { createMaterialFatigueHandler, type MaterialFatigueConfig } from './traits/MaterialFatigueTrait';
2
+ export {
3
+ createStructuralAnalysisHandler,
4
+ type StructuralAnalysisConfig,
5
+ type StructureType,
6
+ type MaterialType,
7
+ } from './traits/StructuralAnalysisTrait';
8
+ export {
9
+ createLoadBearingHandler,
10
+ type LoadBearingConfig,
11
+ type LoadCase,
12
+ type LoadType,
13
+ } from './traits/LoadBearingTrait';
14
+ export {
15
+ createMaterialFatigueHandler,
16
+ type MaterialFatigueConfig,
17
+ } from './traits/MaterialFatigueTrait';
5
18
  export * from './traits/types';
6
19
 
7
20
  import { createStructuralAnalysisHandler } from './traits/StructuralAnalysisTrait';
@@ -10,8 +23,16 @@ import { createMaterialFatigueHandler } from './traits/MaterialFatigueTrait';
10
23
 
11
24
  export * from './frame2d';
12
25
 
13
- export const pluginMeta = { name: '@holoscript/plugin-civil-engineering', version: '1.0.0', traits: ['structural_analysis', 'load_bearing', 'material_fatigue', 'dsm_frame_2d'] };
14
- export const traitHandlers = [createStructuralAnalysisHandler(), createLoadBearingHandler(), createMaterialFatigueHandler()];
26
+ export const pluginMeta = {
27
+ name: '@holoscript/plugin-civil-engineering',
28
+ version: '1.0.0',
29
+ traits: ['structural_analysis', 'load_bearing', 'material_fatigue', 'dsm_frame_2d'],
30
+ };
31
+ export const traitHandlers = [
32
+ createStructuralAnalysisHandler(),
33
+ createLoadBearingHandler(),
34
+ createMaterialFatigueHandler(),
35
+ ];
15
36
 
16
37
  // Runtime integration — behavioral trait handler + registrar that wire the
17
38
  // deterministic Direct-Stiffness-Method 2D frame solver into HoloScriptRuntime's
package/src/runtime.ts CHANGED
@@ -88,7 +88,7 @@ export interface RuntimeTraitHandler {
88
88
  node: unknown,
89
89
  config: DsmFrame2dTraitConfig,
90
90
  context: TraitDispatchContext,
91
- delta: number,
91
+ delta: number
92
92
  ) => void;
93
93
  }
94
94
 
@@ -120,7 +120,7 @@ function resolveModel(config: DsmFrame2dTraitConfig | undefined): Frame2DModel |
120
120
  function solveOntoNode(
121
121
  node: unknown,
122
122
  config: DsmFrame2dTraitConfig | undefined,
123
- context: TraitDispatchContext,
123
+ context: TraitDispatchContext
124
124
  ): void {
125
125
  const carrier = node as DsmFrame2dNode;
126
126
  const nodeId = carrier.id ?? carrier.name ?? 'unknown';
@@ -2,24 +2,57 @@
2
2
  import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
3
3
 
4
4
  export type LoadType = 'dead' | 'live' | 'wind' | 'seismic' | 'snow' | 'impact' | 'thermal';
5
- export interface LoadCase { id: string; type: LoadType; magnitudeKN: number; direction: [number, number, number]; combinationFactor: number; }
6
- export interface LoadBearingConfig { capacityKN: number; loadCases: LoadCase[]; redundancyFactor: number; }
7
- export interface LoadBearingState { totalAppliedKN: number; remainingCapacityKN: number; criticalLoadCase: string | null; isOverloaded: boolean; }
5
+ export interface LoadCase {
6
+ id: string;
7
+ type: LoadType;
8
+ magnitudeKN: number;
9
+ direction: [number, number, number];
10
+ combinationFactor: number;
11
+ }
12
+ export interface LoadBearingConfig {
13
+ capacityKN: number;
14
+ loadCases: LoadCase[];
15
+ redundancyFactor: number;
16
+ }
17
+ export interface LoadBearingState {
18
+ totalAppliedKN: number;
19
+ remainingCapacityKN: number;
20
+ criticalLoadCase: string | null;
21
+ isOverloaded: boolean;
22
+ }
8
23
 
9
24
  const defaultConfig: LoadBearingConfig = { capacityKN: 100, loadCases: [], redundancyFactor: 1.0 };
10
25
 
11
26
  export function createLoadBearingHandler(): TraitHandler<LoadBearingConfig> {
12
- return { name: 'load_bearing', defaultConfig,
27
+ return {
28
+ name: 'load_bearing',
29
+ defaultConfig,
13
30
  onAttach(n: HSPlusNode, c: LoadBearingConfig, ctx: TraitContext) {
14
31
  const total = c.loadCases.reduce((sum, lc) => sum + lc.magnitudeKN * lc.combinationFactor, 0);
15
- n.__loadState = { totalAppliedKN: total, remainingCapacityKN: c.capacityKN - total, criticalLoadCase: null, isOverloaded: total > c.capacityKN };
32
+ n.__loadState = {
33
+ totalAppliedKN: total,
34
+ remainingCapacityKN: c.capacityKN - total,
35
+ criticalLoadCase: null,
36
+ isOverloaded: total > c.capacityKN,
37
+ };
16
38
  ctx.emit?.('load:assessed', { total, capacity: c.capacityKN });
17
39
  },
18
- onDetach(n: HSPlusNode, _c: LoadBearingConfig, ctx: TraitContext) { delete n.__loadState; ctx.emit?.('load:removed'); },
40
+ onDetach(n: HSPlusNode, _c: LoadBearingConfig, ctx: TraitContext) {
41
+ delete n.__loadState;
42
+ ctx.emit?.('load:removed');
43
+ },
19
44
  onUpdate() {},
20
45
  onEvent(n: HSPlusNode, c: LoadBearingConfig, ctx: TraitContext, e: TraitEvent) {
21
- const s = n.__loadState as LoadBearingState | undefined; if (!s) return;
22
- if (e.type === 'load:add') { const kn = (e.payload?.magnitudeKN as number) ?? 0; s.totalAppliedKN += kn; s.remainingCapacityKN = c.capacityKN - s.totalAppliedKN; s.isOverloaded = s.totalAppliedKN > c.capacityKN; if (s.isOverloaded) ctx.emit?.('load:overloaded', { applied: s.totalAppliedKN, capacity: c.capacityKN }); }
46
+ const s = n.__loadState as LoadBearingState | undefined;
47
+ if (!s) return;
48
+ if (e.type === 'load:add') {
49
+ const kn = (e.payload?.magnitudeKN as number) ?? 0;
50
+ s.totalAppliedKN += kn;
51
+ s.remainingCapacityKN = c.capacityKN - s.totalAppliedKN;
52
+ s.isOverloaded = s.totalAppliedKN > c.capacityKN;
53
+ if (s.isOverloaded)
54
+ ctx.emit?.('load:overloaded', { applied: s.totalAppliedKN, capacity: c.capacityKN });
55
+ }
23
56
  },
24
57
  };
25
58
  }
@@ -1,27 +1,67 @@
1
1
  /** @material_fatigue Trait — Fatigue life estimation. @trait material_fatigue */
2
2
  import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types';
3
3
 
4
- export interface MaterialFatigueConfig { sNcurveSlope: number; enduranceLimitMPa: number; designLifeCycles: number; stressRatio: number; surfaceFinishFactor: number; }
5
- export interface MaterialFatigueState { accumulatedCycles: number; damageFraction: number; remainingLifePercent: number; isFailed: boolean; }
4
+ export interface MaterialFatigueConfig {
5
+ sNcurveSlope: number;
6
+ enduranceLimitMPa: number;
7
+ designLifeCycles: number;
8
+ stressRatio: number;
9
+ surfaceFinishFactor: number;
10
+ }
11
+ export interface MaterialFatigueState {
12
+ accumulatedCycles: number;
13
+ damageFraction: number;
14
+ remainingLifePercent: number;
15
+ isFailed: boolean;
16
+ }
6
17
 
7
- const defaultConfig: MaterialFatigueConfig = { sNcurveSlope: -0.1, enduranceLimitMPa: 200, designLifeCycles: 1e6, stressRatio: -1, surfaceFinishFactor: 0.9 };
18
+ const defaultConfig: MaterialFatigueConfig = {
19
+ sNcurveSlope: -0.1,
20
+ enduranceLimitMPa: 200,
21
+ designLifeCycles: 1e6,
22
+ stressRatio: -1,
23
+ surfaceFinishFactor: 0.9,
24
+ };
8
25
 
9
26
  export function createMaterialFatigueHandler(): TraitHandler<MaterialFatigueConfig> {
10
- return { name: 'material_fatigue', defaultConfig,
11
- onAttach(n: HSPlusNode, _c: MaterialFatigueConfig, ctx: TraitContext) { n.__fatigueState = { accumulatedCycles: 0, damageFraction: 0, remainingLifePercent: 100, isFailed: false }; ctx.emit?.('fatigue:monitoring_started'); },
12
- onDetach(n: HSPlusNode, _c: MaterialFatigueConfig, ctx: TraitContext) { delete n.__fatigueState; ctx.emit?.('fatigue:monitoring_stopped'); },
27
+ return {
28
+ name: 'material_fatigue',
29
+ defaultConfig,
30
+ onAttach(n: HSPlusNode, _c: MaterialFatigueConfig, ctx: TraitContext) {
31
+ n.__fatigueState = {
32
+ accumulatedCycles: 0,
33
+ damageFraction: 0,
34
+ remainingLifePercent: 100,
35
+ isFailed: false,
36
+ };
37
+ ctx.emit?.('fatigue:monitoring_started');
38
+ },
39
+ onDetach(n: HSPlusNode, _c: MaterialFatigueConfig, ctx: TraitContext) {
40
+ delete n.__fatigueState;
41
+ ctx.emit?.('fatigue:monitoring_stopped');
42
+ },
13
43
  onUpdate() {},
14
44
  onEvent(n: HSPlusNode, c: MaterialFatigueConfig, ctx: TraitContext, e: TraitEvent) {
15
- const s = n.__fatigueState as MaterialFatigueState | undefined; if (!s || s.isFailed) return;
45
+ const s = n.__fatigueState as MaterialFatigueState | undefined;
46
+ if (!s || s.isFailed) return;
16
47
  if (e.type === 'fatigue:add_cycles') {
17
48
  const cycles = (e.payload?.cycles as number) ?? 0;
18
49
  const stressMPa = (e.payload?.stressAmplitudeMPa as number) ?? 0;
19
50
  s.accumulatedCycles += cycles;
20
- const nf = stressMPa > c.enduranceLimitMPa ? Math.pow(stressMPa / c.enduranceLimitMPa, 1 / c.sNcurveSlope) * c.designLifeCycles : Infinity;
51
+ const nf =
52
+ stressMPa > c.enduranceLimitMPa
53
+ ? Math.pow(stressMPa / c.enduranceLimitMPa, 1 / c.sNcurveSlope) * c.designLifeCycles
54
+ : Infinity;
21
55
  s.damageFraction += cycles / nf;
22
56
  s.remainingLifePercent = Math.max(0, (1 - s.damageFraction) * 100);
23
- if (s.damageFraction >= 1) { s.isFailed = true; ctx.emit?.('fatigue:failure', { cycles: s.accumulatedCycles }); }
24
- else ctx.emit?.('fatigue:updated', { damage: s.damageFraction, remaining: s.remainingLifePercent });
57
+ if (s.damageFraction >= 1) {
58
+ s.isFailed = true;
59
+ ctx.emit?.('fatigue:failure', { cycles: s.accumulatedCycles });
60
+ } else
61
+ ctx.emit?.('fatigue:updated', {
62
+ damage: s.damageFraction,
63
+ remaining: s.remainingLifePercent,
64
+ });
25
65
  }
26
66
  },
27
67
  };
@@ -3,25 +3,62 @@ import type { TraitHandler, HSPlusNode, TraitContext, TraitEvent } from './types
3
3
 
4
4
  export type StructureType = 'beam' | 'column' | 'slab' | 'truss' | 'frame' | 'shell' | 'foundation';
5
5
  export type MaterialType = 'steel' | 'concrete' | 'timber' | 'masonry' | 'composite' | 'aluminum';
6
- export interface StructuralAnalysisConfig { structureType: StructureType; material: MaterialType; yieldStrengthMPa: number; elasticModulusGPa: number; safetyFactor: number; loadCasesCount: number; }
7
- export interface StructuralAnalysisState { maxStressMPa: number; maxDeflectionMm: number; utilizationRatio: number; isPasssing: boolean; }
6
+ export interface StructuralAnalysisConfig {
7
+ structureType: StructureType;
8
+ material: MaterialType;
9
+ yieldStrengthMPa: number;
10
+ elasticModulusGPa: number;
11
+ safetyFactor: number;
12
+ loadCasesCount: number;
13
+ }
14
+ export interface StructuralAnalysisState {
15
+ maxStressMPa: number;
16
+ maxDeflectionMm: number;
17
+ utilizationRatio: number;
18
+ isPasssing: boolean;
19
+ }
8
20
 
9
- const defaultConfig: StructuralAnalysisConfig = { structureType: 'beam', material: 'steel', yieldStrengthMPa: 250, elasticModulusGPa: 200, safetyFactor: 1.5, loadCasesCount: 1 };
21
+ const defaultConfig: StructuralAnalysisConfig = {
22
+ structureType: 'beam',
23
+ material: 'steel',
24
+ yieldStrengthMPa: 250,
25
+ elasticModulusGPa: 200,
26
+ safetyFactor: 1.5,
27
+ loadCasesCount: 1,
28
+ };
10
29
 
11
30
  export function createStructuralAnalysisHandler(): TraitHandler<StructuralAnalysisConfig> {
12
- return { name: 'structural_analysis', defaultConfig,
13
- onAttach(n: HSPlusNode, _c: StructuralAnalysisConfig, ctx: TraitContext) { n.__structState = { maxStressMPa: 0, maxDeflectionMm: 0, utilizationRatio: 0, isPasssing: true }; ctx.emit?.('structural:ready'); },
14
- onDetach(n: HSPlusNode, _c: StructuralAnalysisConfig, ctx: TraitContext) { delete n.__structState; ctx.emit?.('structural:removed'); },
31
+ return {
32
+ name: 'structural_analysis',
33
+ defaultConfig,
34
+ onAttach(n: HSPlusNode, _c: StructuralAnalysisConfig, ctx: TraitContext) {
35
+ n.__structState = {
36
+ maxStressMPa: 0,
37
+ maxDeflectionMm: 0,
38
+ utilizationRatio: 0,
39
+ isPasssing: true,
40
+ };
41
+ ctx.emit?.('structural:ready');
42
+ },
43
+ onDetach(n: HSPlusNode, _c: StructuralAnalysisConfig, ctx: TraitContext) {
44
+ delete n.__structState;
45
+ ctx.emit?.('structural:removed');
46
+ },
15
47
  onUpdate() {},
16
48
  onEvent(n: HSPlusNode, c: StructuralAnalysisConfig, ctx: TraitContext, e: TraitEvent) {
17
- const s = n.__structState as StructuralAnalysisState | undefined; if (!s) return;
49
+ const s = n.__structState as StructuralAnalysisState | undefined;
50
+ if (!s) return;
18
51
  if (e.type === 'structural:analyze') {
19
52
  const loadKN = (e.payload?.loadKN as number) ?? 0;
20
53
  const spanM = (e.payload?.spanM as number) ?? 1;
21
- s.maxStressMPa = (loadKN * 1000 * spanM) / (0.001 * c.elasticModulusGPa * 1e9) * 1e6;
54
+ s.maxStressMPa = ((loadKN * 1000 * spanM) / (0.001 * c.elasticModulusGPa * 1e9)) * 1e6;
22
55
  s.utilizationRatio = s.maxStressMPa / (c.yieldStrengthMPa / c.safetyFactor);
23
56
  s.isPasssing = s.utilizationRatio <= 1.0;
24
- ctx.emit?.('structural:result', { stress: s.maxStressMPa, utilization: s.utilizationRatio, pass: s.isPasssing });
57
+ ctx.emit?.('structural:result', {
58
+ stress: s.maxStressMPa,
59
+ utilization: s.utilizationRatio,
60
+ pass: s.isPasssing,
61
+ });
25
62
  }
26
63
  },
27
64
  };
@@ -1,4 +1,22 @@
1
- export interface HSPlusNode { id?: string; properties?: Record<string, unknown>; [key: string]: unknown; }
2
- export interface TraitContext { emit?: (event: string, payload?: unknown) => void; [key: string]: unknown; }
3
- export interface TraitEvent { type: string; payload?: Record<string, unknown>; [key: string]: unknown; }
4
- export interface TraitHandler<T = unknown> { name: string; defaultConfig: T; onAttach(n: HSPlusNode, c: T, ctx: TraitContext): void; onDetach(n: HSPlusNode, c: T, ctx: TraitContext): void; onUpdate(n: HSPlusNode, c: T, ctx: TraitContext, d: number): void; onEvent(n: HSPlusNode, c: T, ctx: TraitContext, e: TraitEvent): void; }
1
+ export interface HSPlusNode {
2
+ id?: string;
3
+ properties?: Record<string, unknown>;
4
+ [key: string]: unknown;
5
+ }
6
+ export interface TraitContext {
7
+ emit?: (event: string, payload?: unknown) => void;
8
+ [key: string]: unknown;
9
+ }
10
+ export interface TraitEvent {
11
+ type: string;
12
+ payload?: Record<string, unknown>;
13
+ [key: string]: unknown;
14
+ }
15
+ export interface TraitHandler<T = unknown> {
16
+ name: string;
17
+ defaultConfig: T;
18
+ onAttach(n: HSPlusNode, c: T, ctx: TraitContext): void;
19
+ onDetach(n: HSPlusNode, c: T, ctx: TraitContext): void;
20
+ onUpdate(n: HSPlusNode, c: T, ctx: TraitContext, d: number): void;
21
+ onEvent(n: HSPlusNode, c: T, ctx: TraitContext, e: TraitEvent): void;
22
+ }
package/tsconfig.json CHANGED
@@ -1 +1,5 @@
1
- { "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "dist", "rootDir": "src", "declaration": true }, "include": ["src"] }
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": { "outDir": "dist", "rootDir": "src", "declaration": true },
4
+ "include": ["src"]
5
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025-2026 HoloScript Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.