@trioforge/crucible 0.2.0 → 0.3.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.
@@ -0,0 +1,2 @@
1
+ export { catmullRom, cubicBezier, uniformBSpline } from "./curve";
2
+ export type { CatmullRomOptions, Curve, CurveFrame, CurveOptions, CurveProjection, CurveVector } from "./curve";
@@ -0,0 +1,8 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local exports = {}
4
+ local _curve = TS.import(script, script, "curve")
5
+ exports.catmullRom = _curve.catmullRom
6
+ exports.cubicBezier = _curve.cubicBezier
7
+ exports.uniformBSpline = _curve.uniformBSpline
8
+ return exports
@@ -1,6 +1,7 @@
1
1
  import type { BootOptions } from "../index";
2
2
  import type { PayloadArgs, ToClient } from "../network";
3
3
  import "../internal/data/record-bindings";
4
+ export declare const BOOTED_ATTRIBUTE = "CrucibleBooted";
4
5
  export { config, playerConfig, configReady, refreshConfigs } from "../configs";
5
6
  export type { Config, PlayerConfig } from "../configs";
6
7
  export { publish } from "./publish";
@@ -13,6 +13,7 @@ if not game:GetService("RunService"):IsServer() then
13
13
  error("@trioforge/crucible/server loaded on the client")
14
14
  end
15
15
  local session
16
+ local BOOTED_ATTRIBUTE = "CrucibleBooted"
16
17
  local _configs = TS.import(script, script.Parent, "configs")
17
18
  exports.config = _configs.config
18
19
  exports.playerConfig = _configs.playerConfig
@@ -37,7 +38,13 @@ local function boot(options)
37
38
  bootSide({
38
39
  name = "server",
39
40
  renders = false,
40
- booted = function() end,
41
+ booted = function()
42
+ local service = game:GetService("ServerScriptService")
43
+ service:SetAttribute(BOOTED_ATTRIBUTE, true)
44
+ return function()
45
+ return service:SetAttribute(BOOTED_ATTRIBUTE, nil)
46
+ end
47
+ end,
41
48
  network = network,
42
49
  }, options)
43
50
  end
@@ -58,5 +65,6 @@ do
58
65
  _container.broadcast = broadcast
59
66
  end
60
67
  exports.boot = boot
68
+ exports.BOOTED_ATTRIBUTE = BOOTED_ATTRIBUTE
61
69
  exports.Network = Network
62
70
  return exports
@@ -0,0 +1,38 @@
1
+ import type { CrucibleBehaviour } from "../crucible-behaviour";
2
+ import { type MessageKind } from "../internal/network/catalog";
3
+ import type { Branch } from "../internal/network/schema";
4
+ import type { Subsystem } from "../subsystem";
5
+ export { n } from "../internal/network/schema";
6
+ /**
7
+ * Builds one Subsystem with the constructor arguments given, without `boot`. Its `receive()` fields register, and
8
+ * the spec calls its hooks as methods.
9
+ */
10
+ export declare function subsystem<T extends Subsystem, A extends unknown[]>(klass: new (...args: A) => T, ...args: A): T;
11
+ /**
12
+ * Builds one behaviour bound to `instance`, which may be a plain table. Each `attribute()` field reads the instance
13
+ * through `GetAttribute` and is refused as binding refuses it.
14
+ */
15
+ export declare function behaviour<T extends CrucibleBehaviour<string>, A extends unknown[]>(klass: new (...args: A) => T, instance: Instance, ...args: A): T;
16
+ /** The callback a built Subsystem's `receive()` field registered for `message`. */
17
+ export declare function receiver(message: unknown): ((...args: never[]) => unknown) | undefined;
18
+ /** The name, kind and schema nodes of a declared message. */
19
+ export declare function message(token: unknown): {
20
+ name: string;
21
+ kind: MessageKind;
22
+ input: Branch;
23
+ output?: Branch;
24
+ } | undefined;
25
+ /** Sends `value` through the message's compiled codec and back, as the wire does, and returns what arrives. */
26
+ export declare function transfer<T>(token: unknown, value: T, which?: "input" | "output"): T;
27
+ /** Seals the message catalog and lists the names in wire order. It seals once per module graph. */
28
+ export declare function sealMessages(): readonly string[];
29
+ /** What the transformer registered for a class. */
30
+ export declare function registration(klass: object): {
31
+ id: string;
32
+ kind: "subsystem";
33
+ } | {
34
+ id: string;
35
+ kind: "behaviour";
36
+ tag: string;
37
+ instanceClasses: readonly string[];
38
+ };
@@ -0,0 +1,189 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local TS = _G[script]
3
+ local exports = {}
4
+ local _construction_claim = TS.import(script, script.Parent, "internal", "construction-claim")
5
+ local clearClaim = _construction_claim.clearClaim
6
+ local setClaim = _construction_claim.setClaim
7
+ local track = TS.import(script, script.Parent, "internal", "lifecycle").track
8
+ local _claims = TS.import(script, script.Parent, "internal", "network", "claims")
9
+ local closeClaims = _claims.closeClaims
10
+ local openClaims = _claims.openClaims
11
+ local receiverOf = _claims.receiverOf
12
+ local _catalog = TS.import(script, script.Parent, "internal", "network", "catalog")
13
+ local definitionOf = _catalog.definitionOf
14
+ local seal = _catalog.seal
15
+ local _codec = TS.import(script, script.Parent, "internal", "network", "codec")
16
+ local Reader = _codec.Reader
17
+ local Writer = _codec.Writer
18
+ local recordOf = TS.import(script, script.Parent, "internal", "registry").recordOf
19
+ exports.n = TS.import(script, script.Parent, "internal", "network", "schema").n
20
+ local function recordFor(klass)
21
+ return recordOf(klass) or error(`{tostring(klass)} is not a registered Crucible class; compile it with the Crucible transformer`, 0)
22
+ end
23
+ local function definitionFor(token)
24
+ return definitionOf(token) or error("not a message declared with toServer, toClient or askServer", 0)
25
+ end
26
+ local function construct(klass, state, args)
27
+ local id = (state.record).id
28
+ local thread = coroutine.create(function()
29
+ setClaim(state)
30
+ return klass.new(unpack(args))
31
+ end)
32
+ local ok, result = coroutine.resume(thread)
33
+ local yielded = ok and coroutine.status(thread) ~= "dead"
34
+ if yielded then
35
+ coroutine.close(thread)
36
+ end
37
+ clearClaim(thread)
38
+ if yielded then
39
+ error(`{id}'s constructor yielded; boot faults a constructor that yields`, 0)
40
+ end
41
+ if not ok then
42
+ error(result, 0)
43
+ end
44
+ return result
45
+ end
46
+ --[[
47
+ *
48
+ * Builds one Subsystem with the constructor arguments given, without `boot`. Its `receive()` fields register, and
49
+ * the spec calls its hooks as methods.
50
+
51
+ ]]
52
+ local function subsystem(klass, ...)
53
+ local args = { ... }
54
+ local record = recordFor(klass)
55
+ if record.kind ~= 0 then
56
+ error(`{record.id} is a behaviour; build it with behaviour()`, 0)
57
+ end
58
+ -- A spec's RunService double only needs IsClient when the class has a receive() field.
59
+ openClaims({
60
+ side = function()
61
+ return if game:GetService("RunService"):IsClient() then "client" else "server"
62
+ end,
63
+ retired = function() end,
64
+ })
65
+ local ok, result = pcall(construct, klass, track(record), args)
66
+ closeClaims(false)
67
+ if not ok then
68
+ error(result, 0)
69
+ end
70
+ return result
71
+ end
72
+ --[[
73
+ *
74
+ * Builds one behaviour bound to `instance`, which may be a plain table. Each `attribute()` field reads the instance
75
+ * through `GetAttribute` and is refused as binding refuses it.
76
+
77
+ ]]
78
+ local function behaviour(klass, instance, ...)
79
+ local args = { ... }
80
+ local record = recordFor(klass)
81
+ if record.kind ~= 1 then
82
+ error(`{record.id} is a Subsystem; build it with subsystem()`, 0)
83
+ end
84
+ local attributes
85
+ if record.attributes ~= nil then
86
+ attributes = {}
87
+ for field, descriptor in record.attributes do
88
+ local value = instance:GetAttribute(descriptor.name)
89
+ if not (if value == nil then not descriptor.required else descriptor.check(value)) then
90
+ error(`{record.id}: attribute {descriptor.name} is invalid ({typeof(value)})`, 0)
91
+ end
92
+ attributes[field] = value
93
+ end
94
+ end
95
+ return construct(klass, track(record, instance, attributes), args)
96
+ end
97
+ --* The callback a built Subsystem's `receive()` field registered for `message`.
98
+ local function receiver(message)
99
+ local _result = receiverOf(definitionFor(message))
100
+ if _result ~= nil then
101
+ _result = _result.callback
102
+ end
103
+ return _result
104
+ end
105
+ --* The name, kind and schema nodes of a declared message.
106
+ local function message(token)
107
+ local definition = definitionOf(token)
108
+ if definition == nil then
109
+ return nil
110
+ end
111
+ local _object = {
112
+ name = definition.name,
113
+ kind = definition.kind,
114
+ input = definition.input.node,
115
+ }
116
+ local _left = "output"
117
+ local _result = definition.output
118
+ if _result ~= nil then
119
+ _result = _result.node
120
+ end
121
+ _object[_left] = _result
122
+ return _object
123
+ end
124
+ local function failure(err)
125
+ local _err = err
126
+ local _condition = type(_err) == "table"
127
+ if _condition then
128
+ _condition = err.codecFailure == true
129
+ end
130
+ if _condition then
131
+ local _binding = err
132
+ local path = _binding.path
133
+ local reason = _binding.reason
134
+ return `{path}: {reason}`
135
+ end
136
+ return tostring(err)
137
+ end
138
+ --* Sends `value` through the message's compiled codec and back, as the wire does, and returns what arrives.
139
+ local function transfer(token, value, which)
140
+ if which == nil then
141
+ which = "input"
142
+ end
143
+ local definition = definitionFor(token)
144
+ local codec = definition[which] or error(`{definition.name} has no {which} codec`, 0)
145
+ local writer = Writer.new()
146
+ local encoded, encodeError = pcall(codec.encode, writer, value, "payload")
147
+ if not encoded then
148
+ error(`{definition.name} refused to encode {failure(encodeError)}`, 0)
149
+ end
150
+ local packet, sidecar, slots = writer:finish(buffer.create(0))
151
+ local reader = Reader.new(packet, 0, sidecar, slots)
152
+ local decoded, result = pcall(codec.decode, reader, "payload")
153
+ if not decoded then
154
+ error(`{definition.name} failed to decode {failure(result)}`, 0)
155
+ end
156
+ if not reader:finished() then
157
+ error(`{definition.name} left trailing bytes or slots`, 0)
158
+ end
159
+ return result
160
+ end
161
+ --* Seals the message catalog and lists the names in wire order. It seals once per module graph.
162
+ local function sealMessages()
163
+ local catalog = seal()
164
+ return if catalog == "" then {} else string.split(catalog, "\n")
165
+ end
166
+ --* What the transformer registered for a class.
167
+ local function registration(klass)
168
+ local record = recordFor(klass)
169
+ if record.kind == 0 then
170
+ return {
171
+ id = record.id,
172
+ kind = "subsystem",
173
+ }
174
+ end
175
+ return {
176
+ id = record.id,
177
+ kind = "behaviour",
178
+ tag = record.tag,
179
+ instanceClasses = record.instanceClasses,
180
+ }
181
+ end
182
+ exports.subsystem = subsystem
183
+ exports.behaviour = behaviour
184
+ exports.receiver = receiver
185
+ exports.message = message
186
+ exports.transfer = transfer
187
+ exports.sealMessages = sealMessages
188
+ exports.registration = registration
189
+ return exports
@@ -0,0 +1,24 @@
1
+ export type SeedPart = string | number;
2
+ export type RngState = readonly [number, number, number, number];
3
+ export declare class Rng {
4
+ private a;
5
+ private b;
6
+ private c;
7
+ private d;
8
+ constructor(...seed: SeedPart[]);
9
+ static fromState(state: RngState): Rng;
10
+ state(): RngState;
11
+ clone(): Rng;
12
+ fork(): Rng;
13
+ uint32(): number;
14
+ float(): number;
15
+ float(min: number, max: number): number;
16
+ int(min: number, max: number): number;
17
+ chance(probability: number): boolean;
18
+ normal(mean?: number, deviation?: number): number;
19
+ pick<T extends defined>(items: readonly T[]): T;
20
+ weightedIndex(weights: readonly number[]): number;
21
+ weighted<T extends defined>(items: readonly T[], weight: (item: T, index: number) => number): T;
22
+ shuffle<T extends defined>(items: readonly T[]): T[];
23
+ sample<T extends defined>(items: readonly T[], count: number): T[];
24
+ }
@@ -0,0 +1,301 @@
1
+ -- Compiled with roblox-ts v3.0.0
2
+ local WORD = 2 ^ 32
3
+ local DOUBLE = 2 ^ 53
4
+ -- Luau has no 32-bit multiply, and a full product of two words overflows a double's 53 bits, so it splits one side.
5
+ local function imul(a, b)
6
+ local low = a * (b % 65536)
7
+ local high = ((a * math.floor(b / 65536)) % WORD) * 65536
8
+ return (low + high) % WORD
9
+ end
10
+ local function fmix(h)
11
+ h = imul(bit32.bxor(h, bit32.rshift(h, 16)), 0x85ebca6b)
12
+ h = imul(bit32.bxor(h, bit32.rshift(h, 13)), 0xc2b2ae35)
13
+ return bit32.bxor(h, bit32.rshift(h, 16))
14
+ end
15
+ local STRING = 0x5354524e
16
+ local NUMBER = 0x4e554d42
17
+ local function words(parts)
18
+ local result = { #parts }
19
+ for _, part in parts do
20
+ if type(part) == "string" then
21
+ local _arg1 = #part
22
+ -- ▼ Array.push ▼
23
+ table.insert(result, STRING)
24
+ table.insert(result, _arg1)
25
+ -- ▲ Array.push ▲
26
+ for index = 1, #part, 4 do
27
+ local a, b, c, d = string.byte(part, index, index + 3)
28
+ local _condition = a
29
+ if _condition == nil then
30
+ _condition = 0
31
+ end
32
+ local _condition_1 = b
33
+ if _condition_1 == nil then
34
+ _condition_1 = 0
35
+ end
36
+ local _exp = _condition + _condition_1 * 0x100
37
+ local _condition_2 = c
38
+ if _condition_2 == nil then
39
+ _condition_2 = 0
40
+ end
41
+ local _exp_1 = _exp + _condition_2 * 0x10000
42
+ local _condition_3 = d
43
+ if _condition_3 == nil then
44
+ _condition_3 = 0
45
+ end
46
+ table.insert(result, _exp_1 + _condition_3 * 0x1000000)
47
+ end
48
+ else
49
+ if part ~= part or part % 1 ~= 0 or math.abs(part) > DOUBLE then
50
+ error(`a seed number must be an integer within ±2^53, got {part}`, 3)
51
+ end
52
+ local _arg1 = part % WORD
53
+ local _arg2 = math.floor(part / WORD) % WORD
54
+ -- ▼ Array.push ▼
55
+ table.insert(result, NUMBER)
56
+ table.insert(result, _arg1)
57
+ table.insert(result, _arg2)
58
+ -- ▲ Array.push ▲
59
+ end
60
+ end
61
+ return result
62
+ end
63
+ local LANES = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a }
64
+ local function hashState(input)
65
+ -- ▼ ReadonlyArray.map ▼
66
+ local _newValue = table.create(#LANES)
67
+ local _callback = function(lane)
68
+ local h = lane
69
+ for _, word in input do
70
+ h = fmix((imul(bit32.bxor(h, word), 0x9e3779b1) + lane) % WORD)
71
+ end
72
+ return fmix(h)
73
+ end
74
+ for _k, _v in LANES do
75
+ _newValue[_k] = _callback(_v, _k - 1, LANES)
76
+ end
77
+ -- ▲ ReadonlyArray.map ▲
78
+ local state = _newValue
79
+ -- ▼ ReadonlyArray.every ▼
80
+ local _result = true
81
+ local _callback_1 = function(word)
82
+ return word == 0
83
+ end
84
+ for _k, _v in state do
85
+ if not _callback_1(_v, _k - 1, state) then
86
+ _result = false
87
+ break
88
+ end
89
+ end
90
+ -- ▲ ReadonlyArray.every ▲
91
+ if _result then
92
+ state[1] = 1
93
+ end
94
+ return { state[1], state[2], state[3], state[4] }
95
+ end
96
+ local function entropy()
97
+ return { math.random(0, 0xffff), math.random(0, 0xffff), math.random(0, 0xffff), math.floor(os.clock() * 1e6) % WORD }
98
+ end
99
+ local Rng
100
+ do
101
+ Rng = setmetatable({}, {
102
+ __tostring = function()
103
+ return "Rng"
104
+ end,
105
+ })
106
+ Rng.__index = Rng
107
+ function Rng.new(...)
108
+ local self = setmetatable({}, Rng)
109
+ return self:constructor(...) or self
110
+ end
111
+ function Rng:constructor(...)
112
+ local seed = { ... }
113
+ local _binding = hashState(if #seed > 0 then words(seed) else entropy())
114
+ self.a = _binding[1]
115
+ self.b = _binding[2]
116
+ self.c = _binding[3]
117
+ self.d = _binding[4]
118
+ end
119
+ function Rng:fromState(state)
120
+ local rng = Rng.new(0)
121
+ for _, word in state do
122
+ if word % 1 ~= 0 or word < 0 or word >= WORD then
123
+ error(`a state word must be a uint32, got {word}`, 2)
124
+ end
125
+ end
126
+ -- ▼ ReadonlyArray.every ▼
127
+ local _result = true
128
+ local _callback = function(word)
129
+ return word == 0
130
+ end
131
+ for _k, _v in state do
132
+ if not _callback(_v, _k - 1, state) then
133
+ _result = false
134
+ break
135
+ end
136
+ end
137
+ -- ▲ ReadonlyArray.every ▲
138
+ if _result then
139
+ error("a state can't be all zeroes", 2)
140
+ end
141
+ local _binding = state
142
+ rng.a = _binding[1]
143
+ rng.b = _binding[2]
144
+ rng.c = _binding[3]
145
+ rng.d = _binding[4]
146
+ return rng
147
+ end
148
+ function Rng:state()
149
+ return { self.a, self.b, self.c, self.d }
150
+ end
151
+ function Rng:clone()
152
+ return Rng:fromState(self:state())
153
+ end
154
+ function Rng:fork()
155
+ return Rng.new(self:uint32(), self:uint32(), self:uint32(), self:uint32())
156
+ end
157
+ function Rng:uint32()
158
+ local result = (bit32.lrotate((self.b * 5) % WORD, 7) * 9) % WORD
159
+ local t = bit32.lshift(self.b, 9)
160
+ self.c = bit32.bxor(self.c, self.a)
161
+ self.d = bit32.bxor(self.d, self.b)
162
+ self.b = bit32.bxor(self.b, self.c)
163
+ self.a = bit32.bxor(self.a, self.d)
164
+ self.c = bit32.bxor(self.c, t)
165
+ self.d = bit32.lrotate(self.d, 11)
166
+ return result
167
+ end
168
+ function Rng:float(min, max)
169
+ local unit = (bit32.rshift(self:uint32(), 5) * 2 ^ 26 + bit32.rshift(self:uint32(), 6)) / DOUBLE
170
+ if min == nil or max == nil then
171
+ return unit
172
+ end
173
+ if not (min <= max) then
174
+ error(`float needs min <= max, got {min} and {max}`, 2)
175
+ end
176
+ return min + unit * (max - min)
177
+ end
178
+ function Rng:int(min, max)
179
+ if min % 1 ~= 0 or max % 1 ~= 0 then
180
+ error(`int needs integer bounds, got {min} and {max}`, 2)
181
+ end
182
+ local span = max - min + 1
183
+ if span < 1 then
184
+ error(`int needs min <= max, got {min} and {max}`, 2)
185
+ end
186
+ if span > WORD then
187
+ error(`int spans at most 2^32 values, got {span}`, 2)
188
+ end
189
+ local limit = WORD - (WORD % span)
190
+ local value = self:uint32()
191
+ while value >= limit do
192
+ value = self:uint32()
193
+ end
194
+ return min + (value % span)
195
+ end
196
+ function Rng:chance(probability)
197
+ return self:float() < probability
198
+ end
199
+ function Rng:normal(mean, deviation)
200
+ if mean == nil then
201
+ mean = 0
202
+ end
203
+ if deviation == nil then
204
+ deviation = 1
205
+ end
206
+ local radius = math.sqrt(-2 * math.log(1 - self:float()))
207
+ return mean + deviation * radius * math.cos(2 * math.pi * self:float())
208
+ end
209
+ function Rng:pick(items)
210
+ if #items == 0 then
211
+ error("can't pick from an empty array", 2)
212
+ end
213
+ return items[self:int(0, #items - 1) + 1]
214
+ end
215
+ function Rng:weightedIndex(weights)
216
+ local total = 0
217
+ for _, weight in weights do
218
+ if not (weight >= 0 and weight < math.huge) then
219
+ error(`a weight must be finite and non-negative, got {weight}`, 2)
220
+ end
221
+ total += weight
222
+ end
223
+ if total <= 0 then
224
+ error("weights must add up to more than zero", 2)
225
+ end
226
+ local roll = self:float() * total
227
+ local last = 0
228
+ for index = 0, #weights - 1 do
229
+ if weights[index + 1] <= 0 then
230
+ continue
231
+ end
232
+ last = index
233
+ roll -= weights[index + 1]
234
+ if roll < 0 then
235
+ return index
236
+ end
237
+ end
238
+ -- Rounding can leave a sliver of the roll past the last bucket.
239
+ return last
240
+ end
241
+ function Rng:weighted(items, weight)
242
+ local _exp = items
243
+ local _self = self
244
+ local _items = items
245
+ local _weight = weight
246
+ -- ▼ ReadonlyArray.map ▼
247
+ local _newValue = table.create(#_items)
248
+ for _k, _v in _items do
249
+ _newValue[_k] = _weight(_v, _k - 1, _items)
250
+ end
251
+ -- ▲ ReadonlyArray.map ▲
252
+ return _exp[_self:weightedIndex(_newValue) + 1]
253
+ end
254
+ function Rng:shuffle(items)
255
+ local _array = {}
256
+ local _length = #_array
257
+ table.move(items, 1, #items, _length + 1, _array)
258
+ local result = _array
259
+ for index = #result - 1, 1, -1 do
260
+ local other = self:int(0, index)
261
+ local _index = index + 1
262
+ local _index_1 = other + 1
263
+ result[_index], result[_index_1] = result[other + 1], result[index + 1]
264
+ end
265
+ return result
266
+ end
267
+ function Rng:sample(items, count)
268
+ if count % 1 ~= 0 or count < 0 or count > #items then
269
+ error(`sample needs a count from 0 to {#items}, got {count}`, 2)
270
+ end
271
+ local _array = {}
272
+ local _length = #_array
273
+ table.move(items, 1, #items, _length + 1, _array)
274
+ local pool = _array
275
+ local result = {}
276
+ do
277
+ local index = 0
278
+ local _shouldIncrement = false
279
+ while true do
280
+ if _shouldIncrement then
281
+ index += 1
282
+ else
283
+ _shouldIncrement = true
284
+ end
285
+ if not (index < count) then
286
+ break
287
+ end
288
+ local other = self:int(index, #pool - 1)
289
+ local _index = index + 1
290
+ local _index_1 = other + 1
291
+ pool[_index], pool[_index_1] = pool[other + 1], pool[index + 1]
292
+ local _arg0 = pool[index + 1]
293
+ table.insert(result, _arg0)
294
+ end
295
+ end
296
+ return result
297
+ end
298
+ end
299
+ return {
300
+ Rng = Rng,
301
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trioforge/crucible",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "main": "out/init.luau",
6
6
  "types": "out/index.d.ts",
@@ -25,6 +25,18 @@
25
25
  "types": "./out/vide/index.d.ts",
26
26
  "default": "./out/vide/init.luau"
27
27
  },
28
+ "./util/random": {
29
+ "types": "./out/util/random/index.d.ts",
30
+ "default": "./out/util/random/init.luau"
31
+ },
32
+ "./maths/spline": {
33
+ "types": "./out/maths/spline/index.d.ts",
34
+ "default": "./out/maths/spline/init.luau"
35
+ },
36
+ "./testing": {
37
+ "types": "./out/testing/index.d.ts",
38
+ "default": "./out/testing/init.luau"
39
+ },
28
40
  "./transformer": "./transformer/index.js"
29
41
  },
30
42
  "peerDependencies": {
@@ -43,6 +55,9 @@
43
55
  "client",
44
56
  "emit",
45
57
  "vide",
58
+ "util",
59
+ "maths",
60
+ "testing",
46
61
  "LICENSE"
47
62
  ],
48
63
  "repository": {
@@ -0,0 +1,4 @@
1
+ {
2
+ "main": "../out/testing/init.luau",
3
+ "types": "../out/testing/index.d.ts"
4
+ }