@solidrt/core 0.0.52 → 0.0.54

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/AGENTS.md CHANGED
@@ -111,7 +111,12 @@ tsconfig.json - the two load-bearing lines are jsx + jsxImportSource:
111
111
  ```
112
112
 
113
113
  Peer deps @solidjs/signals and @solidjs/universal must match (currently
114
- 2.0.0-rc.1); bun resolves them from peerDependencies.
114
+ 2.0.0-rc.3); bun resolves them from peerDependencies. On a version bump,
115
+ `bun install` only warns ("incorrect peer dependency") and keeps the old
116
+ ones, and `bun update solid-js @solidjs/signals @solidjs/universal` does
117
+ not read the pin either: it adds all three to package.json at registry
118
+ latest (solid-js 1.x, off Solid 2.0 entirely). The recipe that works is
119
+ `rm -rf bun.lock node_modules/solid-js node_modules/@solidjs && bun install`.
115
120
 
116
121
  ## Element model (the parts that are easy to get wrong)
117
122
 
@@ -147,6 +152,13 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
147
152
  endpoint props and spans its layout box corner to corner. `points` (a flat
148
153
  `[x0, y0, x1, y1, ...]` array, plus `closed`) turns either form into a
149
154
  polyline and wins over the endpoints while set.
155
+ - Nesting rule: d-* elements go under anything; laid-out elements only under
156
+ laid-out parents. A detached subtree is entirely detached, and the insert
157
+ throws otherwise (`<view> cannot be a child of <d-view>: ...`). So a
158
+ component that renders any laid-out element is a LAYOUT component and
159
+ cannot be used inside a d-* subtree, and a component meant for d-* trees
160
+ renders d-* only. Say which in the component's doc comment: the types
161
+ cannot tell (every JSX expression is the same `Element`).
150
162
 
151
163
  - Plain vs `d-` variant (the `d-` prefix means "detached" - detached from the
152
164
  layout engine, Taffy): a plain element (e.g. `rect`) is `RectProps &
@@ -267,11 +279,18 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
267
279
  `children()` helper (re-exported from @solidrt/core) and probe the resolved
268
280
  memo - never `typeof props.children` on the raw prop.
269
281
 
282
+ - Keep the simulation out of the renderer: app or game state as plain
283
+ TypeScript with no @solidrt imports, with the JSX reading it and the
284
+ per-frame hook doing one state step plus a handful of property writes.
285
+ That shape runs under bun with no window (tests, audits, replaying a whole
286
+ session in seconds) and it is what keeps per-frame work cheap.
270
287
  - Animation is target-shaped first: declare `transition` on the element and
271
288
  write targets, and the runtime animates natively with no per-frame JS.
272
289
  Reach for per-frame work only for genuinely procedural motion:
273
- `onFrame((tick, frame) => {})` is the native hook (runtime-paced, returns a
274
- cleanup, auto-cleaned inside a reactive scope); `requestAnimationFrame`
290
+ `onFrame((tick, frame, rate) => {})` is the native hook (runtime-paced,
291
+ returns a cleanup, auto-cleaned inside a reactive scope); `rate` is the
292
+ display's nominal refresh rate in Hz, which a fixed-timestep loop needs
293
+ (see @solidrt/cli agents/debugging.md on stepping); `requestAnimationFrame`
275
294
  exists as a web-standard one-shot but is not the preferred driver. A JS
276
295
  tween loop or an animation library pushing interpolated values through
277
296
  signals is the single most expensive mistake available here - read
@@ -294,7 +313,10 @@ Peer deps @solidjs/signals and @solidjs/universal must match (currently
294
313
 
295
314
  - Device/GPU access via subpath imports: @solidrt/core/camera, /microphone,
296
315
  /speech, /gpu. Image flow: `decodeImage(bytes)` ->
297
- `createTexture(data,w,h)` -> `<texture src={id} />`.
316
+ `createTexture(data,w,h)` -> `<texture src={id} />`. Pixels are
317
+ premultiplied alpha from decode onward (the GPU contract); `decodeImage(bytes,
318
+ { alpha: "straight" })` keeps the file's color under transparent pixels for
319
+ CPU work, and `encodeImage` converts back to straight for the file.
298
320
 
299
321
  ## Minimal app, core primitives only (verified to render)
300
322
 
@@ -167,7 +167,10 @@ Three facts hold for every texture and target:
167
167
  a top-left origin.
168
168
  - **Color is premultiplied alpha.** A target's RGB is already multiplied by
169
169
  its A: write `vec4(rgb * a, a)`. `vec4(rgb, a)` composites as opaque.
170
- `clearColor` is premultiplied too.
170
+ `clearColor` is premultiplied too, and so are uploaded pixels:
171
+ `decodeImage` premultiplies at the codec boundary (image files store
172
+ straight alpha) and `encodeImage` converts back, so pixels inside the app
173
+ are premultiplied everywhere.
171
174
  - **Values are non-linear RGBA8** (or `"r8"` for single-channel data), with
172
175
  no color-space conversion anywhere. Filtering and blending operate on the
173
176
  stored values.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidrt/core",
3
- "version": "0.0.52",
3
+ "version": "0.0.54",
4
4
  "license": "MIT",
5
5
  "funding": "https://github.com/sponsors/wellawaretech",
6
6
  "author": "Antoine van Wel",
@@ -29,11 +29,11 @@
29
29
  "AGENTS.md"
30
30
  ],
31
31
  "devDependencies": {
32
- "@solidrt/flux-types": "0.0.52"
32
+ "@solidrt/flux-types": "0.0.54"
33
33
  },
34
34
  "peerDependencies": {
35
- "@solidjs/signals": "2.0.0-rc.1",
36
- "@solidjs/universal": "2.0.0-rc.1",
37
- "solid-js": "2.0.0-rc.1"
35
+ "@solidjs/signals": "2.0.0-rc.3",
36
+ "@solidjs/universal": "2.0.0-rc.3",
37
+ "solid-js": "2.0.0-rc.3"
38
38
  }
39
39
  }
package/src/gpu.ts CHANGED
@@ -42,7 +42,10 @@
42
42
  // multiplied by its A - `vec4(rgb * a, a)`, not `vec4(rgb, a)`, which
43
43
  // composites as opaque. That is what Impeller composites and what
44
44
  // `<texture blendMode>` blends; `clearColor` is premultiplied too, so the
45
- // default transparent black needs no thought.
45
+ // default transparent black needs no thought. Uploaded pixels follow the
46
+ // same rule: `decodeImage` premultiplies at the codec boundary (image
47
+ // files store straight alpha) and `encodeImage` undoes it, so pixels
48
+ // inside the app are premultiplied everywhere.
46
49
  // - Values are non-linear RGBA8, with no color-space concept. Every texture
47
50
  // and target holds 8-bit RGBA UNORM exactly as written; nothing converts to
48
51
  // or from linear light. `filter: "linear"` averages and the `blend` modes
@@ -110,6 +113,7 @@ export {
110
113
  endBufferWrite,
111
114
  resizeTexture,
112
115
  setTargetParams,
116
+ setTargetRect,
113
117
  setTargetSize,
114
118
  setTargetTextures,
115
119
  uploadTexture,
@@ -213,7 +217,9 @@ export { captureSnapshot, readTexture } from "flux:gpu"
213
217
  * Uploads raw pixels to an immutable GPU texture and returns its id (use it
214
218
  * as `<texture src={id} />`). `data` must be exactly `width * height` pixels
215
219
  * at the declared format's size (`* 4` bytes for the default "rgba8", `* 1`
216
- * for "r8"); a mismatch throws. For pixels you intend to mutate and
220
+ * for "r8"); a mismatch throws. RGBA data is uploaded verbatim and composited
221
+ * as premultiplied alpha, like every texture: `decodeImage` and the readback
222
+ * calls already deliver that, and hand-built pixels must too (`rgb * a`). For pixels you intend to mutate and
217
223
  * re-upload, use `createMutableTexture` instead. When called inside a
218
224
  * reactive scope the texture is freed automatically once that owner is
219
225
  * disposed; when called outside one (e.g. after an `await`, where the owner
@@ -389,6 +395,13 @@ export function createShaderTarget(
389
395
  * driven later with `setTargetTextures`, same precedence and coverage
390
396
  * rules.
391
397
  *
398
+ * `into` makes a sub-target: a draw target rendering into the rectangle at
399
+ * `x`/`y` (top-left origin) of draw target `into`'s storage, so N views or
400
+ * N shadow maps share one texture and ONE pass; the id is a draw target to
401
+ * every verb but not a texture (display and sample the parent, with
402
+ * `srcX`/`srcY` on the leaf), and `setTargetRect` moves it. Auto-free works
403
+ * the same way; destroying the parent takes its tiles.
404
+ *
392
405
  * The render contract is unchanged: the list is input data, so an ordinary
393
406
  * (`render: "auto"`) draw target re-renders exactly when its entries or
394
407
  * their inputs change - a static scene costs zero passes, and one render is
@@ -408,6 +421,9 @@ export function createDrawTarget(
408
421
  render?: "auto" | "manual"
409
422
  loadOp?: "clear" | "load"
410
423
  samples?: 1 | 2 | 4 | 8
424
+ into?: gpu.TextureId
425
+ x?: number
426
+ y?: number
411
427
  } & CreateOptions &
412
428
  SamplerOptions,
413
429
  ): gpu.TextureId {
package/src/renderer.ts CHANGED
@@ -266,6 +266,14 @@ let renderer = createRenderer<ProxyNode>({
266
266
  pendingDestroy.delete(node.id)
267
267
 
268
268
  if (parent) {
269
+ // console.debug("[srt] insertNode", parent.id, node.id, anchor?.id ?? "")
270
+
271
+ // Native first: the tree refuses a laid-out element under a d-* parent
272
+ // (it throws, naming both tags), and the mirror must not record a child
273
+ // the tree does not have.
274
+ if (anchor) tree.insertNode(parent.id, node.id, anchor.id)
275
+ else tree.insertNode(parent.id, node.id)
276
+
269
277
  node.parent = parent
270
278
 
271
279
  if (!anchor) {
@@ -278,11 +286,6 @@ let renderer = createRenderer<ProxyNode>({
278
286
  parent.children.splice(index, 0, node)
279
287
  }
280
288
  }
281
-
282
- // console.debug("[srt] insertNode", parent.id, node.id, anchor?.id ?? "")
283
-
284
- if (anchor) tree.insertNode(parent.id, node.id, anchor.id)
285
- else tree.insertNode(parent.id, node.id)
286
289
  }
287
290
  },
288
291