@almadar/ui 5.112.0 → 5.113.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.
@@ -16072,14 +16072,16 @@ function Canvas2D({
16072
16072
  cameraPos,
16073
16073
  bgColor
16074
16074
  }) {
16075
+ const instanceId = useMemo(() => Math.random().toString(36).slice(2, 8), []);
16076
+ function isDrawableLayer(node) {
16077
+ return node.type === "draw-sprite-layer" || node.type === "draw-shape-layer" || node.type === "draw-text-layer";
16078
+ }
16075
16079
  const layerSummaries = drawables?.map((d) => {
16076
- if (!d || typeof d !== "object") return d;
16077
- const rec = d;
16078
- return { type: rec.type, itemsLen: Array.isArray(rec.items) ? rec.items.length : void 0, firstItem: Array.isArray(rec.items) ? rec.items[0] : void 0 };
16080
+ if (!isDrawableLayer(d)) return { type: d.type };
16081
+ return { type: d.type, itemsLen: d.items.length, firstItem: d.items[0] };
16079
16082
  });
16080
- console.error("[debug:Canvas2D] " + JSON.stringify({ projection, scale, cameraMode: camera, cameraPos, drawablesCount: drawables?.length, layerSummaries }));
16083
+ canvas2DLog.debug("Canvas2D render", { instanceId, projection, scale, cameraMode: camera, cameraPos: cameraPos ? JSON.stringify(cameraPos) : void 0, drawablesCount: drawables?.length, layerSummaries: layerSummaries ? JSON.stringify(layerSummaries) : void 0 });
16081
16084
  const backgroundImage = normalizeBackdrop(backgroundImageRaw);
16082
- const instanceId = useMemo(() => Math.random().toString(36).slice(2, 8), []);
16083
16085
  const isFree = projection === "free";
16084
16086
  const squareGrid = projection === "flat" || isFree || projection === "side";
16085
16087
  const layout = projection === "side" ? "free" : projection;
@@ -16214,7 +16216,7 @@ function Canvas2D({
16214
16216
  }
16215
16217
  const containerRect = containerRef.current?.getBoundingClientRect();
16216
16218
  const canvasRect = canvas.getBoundingClientRect();
16217
- console.error("[debug:Canvas2D:draw:" + instanceId + "] " + JSON.stringify({ viewportSize, baseOffsetX, cam: { x: cam.x, y: cam.y, zoom: cam.zoom }, cameraPos, containerRect: containerRect ? { width: containerRect.width, height: containerRect.height } : null, canvasRect: canvasRect ? { width: canvasRect.width, height: canvasRect.height } : null }));
16219
+ canvas2DLog.debug("Canvas2D draw", { instanceId, viewportSize, baseOffsetX, cam: { x: cam.x, y: cam.y, zoom: cam.zoom }, cameraPos: cameraPos ? JSON.stringify(cameraPos) : void 0, containerRect: containerRect ? { width: containerRect.width, height: containerRect.height } : null, canvasRect: canvasRect ? { width: canvasRect.width, height: canvasRect.height } : null });
16218
16220
  const painter = createWebPainter(ctx, bumpAtlas);
16219
16221
  painter.save();
16220
16222
  painter.translate(viewportSize.width / 2, viewportSize.height / 2);
@@ -16426,6 +16428,7 @@ function Canvas2D({
16426
16428
  }
16427
16429
  );
16428
16430
  }
16431
+ var canvas2DLog;
16429
16432
  var init_Canvas2D = __esm({
16430
16433
  "components/game/molecules/Canvas2D.tsx"() {
16431
16434
  "use client";
@@ -16447,6 +16450,7 @@ var init_Canvas2D = __esm({
16447
16450
  init_paintDispatch();
16448
16451
  init_hitTest();
16449
16452
  init_isometric();
16453
+ canvas2DLog = createLogger("almadar:ui:game-canvas");
16450
16454
  Canvas2D.displayName = "Canvas2D";
16451
16455
  }
16452
16456
  });
@@ -16485,7 +16489,7 @@ function Canvas({
16485
16489
  keyMap,
16486
16490
  keyUpMap
16487
16491
  }) {
16488
- console.error("[debug:Canvas] " + JSON.stringify({ mode, drawablesCount: drawables?.length, projection, camera }));
16492
+ canvasLog.debug("Canvas render", { mode, drawablesCount: drawables?.length, projection, camera: camera ? JSON.stringify(camera) : void 0 });
16489
16493
  const zoom = camera?.zoom;
16490
16494
  if (mode === "3d") {
16491
16495
  const props3d = {
@@ -16538,7 +16542,7 @@ function Canvas({
16538
16542
  }
16539
16543
  );
16540
16544
  }
16541
- var Canvas3DHost;
16545
+ var Canvas3DHost, canvasLog;
16542
16546
  var init_Canvas = __esm({
16543
16547
  "components/game/molecules/Canvas.tsx"() {
16544
16548
  "use client";
@@ -16546,6 +16550,7 @@ var init_Canvas = __esm({
16546
16550
  Canvas3DHost = lazy(
16547
16551
  () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
16548
16552
  );
16553
+ canvasLog = createLogger("almadar:ui:game-canvas");
16549
16554
  Canvas.displayName = "Canvas";
16550
16555
  }
16551
16556
  });
@@ -3,9 +3,10 @@ import React__default, { Component, ReactNode, ErrorInfo } from 'react';
3
3
  import { ScenePos, EventEmit, JsonObject, OrbitalSchema } from '@almadar/core';
4
4
  import * as THREE from 'three';
5
5
  import { QuadraticBezierCurve3 } from 'three';
6
- import { D as DrawableNode, I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-DKRm-nyh.cjs';
7
- export { U as UnitAnimationState } from '../../../avl-schema-parser-DKRm-nyh.cjs';
6
+ import { D as DrawableNode } from '../../../paintDispatch-DR942knx.cjs';
8
7
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
8
+ import { I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-B8Onmfsu.cjs';
9
+ export { U as UnitAnimationState } from '../../../avl-schema-parser-B8Onmfsu.cjs';
9
10
 
10
11
  /**
11
12
  * Canvas3DHost — the thin 3D draw-host: the `canvas` host's 3D painter backend
@@ -3,9 +3,10 @@ import React__default, { Component, ReactNode, ErrorInfo } from 'react';
3
3
  import { ScenePos, EventEmit, JsonObject, OrbitalSchema } from '@almadar/core';
4
4
  import * as THREE from 'three';
5
5
  import { QuadraticBezierCurve3 } from 'three';
6
- import { D as DrawableNode, I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-DKRm-nyh.js';
7
- export { U as UnitAnimationState } from '../../../avl-schema-parser-DKRm-nyh.js';
6
+ import { D as DrawableNode } from '../../../paintDispatch-DR942knx.js';
8
7
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
8
+ import { I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-B8Onmfsu.js';
9
+ export { U as UnitAnimationState } from '../../../avl-schema-parser-B8Onmfsu.js';
9
10
 
10
11
  /**
11
12
  * Canvas3DHost — the thin 3D draw-host: the `canvas` host's 3D painter backend
@@ -1,6 +1,7 @@
1
- export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseMarkdownWithCodeBlocks, B as recordServerResponse, F as recordTransition, G as registerCheck, H as registerTraitSnapshot, I as renderStateMachineToDomData, J as renderStateMachineToSvg, K as subscribeToVerification, L as updateAssetStatus, M as updateBridgeHealth, N as updateCheck, O as waitForTransition } from '../cn-BbPvQLHt.cjs';
1
+ export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseMarkdownWithCodeBlocks, B as recordServerResponse, F as recordTransition, G as registerCheck, H as registerTraitSnapshot, I as renderStateMachineToDomData, J as renderStateMachineToSvg, K as subscribeToVerification, L as updateAssetStatus, M as updateBridgeHealth, N as updateCheck, O as waitForTransition } from '../cn-BkQrMR01.cjs';
2
2
  import { FieldValue, EntityRow, EventPayload } from '@almadar/core';
3
3
  export { AssetLoadStatus, BridgeHealth, CheckStatus, EffectTrace, EventLogEntry, OrbitalVerificationAPI, ServerResponseTrace, TraitStateSnapshot, TransitionTrace, VerificationCheck, VerificationSnapshot, VerificationSummary } from '@almadar/core';
4
+ import '../paintDispatch-DR942knx.cjs';
4
5
  import 'clsx';
5
6
 
6
7
  /**
@@ -1,6 +1,7 @@
1
- export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseMarkdownWithCodeBlocks, B as recordServerResponse, F as recordTransition, G as registerCheck, H as registerTraitSnapshot, I as renderStateMachineToDomData, J as renderStateMachineToSvg, K as subscribeToVerification, L as updateAssetStatus, M as updateBridgeHealth, N as updateCheck, O as waitForTransition } from '../cn-BbPvQLHt.js';
1
+ export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseMarkdownWithCodeBlocks, B as recordServerResponse, F as recordTransition, G as registerCheck, H as registerTraitSnapshot, I as renderStateMachineToDomData, J as renderStateMachineToSvg, K as subscribeToVerification, L as updateAssetStatus, M as updateBridgeHealth, N as updateCheck, O as waitForTransition } from '../cn-CTEFm9PC.js';
2
2
  import { FieldValue, EntityRow, EventPayload } from '@almadar/core';
3
3
  export { AssetLoadStatus, BridgeHealth, CheckStatus, EffectTrace, EventLogEntry, OrbitalVerificationAPI, ServerResponseTrace, TraitStateSnapshot, TransitionTrace, VerificationCheck, VerificationSnapshot, VerificationSummary } from '@almadar/core';
4
+ import '../paintDispatch-DR942knx.js';
4
5
  import 'clsx';
5
6
 
6
7
  /**
@@ -0,0 +1,221 @@
1
+ import { ScenePos, Asset } from '@almadar/core';
2
+
3
+ /** Source sub-rectangle within a texture (an atlas frame). Omitted → whole texture. */
4
+ interface BlitSrc {
5
+ x: number;
6
+ y: number;
7
+ w: number;
8
+ h: number;
9
+ }
10
+ /** A polyline/polygon vertex in pixel space. */
11
+ interface PainterPoint {
12
+ x: number;
13
+ y: number;
14
+ }
15
+ /** A drop shadow applied to subsequent draws until cleared. */
16
+ interface PainterShadow {
17
+ color: string;
18
+ blur: number;
19
+ }
20
+
21
+ /**
22
+ * Drawable contract — the shared vocabulary every neutral drawable primitive
23
+ * (the `draw-sprite`/`draw-shape`/`draw-text` atoms, `draw-sprite-layer`/
24
+ * `draw-shape-layer` molecules) is authored against. A drawable is a pure
25
+ * descriptor the canvas HOST walks and paints via the portable {@link Painter2D}
26
+ * seam — no `CanvasRenderingContext2D`, no DOM. Genre (a "unit"/"tile"/
27
+ * "highlight") is a `.lolo` COMPOSITION of these, never its own primitive.
28
+ *
29
+ * A position is a core `ScenePos` (logical scene space). The host's {@link Projector}
30
+ * maps it to pixels; primitives never do projection math themselves. Because the
31
+ * `ScenePos` type identity is what pattern-sync stamps as the `drawable`
32
+ * capability, every drawable descriptor's `position` MUST be a core `ScenePos`.
33
+ */
34
+
35
+ /**
36
+ * How a drawable aligns to its projected position.
37
+ * - `top-left` — the cell's top-left corner (tiles).
38
+ * - `ground` — the cell's ground point, bottom-center of the drawable (units/features).
39
+ * - `center` — the cell's visual center (effects).
40
+ */
41
+ type DrawableAnchor = 'top-left' | 'ground' | 'center';
42
+ /** Common shape of every drawable descriptor. `type` keys the host's paint dispatch. */
43
+ interface DrawableBase {
44
+ type: string;
45
+ /**
46
+ * Optional hit-test handle. When a board authors a per-entity id here (e.g.
47
+ * `id: (object/get @u id)` on a unit sprite), the canvas host indexes the
48
+ * descriptor's `ScenePos → id` and resolves a pointer/raycast at that cell to
49
+ * this id — the source of `unitClickEvent {unitId}`. A source-tagged handle,
50
+ * NOT a heuristic: no id → the host emits only the coordinate (tile click).
51
+ */
52
+ id?: string;
53
+ }
54
+
55
+ /**
56
+ * `draw-sprite` — the neutral image/atlas-frame drawable atom (dimension-agnostic).
57
+ *
58
+ * ONE descriptor (`DrawSpriteProps`, grounded in core `ScenePos` + `Asset`) with
59
+ * TWO backends: `paintSprite` (2D, here — via the portable `Painter2D` seam) and
60
+ * an R3F mesh (`Sprite3D` in `lib/drawable/mesh3d`, kept OUT of this file so the
61
+ * 2D paint path never pulls `three`/`drei` into an app bundle — the codebase
62
+ * code-splits R3F). The canvas host picks the backend by `mode`; that is what
63
+ * makes canvas-2d and canvas-3d the same `children` interface. Genre uses (tile,
64
+ * unit body, feature, effect, movement ghost, background cover) are all this atom
65
+ * with different `anchor`/`size`/`frame`/`flip`/`shadow` — composed in `.lolo`,
66
+ * not here. The React component renders `null` (a drawable is painted by the host,
67
+ * not the DOM); it exists so the pattern pipeline registers a `draw-sprite`
68
+ * pattern and standalone pages stay inspectable.
69
+ */
70
+
71
+ interface DrawSpriteProps extends DrawableBase {
72
+ type: 'draw-sprite';
73
+ /** Logical scene position; the projector maps it to pixels / world. */
74
+ position: ScenePos;
75
+ /** The image / atlas reference to blit. */
76
+ asset: Asset;
77
+ /** How the sprite aligns to its projected position. Default `'top-left'`. */
78
+ anchor?: DrawableAnchor;
79
+ /** Draw width in world units (fractions of `projector.tileWidth`); omitted → resolved source width in px. */
80
+ width?: number;
81
+ /** Draw height in world units (fractions of `projector.tileWidth`); omitted → resolved source height in px. */
82
+ height?: number;
83
+ /** Explicit atlas sub-rect override (px); omitted → resolved from `asset.atlas`/`asset.sprite`. */
84
+ frame?: BlitSrc;
85
+ /** Mirror horizontally (facing). */
86
+ flipX?: boolean;
87
+ /** Rotation in radians about the sprite's center. */
88
+ rotation?: number;
89
+ /** 0..1 opacity. */
90
+ opacity?: number;
91
+ /** Drop shadow (e.g. a team-colored glow). */
92
+ shadow?: PainterShadow;
93
+ }
94
+
95
+ /**
96
+ * `draw-shape` — the neutral vector-fill/stroke drawable atom (dimension-agnostic).
97
+ *
98
+ * ONE descriptor (`DrawShapeProps`, grounded in core `ScenePos`) with TWO
99
+ * backends: `paintShape` (2D, here) and an R3F ground-plane mesh (`Shape3D` in
100
+ * `lib/drawable/mesh3d`, kept OUT of this file so the 2D path never pulls R3F).
101
+ * Paints a cell footprint, an axis-aligned rect, an ellipse, or a poly at a scene
102
+ * position, fill and/or stroke. Genre uses (tile fallback, iso/hex diamond
103
+ * fallback, cell highlight, feature disc, unit selection ring, unit fallback
104
+ * circle, ground/ghost discs, solid backgrounds, side-view platform rects) are
105
+ * all this atom with different `shape`/`anchor`/geometry — composed in `.lolo`,
106
+ * not here. The React component renders `null`; it exists so the pattern pipeline
107
+ * registers a `draw-shape` pattern and standalone pages stay inspectable.
108
+ */
109
+
110
+ type ShapeKind = 'cell' | 'rect' | 'ellipse' | 'poly';
111
+ interface DrawShapeProps extends DrawableBase {
112
+ type: 'draw-shape';
113
+ shape: ShapeKind;
114
+ /** Logical scene position; the projector maps it to pixels / world. */
115
+ position: ScenePos;
116
+ /** How the shape aligns to its projected position. Default varies by `shape`. */
117
+ anchor?: DrawableAnchor;
118
+ /** Rect width in world units (fractions of `projector.tileWidth`). */
119
+ width?: number;
120
+ /** Rect height in world units (fractions of `projector.tileWidth`). */
121
+ height?: number;
122
+ /** Ellipse horizontal radius in world units (fractions of `projector.tileWidth`). */
123
+ radiusX?: number;
124
+ /** Ellipse vertical radius in world units; omitted → `radiusX` (a circle). */
125
+ radiusY?: number;
126
+ /** Fine nudge from the anchor point in world units. */
127
+ offsetX?: number;
128
+ offsetY?: number;
129
+ /** Poly vertices as world-unit offsets relative to the cell's projected top-left. */
130
+ points?: PainterPoint[];
131
+ fill?: string;
132
+ stroke?: string;
133
+ strokeWidth?: number;
134
+ /** 0..1 opacity. */
135
+ opacity?: number;
136
+ }
137
+
138
+ /**
139
+ * `draw-text` — the neutral text drawable atom (dimension-agnostic).
140
+ *
141
+ * ONE descriptor (`DrawTextProps`, grounded in core `ScenePos`) with TWO backends:
142
+ * `paintText` (2D, here) and an R3F billboarded `<Text>` (`Text3D` in
143
+ * `lib/drawable/mesh3d`, kept OUT of this file so the 2D path never pulls R3F).
144
+ * Paints a string at a scene position with font/color/alignment. The React
145
+ * component renders `null` (a drawable is painted by the host, not the DOM).
146
+ */
147
+
148
+ interface DrawTextProps extends DrawableBase {
149
+ type: 'draw-text';
150
+ text: string;
151
+ /** Logical scene position; the projector maps it to pixels / world. */
152
+ position: ScenePos;
153
+ anchor?: DrawableAnchor;
154
+ offsetX?: number;
155
+ offsetY?: number;
156
+ color: string;
157
+ font?: string;
158
+ align?: CanvasTextAlign;
159
+ baseline?: CanvasTextBaseline;
160
+ opacity?: number;
161
+ }
162
+
163
+ /**
164
+ * `draw-sprite-layer` — batch multiple sprites in one descriptor for O(layers) rendering.
165
+ *
166
+ * Composes the `draw-sprite` atom; the layer itself carries no `position` — each
167
+ * item does. It is drawable-by-composition: its `items` are `draw-sprite`
168
+ * descriptors (each grounded in core `ScenePos`), which is what stamps the layer
169
+ * `drawable` at pattern-sync time.
170
+ */
171
+
172
+ interface DrawSpriteLayerProps extends DrawableBase {
173
+ type: 'draw-sprite-layer';
174
+ /** Sprites painted in array order; z-ordering is the caller's responsibility. */
175
+ items: DrawSpriteProps[];
176
+ }
177
+
178
+ /**
179
+ * `draw-shape-layer` — batch of shapes in one descriptor for O(layers) perf.
180
+ *
181
+ * Composes the `draw-shape` atom; drawable-by-composition (its `items` are
182
+ * `draw-shape` descriptors, each grounded in core `ScenePos`). The React
183
+ * component renders `null` (a drawable is painted by the host, not the DOM).
184
+ */
185
+
186
+ interface DrawShapeLayerProps extends DrawableBase {
187
+ type: 'draw-shape-layer';
188
+ items: DrawShapeProps[];
189
+ }
190
+
191
+ /**
192
+ * `draw-text-layer` — batch of text labels in one descriptor for O(layers) perf.
193
+ *
194
+ * Composes the `draw-text` atom; drawable-by-composition (its `items` are
195
+ * `draw-text` descriptors, each grounded in core `ScenePos`). Covers the batched
196
+ * world-space text pass — fx messages, damage numbers, unit labels — that a
197
+ * board maps from an entity array (`items: (array/map @entity.fx (fn ...))`),
198
+ * which a literal `children` array can't express. The React component renders
199
+ * `null` (a drawable is painted by the host, not the DOM).
200
+ */
201
+
202
+ interface DrawTextLayerProps extends DrawableBase {
203
+ type: 'draw-text-layer';
204
+ items: DrawTextProps[];
205
+ }
206
+
207
+ /**
208
+ * Drawable paint dispatch (2D) + the `DrawableNode` union.
209
+ *
210
+ * `paintDrawable` routes ONE descriptor to its 2D painter; the host walks its
211
+ * `children` through it. The "drawable" designation itself is NOT recorded here —
212
+ * it is DERIVED from the core `ScenePos` type each descriptor's `position` uses
213
+ * and stamped into `patterns-registry.json` by pattern-sync (mirroring how `Asset`
214
+ * is tagged), then read by the orbital-rust validator. So this module is pure
215
+ * paint routing; the capability is the registry's, not a hand-list.
216
+ */
217
+
218
+ /** Every drawable descriptor. The host's `children` are a `DrawableNode[]`. */
219
+ type DrawableNode = DrawSpriteProps | DrawShapeProps | DrawTextProps | DrawSpriteLayerProps | DrawShapeLayerProps | DrawTextLayerProps;
220
+
221
+ export type { DrawableNode as D };
@@ -0,0 +1,221 @@
1
+ import { ScenePos, Asset } from '@almadar/core';
2
+
3
+ /** Source sub-rectangle within a texture (an atlas frame). Omitted → whole texture. */
4
+ interface BlitSrc {
5
+ x: number;
6
+ y: number;
7
+ w: number;
8
+ h: number;
9
+ }
10
+ /** A polyline/polygon vertex in pixel space. */
11
+ interface PainterPoint {
12
+ x: number;
13
+ y: number;
14
+ }
15
+ /** A drop shadow applied to subsequent draws until cleared. */
16
+ interface PainterShadow {
17
+ color: string;
18
+ blur: number;
19
+ }
20
+
21
+ /**
22
+ * Drawable contract — the shared vocabulary every neutral drawable primitive
23
+ * (the `draw-sprite`/`draw-shape`/`draw-text` atoms, `draw-sprite-layer`/
24
+ * `draw-shape-layer` molecules) is authored against. A drawable is a pure
25
+ * descriptor the canvas HOST walks and paints via the portable {@link Painter2D}
26
+ * seam — no `CanvasRenderingContext2D`, no DOM. Genre (a "unit"/"tile"/
27
+ * "highlight") is a `.lolo` COMPOSITION of these, never its own primitive.
28
+ *
29
+ * A position is a core `ScenePos` (logical scene space). The host's {@link Projector}
30
+ * maps it to pixels; primitives never do projection math themselves. Because the
31
+ * `ScenePos` type identity is what pattern-sync stamps as the `drawable`
32
+ * capability, every drawable descriptor's `position` MUST be a core `ScenePos`.
33
+ */
34
+
35
+ /**
36
+ * How a drawable aligns to its projected position.
37
+ * - `top-left` — the cell's top-left corner (tiles).
38
+ * - `ground` — the cell's ground point, bottom-center of the drawable (units/features).
39
+ * - `center` — the cell's visual center (effects).
40
+ */
41
+ type DrawableAnchor = 'top-left' | 'ground' | 'center';
42
+ /** Common shape of every drawable descriptor. `type` keys the host's paint dispatch. */
43
+ interface DrawableBase {
44
+ type: string;
45
+ /**
46
+ * Optional hit-test handle. When a board authors a per-entity id here (e.g.
47
+ * `id: (object/get @u id)` on a unit sprite), the canvas host indexes the
48
+ * descriptor's `ScenePos → id` and resolves a pointer/raycast at that cell to
49
+ * this id — the source of `unitClickEvent {unitId}`. A source-tagged handle,
50
+ * NOT a heuristic: no id → the host emits only the coordinate (tile click).
51
+ */
52
+ id?: string;
53
+ }
54
+
55
+ /**
56
+ * `draw-sprite` — the neutral image/atlas-frame drawable atom (dimension-agnostic).
57
+ *
58
+ * ONE descriptor (`DrawSpriteProps`, grounded in core `ScenePos` + `Asset`) with
59
+ * TWO backends: `paintSprite` (2D, here — via the portable `Painter2D` seam) and
60
+ * an R3F mesh (`Sprite3D` in `lib/drawable/mesh3d`, kept OUT of this file so the
61
+ * 2D paint path never pulls `three`/`drei` into an app bundle — the codebase
62
+ * code-splits R3F). The canvas host picks the backend by `mode`; that is what
63
+ * makes canvas-2d and canvas-3d the same `children` interface. Genre uses (tile,
64
+ * unit body, feature, effect, movement ghost, background cover) are all this atom
65
+ * with different `anchor`/`size`/`frame`/`flip`/`shadow` — composed in `.lolo`,
66
+ * not here. The React component renders `null` (a drawable is painted by the host,
67
+ * not the DOM); it exists so the pattern pipeline registers a `draw-sprite`
68
+ * pattern and standalone pages stay inspectable.
69
+ */
70
+
71
+ interface DrawSpriteProps extends DrawableBase {
72
+ type: 'draw-sprite';
73
+ /** Logical scene position; the projector maps it to pixels / world. */
74
+ position: ScenePos;
75
+ /** The image / atlas reference to blit. */
76
+ asset: Asset;
77
+ /** How the sprite aligns to its projected position. Default `'top-left'`. */
78
+ anchor?: DrawableAnchor;
79
+ /** Draw width in world units (fractions of `projector.tileWidth`); omitted → resolved source width in px. */
80
+ width?: number;
81
+ /** Draw height in world units (fractions of `projector.tileWidth`); omitted → resolved source height in px. */
82
+ height?: number;
83
+ /** Explicit atlas sub-rect override (px); omitted → resolved from `asset.atlas`/`asset.sprite`. */
84
+ frame?: BlitSrc;
85
+ /** Mirror horizontally (facing). */
86
+ flipX?: boolean;
87
+ /** Rotation in radians about the sprite's center. */
88
+ rotation?: number;
89
+ /** 0..1 opacity. */
90
+ opacity?: number;
91
+ /** Drop shadow (e.g. a team-colored glow). */
92
+ shadow?: PainterShadow;
93
+ }
94
+
95
+ /**
96
+ * `draw-shape` — the neutral vector-fill/stroke drawable atom (dimension-agnostic).
97
+ *
98
+ * ONE descriptor (`DrawShapeProps`, grounded in core `ScenePos`) with TWO
99
+ * backends: `paintShape` (2D, here) and an R3F ground-plane mesh (`Shape3D` in
100
+ * `lib/drawable/mesh3d`, kept OUT of this file so the 2D path never pulls R3F).
101
+ * Paints a cell footprint, an axis-aligned rect, an ellipse, or a poly at a scene
102
+ * position, fill and/or stroke. Genre uses (tile fallback, iso/hex diamond
103
+ * fallback, cell highlight, feature disc, unit selection ring, unit fallback
104
+ * circle, ground/ghost discs, solid backgrounds, side-view platform rects) are
105
+ * all this atom with different `shape`/`anchor`/geometry — composed in `.lolo`,
106
+ * not here. The React component renders `null`; it exists so the pattern pipeline
107
+ * registers a `draw-shape` pattern and standalone pages stay inspectable.
108
+ */
109
+
110
+ type ShapeKind = 'cell' | 'rect' | 'ellipse' | 'poly';
111
+ interface DrawShapeProps extends DrawableBase {
112
+ type: 'draw-shape';
113
+ shape: ShapeKind;
114
+ /** Logical scene position; the projector maps it to pixels / world. */
115
+ position: ScenePos;
116
+ /** How the shape aligns to its projected position. Default varies by `shape`. */
117
+ anchor?: DrawableAnchor;
118
+ /** Rect width in world units (fractions of `projector.tileWidth`). */
119
+ width?: number;
120
+ /** Rect height in world units (fractions of `projector.tileWidth`). */
121
+ height?: number;
122
+ /** Ellipse horizontal radius in world units (fractions of `projector.tileWidth`). */
123
+ radiusX?: number;
124
+ /** Ellipse vertical radius in world units; omitted → `radiusX` (a circle). */
125
+ radiusY?: number;
126
+ /** Fine nudge from the anchor point in world units. */
127
+ offsetX?: number;
128
+ offsetY?: number;
129
+ /** Poly vertices as world-unit offsets relative to the cell's projected top-left. */
130
+ points?: PainterPoint[];
131
+ fill?: string;
132
+ stroke?: string;
133
+ strokeWidth?: number;
134
+ /** 0..1 opacity. */
135
+ opacity?: number;
136
+ }
137
+
138
+ /**
139
+ * `draw-text` — the neutral text drawable atom (dimension-agnostic).
140
+ *
141
+ * ONE descriptor (`DrawTextProps`, grounded in core `ScenePos`) with TWO backends:
142
+ * `paintText` (2D, here) and an R3F billboarded `<Text>` (`Text3D` in
143
+ * `lib/drawable/mesh3d`, kept OUT of this file so the 2D path never pulls R3F).
144
+ * Paints a string at a scene position with font/color/alignment. The React
145
+ * component renders `null` (a drawable is painted by the host, not the DOM).
146
+ */
147
+
148
+ interface DrawTextProps extends DrawableBase {
149
+ type: 'draw-text';
150
+ text: string;
151
+ /** Logical scene position; the projector maps it to pixels / world. */
152
+ position: ScenePos;
153
+ anchor?: DrawableAnchor;
154
+ offsetX?: number;
155
+ offsetY?: number;
156
+ color: string;
157
+ font?: string;
158
+ align?: CanvasTextAlign;
159
+ baseline?: CanvasTextBaseline;
160
+ opacity?: number;
161
+ }
162
+
163
+ /**
164
+ * `draw-sprite-layer` — batch multiple sprites in one descriptor for O(layers) rendering.
165
+ *
166
+ * Composes the `draw-sprite` atom; the layer itself carries no `position` — each
167
+ * item does. It is drawable-by-composition: its `items` are `draw-sprite`
168
+ * descriptors (each grounded in core `ScenePos`), which is what stamps the layer
169
+ * `drawable` at pattern-sync time.
170
+ */
171
+
172
+ interface DrawSpriteLayerProps extends DrawableBase {
173
+ type: 'draw-sprite-layer';
174
+ /** Sprites painted in array order; z-ordering is the caller's responsibility. */
175
+ items: DrawSpriteProps[];
176
+ }
177
+
178
+ /**
179
+ * `draw-shape-layer` — batch of shapes in one descriptor for O(layers) perf.
180
+ *
181
+ * Composes the `draw-shape` atom; drawable-by-composition (its `items` are
182
+ * `draw-shape` descriptors, each grounded in core `ScenePos`). The React
183
+ * component renders `null` (a drawable is painted by the host, not the DOM).
184
+ */
185
+
186
+ interface DrawShapeLayerProps extends DrawableBase {
187
+ type: 'draw-shape-layer';
188
+ items: DrawShapeProps[];
189
+ }
190
+
191
+ /**
192
+ * `draw-text-layer` — batch of text labels in one descriptor for O(layers) perf.
193
+ *
194
+ * Composes the `draw-text` atom; drawable-by-composition (its `items` are
195
+ * `draw-text` descriptors, each grounded in core `ScenePos`). Covers the batched
196
+ * world-space text pass — fx messages, damage numbers, unit labels — that a
197
+ * board maps from an entity array (`items: (array/map @entity.fx (fn ...))`),
198
+ * which a literal `children` array can't express. The React component renders
199
+ * `null` (a drawable is painted by the host, not the DOM).
200
+ */
201
+
202
+ interface DrawTextLayerProps extends DrawableBase {
203
+ type: 'draw-text-layer';
204
+ items: DrawTextProps[];
205
+ }
206
+
207
+ /**
208
+ * Drawable paint dispatch (2D) + the `DrawableNode` union.
209
+ *
210
+ * `paintDrawable` routes ONE descriptor to its 2D painter; the host walks its
211
+ * `children` through it. The "drawable" designation itself is NOT recorded here —
212
+ * it is DERIVED from the core `ScenePos` type each descriptor's `position` uses
213
+ * and stamped into `patterns-registry.json` by pattern-sync (mirroring how `Asset`
214
+ * is tagged), then read by the orbital-rust validator. So this module is pure
215
+ * paint routing; the capability is the registry's, not a hand-list.
216
+ */
217
+
218
+ /** Every drawable descriptor. The host's `children` are a `DrawableNode[]`. */
219
+ type DrawableNode = DrawSpriteProps | DrawShapeProps | DrawTextProps | DrawSpriteLayerProps | DrawShapeLayerProps | DrawTextLayerProps;
220
+
221
+ export type { DrawableNode as D };
@@ -9503,14 +9503,16 @@ function Canvas2D({
9503
9503
  cameraPos,
9504
9504
  bgColor
9505
9505
  }) {
9506
+ const instanceId = React83.useMemo(() => Math.random().toString(36).slice(2, 8), []);
9507
+ function isDrawableLayer(node) {
9508
+ return node.type === "draw-sprite-layer" || node.type === "draw-shape-layer" || node.type === "draw-text-layer";
9509
+ }
9506
9510
  const layerSummaries = drawables?.map((d) => {
9507
- if (!d || typeof d !== "object") return d;
9508
- const rec = d;
9509
- return { type: rec.type, itemsLen: Array.isArray(rec.items) ? rec.items.length : void 0, firstItem: Array.isArray(rec.items) ? rec.items[0] : void 0 };
9511
+ if (!isDrawableLayer(d)) return { type: d.type };
9512
+ return { type: d.type, itemsLen: d.items.length, firstItem: d.items[0] };
9510
9513
  });
9511
- console.error("[debug:Canvas2D] " + JSON.stringify({ projection, scale, cameraMode: camera, cameraPos, drawablesCount: drawables?.length, layerSummaries }));
9514
+ canvas2DLog.debug("Canvas2D render", { instanceId, projection, scale, cameraMode: camera, cameraPos: cameraPos ? JSON.stringify(cameraPos) : void 0, drawablesCount: drawables?.length, layerSummaries: layerSummaries ? JSON.stringify(layerSummaries) : void 0 });
9512
9515
  const backgroundImage = normalizeBackdrop(backgroundImageRaw);
9513
- const instanceId = React83.useMemo(() => Math.random().toString(36).slice(2, 8), []);
9514
9516
  const isFree = projection === "free";
9515
9517
  const squareGrid = projection === "flat" || isFree || projection === "side";
9516
9518
  const layout = projection === "side" ? "free" : projection;
@@ -9645,7 +9647,7 @@ function Canvas2D({
9645
9647
  }
9646
9648
  const containerRect = containerRef.current?.getBoundingClientRect();
9647
9649
  const canvasRect = canvas.getBoundingClientRect();
9648
- console.error("[debug:Canvas2D:draw:" + instanceId + "] " + JSON.stringify({ viewportSize, baseOffsetX, cam: { x: cam.x, y: cam.y, zoom: cam.zoom }, cameraPos, containerRect: containerRect ? { width: containerRect.width, height: containerRect.height } : null, canvasRect: canvasRect ? { width: canvasRect.width, height: canvasRect.height } : null }));
9650
+ canvas2DLog.debug("Canvas2D draw", { instanceId, viewportSize, baseOffsetX, cam: { x: cam.x, y: cam.y, zoom: cam.zoom }, cameraPos: cameraPos ? JSON.stringify(cameraPos) : void 0, containerRect: containerRect ? { width: containerRect.width, height: containerRect.height } : null, canvasRect: canvasRect ? { width: canvasRect.width, height: canvasRect.height } : null });
9649
9651
  const painter = createWebPainter(ctx, bumpAtlas);
9650
9652
  painter.save();
9651
9653
  painter.translate(viewportSize.width / 2, viewportSize.height / 2);
@@ -9857,6 +9859,7 @@ function Canvas2D({
9857
9859
  }
9858
9860
  );
9859
9861
  }
9862
+ var canvas2DLog;
9860
9863
  var init_Canvas2D = __esm({
9861
9864
  "components/game/molecules/Canvas2D.tsx"() {
9862
9865
  "use client";
@@ -9878,6 +9881,7 @@ var init_Canvas2D = __esm({
9878
9881
  init_paintDispatch();
9879
9882
  init_hitTest();
9880
9883
  init_isometric();
9884
+ canvas2DLog = logger.createLogger("almadar:ui:game-canvas");
9881
9885
  Canvas2D.displayName = "Canvas2D";
9882
9886
  }
9883
9887
  });
@@ -9916,7 +9920,7 @@ function Canvas({
9916
9920
  keyMap,
9917
9921
  keyUpMap
9918
9922
  }) {
9919
- console.error("[debug:Canvas] " + JSON.stringify({ mode, drawablesCount: drawables?.length, projection, camera }));
9923
+ canvasLog.debug("Canvas render", { mode, drawablesCount: drawables?.length, projection, camera: camera ? JSON.stringify(camera) : void 0 });
9920
9924
  const zoom = camera?.zoom;
9921
9925
  if (mode === "3d") {
9922
9926
  const props3d = {
@@ -9969,7 +9973,7 @@ function Canvas({
9969
9973
  }
9970
9974
  );
9971
9975
  }
9972
- var Canvas3DHost;
9976
+ var Canvas3DHost, canvasLog;
9973
9977
  var init_Canvas = __esm({
9974
9978
  "components/game/molecules/Canvas.tsx"() {
9975
9979
  "use client";
@@ -9977,6 +9981,7 @@ var init_Canvas = __esm({
9977
9981
  Canvas3DHost = React83.lazy(
9978
9982
  () => import('@almadar/ui/components/molecules/game/three').then((m) => ({ default: m.Canvas3DHost }))
9979
9983
  );
9984
+ canvasLog = logger.createLogger("almadar:ui:game-canvas");
9980
9985
  Canvas.displayName = "Canvas";
9981
9986
  }
9982
9987
  });