bard-legends-framework 1.5.1 → 1.5.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/AGENTS.md +351 -0
- package/package.json +3 -2
package/AGENTS.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
# bard-legends-framework — AI Agent Guide
|
|
2
|
+
|
|
3
|
+
> **Audience**: AI coding agents working in games built on `bard-legends-framework`.
|
|
4
|
+
> This file is self-contained. Read it **instead of** the library source in
|
|
5
|
+
> `node_modules/bard-legends-framework`. Everything you need to use the framework correctly is here.
|
|
6
|
+
>
|
|
7
|
+
> **Hard dependency**: the framework is built on `actions-lib` (every observable, stream and
|
|
8
|
+
> lifecycle rule comes from it). You MUST also know that library — read its `AGENTS.md`. In
|
|
9
|
+
> particular: every subscription/stream must be `.attach(owner)`-ed, idle streams self-destruct,
|
|
10
|
+
> pipelines are linear, delivery is synchronous. Those rules are assumed below and not repeated.
|
|
11
|
+
|
|
12
|
+
`bard-legends-framework` is a TypeScript 2D game framework over **PixiJS** (rendering) and **p2**
|
|
13
|
+
(physics). It gives the game a strict, decorator-driven **layered architecture** with dependency
|
|
14
|
+
injection, so gameplay code is organized into self-contained **modules** that talk to each other
|
|
15
|
+
only through narrow public APIs. Lifecycle is `actions-lib` attachment all the way down: every
|
|
16
|
+
entity, view, scene and subscription is owned by a parent and dies when the parent dies.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Core mental model (read this first)
|
|
21
|
+
|
|
22
|
+
1. **The game is a tree of `actions-lib` attachables.** `Scene` → `Entity` → subscriptions/child
|
|
23
|
+
entities; `Entity` → its `View` → Pixi containers. Destroying any node cascades. You rarely
|
|
24
|
+
call `destroy()` directly — you destroy the owner (close a scene, destroy an entity) and the
|
|
25
|
+
rest follows.
|
|
26
|
+
|
|
27
|
+
2. **Six roles, each with a decorator.** Every gameplay file is exactly one of these:
|
|
28
|
+
|
|
29
|
+
| Role | Decorator | Extends | Responsibility |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| **Entity** | `@EntityDecorator()` | `Entity` family | Holds **state** (`Variable`/`Action`/`Reducer`/`Reference`). No rendering, no orchestration. |
|
|
32
|
+
| **View** | `@ViewDecorator({ entity })` | `View` | Renders one entity with Pixi. Auto-created/destroyed with its entity. |
|
|
33
|
+
| **Service** | `@ServiceDecorator()` | (plain class) | Business logic / orchestration. Singleton, injectable. |
|
|
34
|
+
| **Gateway** | `@ServiceDecorator()` | (plain class) | A module's **public API**. The only thing other modules import. |
|
|
35
|
+
| **Controller** | `@ControllerDecorator({ controllerLink })` | (plain class) | Receives gateway calls, forwards to the Service. Hidden from other modules. |
|
|
36
|
+
| **Scene** | `@SceneDecorator()` | `Scene<In, Out>` | A top-level game state (title, battle, map…). Owns its entities; has an update loop. |
|
|
37
|
+
|
|
38
|
+
3. **Modules are folders; cross-module calls go through Gateways only.** A module
|
|
39
|
+
(`HUD`, `PLAYER_SHIP`, `CAMPAIGN`…) groups one feature's entities/views/services/gateway.
|
|
40
|
+
**Never import another module's Service, Entity or View.** Import its `Gateway` (from the
|
|
41
|
+
module's `⚜️gateways` barrel) and call methods on it. This is what keeps modules decoupled and
|
|
42
|
+
import cycles impossible. (See [Why Gateway + Controller](#why-gateway--controller).)
|
|
43
|
+
|
|
44
|
+
4. **Dependency injection by constructor type.** Services, Gateways and Controllers receive their
|
|
45
|
+
dependencies as typed constructor parameters — the framework resolves and caches singletons
|
|
46
|
+
(reflect-metadata). Get one outside a constructor with `Service.get(SomeService)`. Do **not**
|
|
47
|
+
`new` a Service/Gateway/Controller yourself.
|
|
48
|
+
|
|
49
|
+
5. **Two registration mechanisms, both wired at boot by an eager glob.** Controllers (`@Controller`)
|
|
50
|
+
register to their `ControllerLink`; Views (`@View`) register against their entity class. The app
|
|
51
|
+
entry eagerly imports every `*.controller.ts` and `*.view.ts` so those side effects run before
|
|
52
|
+
gameplay starts (see [Bootstrap](#bootstrap)). A new controller/view file does nothing until it
|
|
53
|
+
matches that glob — keep the `.controller.ts` / `.view.ts` suffix.
|
|
54
|
+
|
|
55
|
+
6. **`actions-lib` is the nervous system.** Entity state is `Variable`/`Action`/`Reducer`. Async
|
|
56
|
+
flows (scene open, animations, `onClose`, gateway results) are `IdleSingleEvent`/`IdleSequence`.
|
|
57
|
+
Attach everything; return idle streams from functions callers may ignore.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Folder convention (one module)
|
|
62
|
+
|
|
63
|
+
Emoji-prefixed folders are load-order / category markers. A typical module:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
SOME_MODULE/
|
|
67
|
+
⚜️gateways/ ← public surface
|
|
68
|
+
some.gateway.ts ← @ServiceDecorator class + `export const XLink = new ControllerLink()`
|
|
69
|
+
controllers/some.controller.ts ← @ControllerDecorator({ controllerLink: XLink })
|
|
70
|
+
dtos/shared.dto.ts ← interfaces crossing the module boundary (DTOs)
|
|
71
|
+
index.ts ← barrel: re-exports the Gateway + DTOs ONLY (never the Service)
|
|
72
|
+
📐services/some.service.ts ← @ServiceDecorator, real logic (module-private)
|
|
73
|
+
🧊entities/some.entity.ts ← @EntityDecorator, state (module-private)
|
|
74
|
+
🧩views/some.view.ts ← @ViewDecorator, rendering (module-private)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Top-level: `🏹scenes/` (Scene classes, may nest their own sub-modules), `⚗️libraries/` (shared UI
|
|
78
|
+
widgets/helpers), `☄️assets/` (generated sprite/definition data). Other modules import from the
|
|
79
|
+
`⚜️gateways/index.ts` barrel — never reach past it.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## The roles in detail
|
|
84
|
+
|
|
85
|
+
### Entity — state only
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { Variable, Reducer } from 'actions-lib';
|
|
89
|
+
import { EntityDecorator, SingletonEntity } from 'bard-legends-framework';
|
|
90
|
+
|
|
91
|
+
@EntityDecorator()
|
|
92
|
+
export class HudEntity extends SingletonEntity {
|
|
93
|
+
readonly barState = new Variable<StatusBarState>(DEFAULT);
|
|
94
|
+
readonly isTransitioning = Reducer.createOr();
|
|
95
|
+
constructor(public readonly pilotThumbnail: SpriteID) { super(); }
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- An Entity is an `IDAttachable`: it has `.id`, `onDestroy()`, and must be attached to a scope
|
|
100
|
+
(`new HudEntity(...).attach(Scene.getActiveSceneOrFail())`). When the scope dies, the entity and
|
|
101
|
+
all state subscriptions die.
|
|
102
|
+
- **Put only state here.** No Pixi, no orchestration, no cross-module calls. Logic lives in the
|
|
103
|
+
Service; rendering in the View.
|
|
104
|
+
- Static lookups: `MyEntity.getInstanceByID(id)`, `getInstanceByIDOrFail(id)`, `getEntities()`.
|
|
105
|
+
|
|
106
|
+
**Entity base classes** — pick the narrowest that fits:
|
|
107
|
+
|
|
108
|
+
| Base | Use for |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `Entity` | Plain stateful object, many instances. |
|
|
111
|
+
| `SingletonEntity` | At most one alive at a time. Adds `getInstance()/getInstanceOrFail()`, `getInstanceAsync(): IdleSingleEvent`, `continuesSubscription()` (open/close stream). |
|
|
112
|
+
| `MovableEntity` | Has `position`/`rotation`/`velocity`/`rotationalSpeed` as `Variable`s, no physics body. |
|
|
113
|
+
| `MovablePhysicsEntity` | A **dynamic** p2 body; the framework drives its `position`/`rotation`/`velocity` Variables from the simulation. Has `onCollision`, `mass`, `area`. |
|
|
114
|
+
| `ImmovablePhysicsEntity` | A **static** p2 body (walls, platforms). |
|
|
115
|
+
|
|
116
|
+
### View — render one entity
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { Container, Game, Service, Sprite, View, ViewDecorator } from 'bard-legends-framework';
|
|
120
|
+
|
|
121
|
+
@ViewDecorator({ entity: HudEntity })
|
|
122
|
+
export class HUDView extends View {
|
|
123
|
+
private hudService = Service.get(HudService);
|
|
124
|
+
constructor(private entity: HudEntity, private someGateway: SomeGateway) { // DI: entity first, then injectables
|
|
125
|
+
super();
|
|
126
|
+
let container = new Container().displayParent(Game.camera.layers.foregroundScreen).attach(this);
|
|
127
|
+
this.entity.barState.subscribe(v => bar.setState(v)).attach(this); // attach to the view
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
- **Auto-lifecycle**: when an entity of the decorated class is created, its View is instantiated
|
|
133
|
+
automatically (constructor args = the entity, then DI-resolved services/gateways). When the
|
|
134
|
+
entity is destroyed, the View is destroyed. **You never `new` or attach a View yourself.**
|
|
135
|
+
- The View is the owner for all its Pixi objects and subscriptions: end every chain with
|
|
136
|
+
`.attach(this)`.
|
|
137
|
+
- A View **reads** entity state and reflects it; it should not mutate game state. User input →
|
|
138
|
+
call a `Service` method (often `Service.get(...)`), which mutates the entity.
|
|
139
|
+
- `View.update(time, delta)` exists if you need per-frame work; static `View.getInstance(entityID)`.
|
|
140
|
+
|
|
141
|
+
### Service — logic & orchestration
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
@ServiceDecorator()
|
|
145
|
+
export class HudService {
|
|
146
|
+
constructor(private playerShipGateway: PlayerShipGateway, private optionsMenuGateway: OptionsMenuGateway) {}
|
|
147
|
+
createUI(options: CreateHudOptionsDTO): void {
|
|
148
|
+
let hud = new HudEntity(/*…*/).attach(Scene.getActiveSceneOrFail());
|
|
149
|
+
this.playerShipGateway.subscribeToShipResourceStates()
|
|
150
|
+
.tap(s => { hud.barState.value = toBarState(s); })
|
|
151
|
+
.attach(hud);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- Singleton, constructor-injected. The only role allowed to **create entities** and run async
|
|
157
|
+
orchestration. Owns subscriptions by attaching them to a relevant entity/scene.
|
|
158
|
+
- It may depend on other modules — **but only via their Gateways** (injected). It may use its own
|
|
159
|
+
module's Service/Entity/View directly.
|
|
160
|
+
|
|
161
|
+
### Why Gateway + Controller
|
|
162
|
+
|
|
163
|
+
These two together let module B call module A without B importing A's internals (no cycles, no
|
|
164
|
+
leaked private classes):
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
// A/⚜️gateways/a.gateway.ts — imported by other modules
|
|
168
|
+
export const ALink = new ControllerLink();
|
|
169
|
+
@ServiceDecorator()
|
|
170
|
+
export class AGateway {
|
|
171
|
+
doThing(x: number): void { return ALink.trigger('doThing', x); } // public, thin, type-safe
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// A/⚜️gateways/controllers/a.controller.ts — module-private, registered by eager glob
|
|
175
|
+
@ControllerDecorator({ controllerLink: ALink })
|
|
176
|
+
export class AController {
|
|
177
|
+
constructor(private aService: AService) {}
|
|
178
|
+
doThing(x: number): void { this.aService.doThing(x); } // forwards to real logic
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- The **Gateway** is a tiny, dependency-free facade other modules import. It holds no logic — each
|
|
183
|
+
method just `link.trigger('methodName', ...args)` and returns the result.
|
|
184
|
+
- The **Controller** binds to the same `ControllerLink` and dispatches the trigger to the real
|
|
185
|
+
`Service`. It lives behind the module boundary and is loaded via the eager `*.controller.ts` glob.
|
|
186
|
+
- Net effect: importing `AGateway` pulls in **none** of A's services/entities. The link is resolved
|
|
187
|
+
at runtime, breaking the static import graph. Gateway method signatures and DTOs (in `dtos/`) are
|
|
188
|
+
the module's contract.
|
|
189
|
+
- **Rule**: keep Gateways logic-free passthroughs; keep their method/DTO names stable; never expose
|
|
190
|
+
an Entity/Service instance across the boundary — cross with plain DTOs.
|
|
191
|
+
|
|
192
|
+
### Scene — a top-level game state with an update loop
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
@SceneDecorator()
|
|
196
|
+
export class TitleScene extends Scene<void, TitleSceneOutputDTO> { // <Input, Output>
|
|
197
|
+
constructor(private cameraGateway: CameraGateway, private cockpitGateway: CockpitGateway) { super(); }
|
|
198
|
+
protected init(): IdleSingleEvent { // build the scene; return when ready
|
|
199
|
+
this.cockpitGateway.create().tap(out => this.close(out)).attach(this);
|
|
200
|
+
return Game.camera.appear(true, 'pureBlack');
|
|
201
|
+
}
|
|
202
|
+
protected update(time: number, delta: number): void { /* per-frame */ }
|
|
203
|
+
protected prepareToClose(): IdleSingleEvent { return Game.camera.appear(false, 'pureBlack'); }
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- `Scene<InputType, OutputType>`: typed input passed to `open`, typed result delivered via `close`.
|
|
208
|
+
- **Lifecycle hooks (abstract, you implement):** `init(input)` build & return readiness;
|
|
209
|
+
`update(time, delta)` called every frame while active; `prepareToClose()` run an exit transition.
|
|
210
|
+
- **Open / close (callers):**
|
|
211
|
+
```ts
|
|
212
|
+
BattleScene.open({ battle }) // static; IdleSingleEvent<BattleScene>
|
|
213
|
+
.asyncMap(scene => scene.onClose) // resolves with OutputType when the scene closes itself
|
|
214
|
+
.tap(result => /* route to next scene */)
|
|
215
|
+
.attach(owner);
|
|
216
|
+
```
|
|
217
|
+
Inside the scene, `this.close(output)` triggers `prepareToClose()` then resolves `onClose`.
|
|
218
|
+
- Only one scene is active at a time. Statics: `Scene.activeScene`, `Scene.getActiveSceneOrFail()`,
|
|
219
|
+
`Scene.isTransitioning`, `Scene.cancelActiveScene()`, `MyScene.getInstanceOrFail()`,
|
|
220
|
+
`MyScene.isActive()`.
|
|
221
|
+
- A scene is the natural **owner** for the entities it spawns: `new XEntity().attach(scene)`.
|
|
222
|
+
- `Scene.destroy()` is `@deprecated` — drive lifecycle with `open`/`close`/`cancelActiveScene`.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Bootstrap
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { Game, Service } from 'bard-legends-framework';
|
|
230
|
+
|
|
231
|
+
// MANDATORY: register every controller and view via side-effecting import (Vite glob).
|
|
232
|
+
import.meta.glob('./**/*.controller.ts', { eager: true });
|
|
233
|
+
import.meta.glob('./**/*.view.ts', { eager: true });
|
|
234
|
+
|
|
235
|
+
let game = new Game({ devMode, resolution: 2, maxScreenResolution });
|
|
236
|
+
await game.setup({ assetDefinitions: [...SpriteAssets, ...FontAssets], spriteDefinitions: SpriteDefinitions });
|
|
237
|
+
Service.get(GameCycleGateway).startGame(); // kick off the first scene via a gateway
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- `Game` is the global singleton: `Game.instance`, `Game.camera`, `Game.time`, `Game.pause`
|
|
241
|
+
(a `Reducer<boolean>` — open an effect to pause), `Game.instance.screenSize`/`screenSizeCenter`
|
|
242
|
+
(`PersistentNotifier<Vector>`), `setResolution`, `screenPositonToStagePosition`.
|
|
243
|
+
- `setup` loads assets and must be awaited before any scene opens.
|
|
244
|
+
- If you add a module, no manual registration is needed **as long as** files keep the
|
|
245
|
+
`*.controller.ts` / `*.view.ts` suffixes so the globs pick them up.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Rendering (Pixi wrappers)
|
|
250
|
+
|
|
251
|
+
All display objects extend `Container` (itself `ContainerAttributes`). They are chainable and are
|
|
252
|
+
`actions-lib` attachables — **attach every one you create**.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
new Sprite(def) // or Sprite.createByName('spriteId')
|
|
256
|
+
.setPosition(pos, { holdFrom: Vector.half }) // anchor-aware placement; round defaults true
|
|
257
|
+
.setScale(0.75)
|
|
258
|
+
.setZIndex(10)
|
|
259
|
+
.displayParent(parent) // parent: a Container OR a Game.camera.layers.* id
|
|
260
|
+
.attach(owner); // lifecycle owner (usually `this` in a View)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
- **Containers**: `Container`, `Sprite`, `Graphics` (factory shapes: `createRectangle`,
|
|
264
|
+
`createCircle`, `createPolygon`, `createArrow`, `createDashedLine`…), `Text`, `RichText`,
|
|
265
|
+
`Placeholder`, plus layout helpers `DisplayObjectArray<T>` (diffs a list via `trackBy`),
|
|
266
|
+
`ScrollAreaUI`, menus (`MenuUI`/`MenuEntity`/`MenuView`).
|
|
267
|
+
- **Attributes** (on every container): `position/x/y`, `size`, `rotation`/`rotationValue`,
|
|
268
|
+
`zIndex`, `scale`, `alpha`, `skewX/Y`, `mirror`, `flip`, `aspectRatio`, `interactive`, `cursor`,
|
|
269
|
+
`setMask`, `boundingBox`, `hitTest`. Setters come in fluent `setX()` and property forms.
|
|
270
|
+
- **Display tree vs. ownership are separate**: `displayParent()` controls where it renders (and
|
|
271
|
+
camera layer); `attach()` controls when it's destroyed. Set both.
|
|
272
|
+
- **Camera layers** (`Game.camera.layers[...]`): `backgroundScreen`, `background`, `main`,
|
|
273
|
+
`foreground`, `foregroundScreen`. `*Screen` layers are fixed to the screen (HUD/menus); the
|
|
274
|
+
others move with the camera (world). `Game.camera` also has `setPosition`, `setTransition`,
|
|
275
|
+
`appear(on, type)`.
|
|
276
|
+
- **Input**: `container.on(ContainerEventType.Click)` returns a `Sequence` (attach it); UI widgets
|
|
277
|
+
expose `onClick`. Global input via injected `KeyboardService` / `MouseService`.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Animation
|
|
282
|
+
|
|
283
|
+
- `Animator(target, 'propName' | ['a','b'], options)` — tween numeric properties; `.animate({...})`
|
|
284
|
+
returns a `SingleEvent<void>`, plus `.set(...)`, `.completeAnimation()`, `.value` notifier.
|
|
285
|
+
- `Animations.{lineer,easeIn,easeOut,easeInOut,easeInOutCubic,blink}` and
|
|
286
|
+
`AnimationInterpolationFunctions` for easing curves.
|
|
287
|
+
- State-driven helpers: `StateAnimation<T>`, `FadeInStateAnimation<T>`, `SlideStateAnimation`,
|
|
288
|
+
`FadeInContent<T>`, `SlideInContent<T>` — drive enter/leave transitions by `setState`/`setIndex`.
|
|
289
|
+
- `UpdateCycle` is the frame clock: `sceneUpdateAction` (+`before`/`after`), `wait(duration)`,
|
|
290
|
+
`lastDelta`, `registerUpdateModifier` (e.g. time-scale). Prefer the scene's `update()` hook over
|
|
291
|
+
subscribing directly unless you need global timing.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Physics (p2)
|
|
296
|
+
|
|
297
|
+
- Bodies are entities: extend `MovablePhysicsEntity` (dynamic) or `ImmovablePhysicsEntity` (static).
|
|
298
|
+
Construct with a `physicsWorldID` and a `PhysicsEntityDefinition` (`shapeDefinition` =
|
|
299
|
+
material/group/shapeType/shapeData, plus `position`, `rotation`, `addInEmptySpace`,
|
|
300
|
+
`includeOnPathfinding`). The framework keeps the entity's `position`/`rotation`/`velocity`
|
|
301
|
+
Variables in sync with the simulation.
|
|
302
|
+
- Collisions: subscribe to `entity.onCollision` (`CollisionReport[]` with `self`/`target` details:
|
|
303
|
+
normal, mass, area, contact position).
|
|
304
|
+
- World & queries via injected `PhysicsGateway`: `createPhysicsWorld`, `hitTest`, `findPath` /
|
|
305
|
+
`findPathDirection`, `applyImpulse`, `createExplosion` / `createElipticExplosion` (return
|
|
306
|
+
`ExplosionHit[]`), `getMapSize`, `setPaused`.
|
|
307
|
+
- Bodies belong to `PhysicsBodyGroup`s; which groups interact is set per-world via
|
|
308
|
+
`interactingBodyGroups`. Pathfinding helpers: `PathFinder`, `VectorFieldPathFinder`,
|
|
309
|
+
`ClosestAvailableSpaceHelper`.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Services you inject (built-ins)
|
|
314
|
+
|
|
315
|
+
Resolve via constructor injection or `Service.get(X)`:
|
|
316
|
+
|
|
317
|
+
- `KeyboardService` — `keyPressed`/`keyReleased` notifiers, `isKeyDown(code, { onlyPressedThisFrame })`.
|
|
318
|
+
- `MouseService` — `stagePosition`/`screenPosition` (world vs. screen), `mainButtonState`,
|
|
319
|
+
`secondaryButtonState`, `initialMouseMovementHappened`.
|
|
320
|
+
- `MouseTargetFocusService` — “did the user click-to-focus a world point” events.
|
|
321
|
+
- `CameraGateway` / `PhysicsGateway` — camera & physics façades shown above.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Testing
|
|
326
|
+
|
|
327
|
+
- Call `BardLegendsHardReset.hardReset()` in `beforeEach` (alongside `actions-lib`'s
|
|
328
|
+
`ActionLib.hardReset()`) to clear entity/singleton/DI registries between tests.
|
|
329
|
+
- Drive frames with `UpdateCycle.triggerUpdateTick(delta)`; use fake timers for `wait`/animations.
|
|
330
|
+
- Because delivery is synchronous (`actions-lib`), most assertions need no awaiting.
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
## Do / Don't
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
// DO: keep state in the Entity, logic in the Service, pixels in the View.
|
|
338
|
+
// DO: cross module boundaries only through an injected Gateway + DTOs.
|
|
339
|
+
// DO: attach every container, subscription and stream to a clear owner (usually `this`/the entity/the scene).
|
|
340
|
+
// DO: create entities in Services; let Views appear automatically; own entities with the Scene.
|
|
341
|
+
// DO: return IdleSingleEvent/IdleSequence from async functions; route scenes via open().asyncMap(s => s.onClose).
|
|
342
|
+
// DO: keep Gateways as thin link.trigger(...) passthroughs; keep DTO/method names stable.
|
|
343
|
+
|
|
344
|
+
// DON'T: import another module's Service/Entity/View — import its Gateway.
|
|
345
|
+
// DON'T: new or attach a View yourself, or new a Service/Gateway/Controller — use DI / Service.get / let entities spawn views.
|
|
346
|
+
// DON'T: put logic in a Gateway, or Pixi/orchestration in an Entity.
|
|
347
|
+
// DON'T: rename a *.controller.ts / *.view.ts file off-suffix — it silently stops registering.
|
|
348
|
+
// DON'T: call scene.destroy() (deprecated) — use close()/cancelActiveScene().
|
|
349
|
+
// DON'T: mutate game state from a View — call a Service method instead.
|
|
350
|
+
// DON'T: forget actions-lib rules (attach-or-throw, linear pipelines, no raw Promises) — see its AGENTS.md.
|
|
351
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bard-legends-framework",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "Bard Legends Framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"publishConfig": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"AGENTS.md"
|
|
19
20
|
],
|
|
20
21
|
"repository": {
|
|
21
22
|
"type": "git",
|