@invinite-org/chartlang-core 1.0.1

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.
Files changed (208) hide show
  1. package/CHANGELOG.md +1694 -0
  2. package/LICENSE +21 -0
  3. package/README.md +53 -0
  4. package/dist/alert/alert.d.ts +32 -0
  5. package/dist/alert/alert.d.ts.map +1 -0
  6. package/dist/alert/alert.js +19 -0
  7. package/dist/alert/alert.js.map +1 -0
  8. package/dist/alert/index.d.ts +2 -0
  9. package/dist/alert/index.d.ts.map +1 -0
  10. package/dist/alert/index.js +4 -0
  11. package/dist/alert/index.js.map +1 -0
  12. package/dist/color/colorHelpers.d.ts +65 -0
  13. package/dist/color/colorHelpers.d.ts.map +1 -0
  14. package/dist/color/colorHelpers.js +137 -0
  15. package/dist/color/colorHelpers.js.map +1 -0
  16. package/dist/color/index.d.ts +37 -0
  17. package/dist/color/index.d.ts.map +1 -0
  18. package/dist/color/index.js +23 -0
  19. package/dist/color/index.js.map +1 -0
  20. package/dist/color/parseColor.d.ts +48 -0
  21. package/dist/color/parseColor.d.ts.map +1 -0
  22. package/dist/color/parseColor.js +141 -0
  23. package/dist/color/parseColor.js.map +1 -0
  24. package/dist/define/defineAlert.d.ts +43 -0
  25. package/dist/define/defineAlert.d.ts.map +1 -0
  26. package/dist/define/defineAlert.js +49 -0
  27. package/dist/define/defineAlert.js.map +1 -0
  28. package/dist/define/defineAlertCondition.d.ts +65 -0
  29. package/dist/define/defineAlertCondition.d.ts.map +1 -0
  30. package/dist/define/defineAlertCondition.js +64 -0
  31. package/dist/define/defineAlertCondition.js.map +1 -0
  32. package/dist/define/defineDrawing.d.ts +62 -0
  33. package/dist/define/defineDrawing.d.ts.map +1 -0
  34. package/dist/define/defineDrawing.js +67 -0
  35. package/dist/define/defineDrawing.js.map +1 -0
  36. package/dist/define/defineIndicator.d.ts +48 -0
  37. package/dist/define/defineIndicator.d.ts.map +1 -0
  38. package/dist/define/defineIndicator.js +54 -0
  39. package/dist/define/defineIndicator.js.map +1 -0
  40. package/dist/define/index.d.ts +6 -0
  41. package/dist/define/index.d.ts.map +1 -0
  42. package/dist/define/index.js +7 -0
  43. package/dist/define/index.js.map +1 -0
  44. package/dist/define/overrides.d.ts +102 -0
  45. package/dist/define/overrides.d.ts.map +1 -0
  46. package/dist/define/overrides.js +4 -0
  47. package/dist/define/overrides.js.map +1 -0
  48. package/dist/draw/buckets.d.ts +57 -0
  49. package/dist/draw/buckets.d.ts.map +1 -0
  50. package/dist/draw/buckets.js +110 -0
  51. package/dist/draw/buckets.js.map +1 -0
  52. package/dist/draw/draw.d.ts +128 -0
  53. package/dist/draw/draw.d.ts.map +1 -0
  54. package/dist/draw/draw.js +40 -0
  55. package/dist/draw/draw.js.map +1 -0
  56. package/dist/draw/drawingKind.d.ts +80 -0
  57. package/dist/draw/drawingKind.d.ts.map +1 -0
  58. package/dist/draw/drawingKind.js +177 -0
  59. package/dist/draw/drawingKind.js.map +1 -0
  60. package/dist/draw/drawingState.d.ts +1420 -0
  61. package/dist/draw/drawingState.d.ts.map +1 -0
  62. package/dist/draw/drawingState.js +4 -0
  63. package/dist/draw/drawingState.js.map +1 -0
  64. package/dist/draw/drawingStyle.d.ts +219 -0
  65. package/dist/draw/drawingStyle.d.ts.map +1 -0
  66. package/dist/draw/drawingStyle.js +4 -0
  67. package/dist/draw/drawingStyle.js.map +1 -0
  68. package/dist/draw/handle.d.ts +32 -0
  69. package/dist/draw/handle.d.ts.map +1 -0
  70. package/dist/draw/handle.js +4 -0
  71. package/dist/draw/handle.js.map +1 -0
  72. package/dist/draw/index.d.ts +13 -0
  73. package/dist/draw/index.d.ts.map +1 -0
  74. package/dist/draw/index.js +7 -0
  75. package/dist/draw/index.js.map +1 -0
  76. package/dist/draw/table.d.ts +84 -0
  77. package/dist/draw/table.d.ts.map +1 -0
  78. package/dist/draw/table.js +22 -0
  79. package/dist/draw/table.js.map +1 -0
  80. package/dist/draw/worldPoint.d.ts +114 -0
  81. package/dist/draw/worldPoint.d.ts.map +1 -0
  82. package/dist/draw/worldPoint.js +4 -0
  83. package/dist/draw/worldPoint.js.map +1 -0
  84. package/dist/index.d.ts +28 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +16 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/input/index.d.ts +3 -0
  89. package/dist/input/index.d.ts.map +1 -0
  90. package/dist/input/index.js +4 -0
  91. package/dist/input/index.js.map +1 -0
  92. package/dist/input/input.d.ts +174 -0
  93. package/dist/input/input.d.ts.map +1 -0
  94. package/dist/input/input.js +174 -0
  95. package/dist/input/input.js.map +1 -0
  96. package/dist/input/inputDescriptor.d.ts +204 -0
  97. package/dist/input/inputDescriptor.d.ts.map +1 -0
  98. package/dist/input/inputDescriptor.js +4 -0
  99. package/dist/input/inputDescriptor.js.map +1 -0
  100. package/dist/interval/intervalToSeconds.d.ts +20 -0
  101. package/dist/interval/intervalToSeconds.d.ts.map +1 -0
  102. package/dist/interval/intervalToSeconds.js +50 -0
  103. package/dist/interval/intervalToSeconds.js.map +1 -0
  104. package/dist/plot/index.d.ts +2 -0
  105. package/dist/plot/index.d.ts.map +1 -0
  106. package/dist/plot/index.js +4 -0
  107. package/dist/plot/index.js.map +1 -0
  108. package/dist/plot/plot.d.ts +277 -0
  109. package/dist/plot/plot.d.ts.map +1 -0
  110. package/dist/plot/plot.js +37 -0
  111. package/dist/plot/plot.js.map +1 -0
  112. package/dist/request/index.d.ts +2 -0
  113. package/dist/request/index.d.ts.map +1 -0
  114. package/dist/request/index.js +4 -0
  115. package/dist/request/index.js.map +1 -0
  116. package/dist/request/request.d.ts +106 -0
  117. package/dist/request/request.d.ts.map +1 -0
  118. package/dist/request/request.js +44 -0
  119. package/dist/request/request.js.map +1 -0
  120. package/dist/runtime/index.d.ts +3 -0
  121. package/dist/runtime/index.d.ts.map +1 -0
  122. package/dist/runtime/index.js +4 -0
  123. package/dist/runtime/index.js.map +1 -0
  124. package/dist/runtime/runtime.d.ts +84 -0
  125. package/dist/runtime/runtime.d.ts.map +1 -0
  126. package/dist/runtime/runtime.js +72 -0
  127. package/dist/runtime/runtime.js.map +1 -0
  128. package/dist/state/index.d.ts +5 -0
  129. package/dist/state/index.d.ts.map +1 -0
  130. package/dist/state/index.js +4 -0
  131. package/dist/state/index.js.map +1 -0
  132. package/dist/state/mutableSlot.d.ts +25 -0
  133. package/dist/state/mutableSlot.d.ts.map +1 -0
  134. package/dist/state/mutableSlot.js +4 -0
  135. package/dist/state/mutableSlot.js.map +1 -0
  136. package/dist/state/snapshot.d.ts +108 -0
  137. package/dist/state/snapshot.d.ts.map +1 -0
  138. package/dist/state/snapshot.js +4 -0
  139. package/dist/state/snapshot.js.map +1 -0
  140. package/dist/state/state.d.ts +86 -0
  141. package/dist/state/state.d.ts.map +1 -0
  142. package/dist/state/state.js +95 -0
  143. package/dist/state/state.js.map +1 -0
  144. package/dist/statefulPrimitives.d.ts +71 -0
  145. package/dist/statefulPrimitives.d.ts.map +1 -0
  146. package/dist/statefulPrimitives.js +234 -0
  147. package/dist/statefulPrimitives.js.map +1 -0
  148. package/dist/ta/index.d.ts +2 -0
  149. package/dist/ta/index.d.ts.map +1 -0
  150. package/dist/ta/index.js +4 -0
  151. package/dist/ta/index.js.map +1 -0
  152. package/dist/ta/ta.d.ts +2476 -0
  153. package/dist/ta/ta.d.ts.map +1 -0
  154. package/dist/ta/ta.js +312 -0
  155. package/dist/ta/ta.js.map +1 -0
  156. package/dist/time/_lib/dateTimeFormatCache.d.ts +11 -0
  157. package/dist/time/_lib/dateTimeFormatCache.d.ts.map +1 -0
  158. package/dist/time/_lib/dateTimeFormatCache.js +22 -0
  159. package/dist/time/_lib/dateTimeFormatCache.js.map +1 -0
  160. package/dist/time/index.d.ts +7 -0
  161. package/dist/time/index.d.ts.map +1 -0
  162. package/dist/time/index.js +8 -0
  163. package/dist/time/index.js.map +1 -0
  164. package/dist/time/nyDayKey.d.ts +24 -0
  165. package/dist/time/nyDayKey.d.ts.map +1 -0
  166. package/dist/time/nyDayKey.js +49 -0
  167. package/dist/time/nyDayKey.js.map +1 -0
  168. package/dist/time/session.d.ts +16 -0
  169. package/dist/time/session.d.ts.map +1 -0
  170. package/dist/time/session.js +18 -0
  171. package/dist/time/session.js.map +1 -0
  172. package/dist/time/sessionBoundaries.d.ts +40 -0
  173. package/dist/time/sessionBoundaries.d.ts.map +1 -0
  174. package/dist/time/sessionBoundaries.js +102 -0
  175. package/dist/time/sessionBoundaries.js.map +1 -0
  176. package/dist/time/types.d.ts +34 -0
  177. package/dist/time/types.d.ts.map +1 -0
  178. package/dist/time/types.js +4 -0
  179. package/dist/time/types.js.map +1 -0
  180. package/dist/time/weekKey.d.ts +11 -0
  181. package/dist/time/weekKey.d.ts.map +1 -0
  182. package/dist/time/weekKey.js +25 -0
  183. package/dist/time/weekKey.js.map +1 -0
  184. package/dist/time/weekday.d.ts +12 -0
  185. package/dist/time/weekday.d.ts.map +1 -0
  186. package/dist/time/weekday.js +22 -0
  187. package/dist/time/weekday.js.map +1 -0
  188. package/dist/types.d.ts +464 -0
  189. package/dist/types.d.ts.map +1 -0
  190. package/dist/types.js +4 -0
  191. package/dist/types.js.map +1 -0
  192. package/dist/views/barstate.d.ts +40 -0
  193. package/dist/views/barstate.d.ts.map +1 -0
  194. package/dist/views/barstate.js +22 -0
  195. package/dist/views/barstate.js.map +1 -0
  196. package/dist/views/index.d.ts +4 -0
  197. package/dist/views/index.d.ts.map +1 -0
  198. package/dist/views/index.js +4 -0
  199. package/dist/views/index.js.map +1 -0
  200. package/dist/views/syminfo.d.ts +46 -0
  201. package/dist/views/syminfo.d.ts.map +1 -0
  202. package/dist/views/syminfo.js +25 -0
  203. package/dist/views/syminfo.js.map +1 -0
  204. package/dist/views/timeframe.d.ts +40 -0
  205. package/dist/views/timeframe.d.ts.map +1 -0
  206. package/dist/views/timeframe.js +21 -0
  207. package/dist/views/timeframe.js.map +1 -0
  208. package/package.json +45 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,1694 @@
1
+ # @invinite-org/chartlang-core
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - d1de692: Fix end-user-blocking Node-ESM packaging bug. Every published `dist/index.js` previously failed to load under Node's strict ESM resolver because `tsc` had been configured with `moduleResolution: "Bundler"` and emitted relative specifiers verbatim, so `dist/index.js` carried `from "./api"` (extensionless) and Node rejected the resolution. Workspace consumers never saw this because tsx / vitest / Vite resolve loosely, but `npm install @invinite-org/chartlang-compiler` followed by `import` failed immediately for any Node consumer, and `examples/react-demo/vite.config.ts`'s server-side compile plugin broke at dev-config-load time.
8
+
9
+ This release switches `tsconfig.base.json` to `module: "NodeNext"` / `moduleResolution: "NodeNext"`, and rewrites every relative import / export / dynamic-import / `typeof import("…")` specifier across all packages' source to carry an explicit `.js` (or `/index.js`) suffix. The new resolution mode also surfaces this bug class as a compile error rather than runtime breakage, so it cannot regress.
10
+
11
+ No behavioural change for runtime consumers — the rewritten specifiers resolve to the same TypeScript sources at build time and the same `dist/<path>.js` files at consumer-load time.
12
+
13
+ - 98599b2: Generate primitive reference pages for `plot`, `hline`, `alert`, and `request.lowerTf`: extended the Phase 4 docs generator with entries that source JSDoc from `packages/core/src/{plot,alert,request}/`, and added `@stable` markers to the top-level `plot` / `hline` / `alert` callable holes so the generator emits a stability label. The new pages are wired into the VitePress sidebar under Plot, Alert, and Request.
14
+
15
+ ## 1.0.0
16
+
17
+ ### Major Changes
18
+
19
+ - chartlang `1.0.0` -- the `apiVersion: 1` standard.
20
+
21
+ - `apiVersion: 1` frozen: compiler accepts only the frozen language
22
+ version; `STATEFUL_PRIMITIVES` locked at 172 entries by exact
23
+ name-set; every shipping export `@stable`; pre-1.0 deprecations
24
+ removed (`PHASE_1_SCENARIOS`).
25
+ - Canonical language spec published (`docs/spec/`): grammar,
26
+ semantics, manifest, emissions, versioning -- self-contained for
27
+ alternate implementations. The `v1.0.0` tag is the frozen spec
28
+ snapshot.
29
+ - Public conformance reports: `pnpm conformance --report` emits
30
+ `CONFORMANCE.md` + `conformance-report.json`; canvas2d reference
31
+ report published and drift-gated.
32
+ - Adapter-author path proven end-to-end: scaffolded adapters ship a
33
+ wired conformance test; full writing-an-adapter tutorial +
34
+ Lightweight Charts porting walkthrough.
35
+ - Pine migration guide finalised with a pattern-coverage matrix
36
+ audited against the top ~50 Pine scripts.
37
+
38
+ ### Minor Changes
39
+
40
+ - d14a034: Add phase 5 server alerts, multi-timeframe request handling, runtime persistence, QuickJS hosting, expanded plot and table rendering, color helpers, alert conditions, and volume profile primitives.
41
+ - 3cfff10: Phase 6 closeout for Tier-3 ergonomics and lower-timeframe support.
42
+ - 3cfff10: Add `IntervalDescriptor.intervalSeconds?: number` and `intervalToSeconds`.
43
+ - 3cfff10: Add `request.lowerTf({ interval })` and compiler diagnostics for invalid lower-timeframe intervals.
44
+ - 3cfff10: Add the `@invinite-org/chartlang-core/time` subpath with session and timezone helpers.
45
+
46
+ ### Patch Changes
47
+
48
+ - Freeze `apiVersion: 1`: release-grade compiler diagnostics for version
49
+ mismatches, an exact name-set lock on the 172-entry `STATEFUL_PRIMITIVES`
50
+ registry, and freeze-contract documentation on pinned surfaces. No behavioural
51
+ change: the structural check already enforced `apiVersion: 1`.
52
+ - Pre-1.0 surface cleanup: remove the deprecated `PHASE_1_SCENARIOS`
53
+ alias (use `ALL_SCENARIOS`) and promote every shipping export from
54
+ `@experimental` to `@stable` ahead of the `apiVersion: 1` freeze.
55
+
56
+ ## 0.5.0
57
+
58
+ ### Phase 5
59
+
60
+ #### Minor Changes
61
+
62
+ - Ship Phase 5 color helpers from PLAN §11.4: `color.fromGradient`, `color.withAlpha`, `color.rgb`, and `color.hsl`.
63
+ - Add canonical StateSnapshot, StreamSnapshot, and StateStoreKey type declarations for PLAN.md §6.1 and §6.9 persistence.
64
+ - Ship Phase 5 `defineAlertCondition`, compiler manifest extraction, runtime `signal()` emissions, adapter validation, and conformance coverage per PLAN §11.2.
65
+ - Add `draw.table` with `TableCell`/`TablePosition` types, runtime emission,
66
+ viewport-anchored canvas2d rendering, and conformance coverage per PLAN §10.2.
67
+ - Add Phase 5 plot kinds, runtime emission dispatch, validation, conformance scenarios, and canvas2d reference renderers.
68
+ - Add the Phase 5 `runtime.log.*` and `runtime.error()` surface, log emissions, runtime halt diagnostics, and conformance coverage.
69
+ - Replace the Phase 4 `request.security` NaN-only path with real
70
+ multi-timeframe secondary stream alignment per PLAN.md §6.8 and §7.2.
71
+ Adapters can route tagged `CandleEvent.streamKey` candles, the worker
72
+ host dispatches them through `ScriptRunner.push`, conformance includes
73
+ MTF scenarios, and the private canvas2d reference adapter now declares
74
+ `multiTimeframe: true`.
75
+ - Port `ta.anchoredVolumeProfile` from invinite commit 3234c8c0c3f9880d9d1e3a3ee63ebd55ddd535f4, adding the PLAN §9.2 horizontal-histogram volume-profile primitive and PLAN §10.1.1 input-time anchor workflow.
76
+ - Add `ta.fixedRangeVolumeProfile`, completing the Phase 5 volume-profile set
77
+ from PLAN §9.2 and §10.1.1 with fixed `[from, to]` anchors, frozen post-range
78
+ histograms, and `fixed-range-inverted` diagnostics. Ported from invinite
79
+ commit `3234c8c0c3f9880d9d1e3a3ee63ebd55ddd535f4`.
80
+ - Port `ta.sessionVolumeProfile` from invinite commit 3234c8c0c3f9880d9d1e3a3ee63ebd55ddd535f4, adding the PLAN §9.2 horizontal-histogram session volume-profile primitive, PLAN §4.8 syminfo-session fallback diagnostics, and compiler/runtime registration.
81
+ - Add `ta.visibleRangeVolumeProfile` per PLAN §9.2, ported from invinite commit `3234c8c0c3f9880d9d1e3a3ee63ebd55ddd535f4`, with runtime histogram emission, compiler/core type surfaces, conformance coverage, and generated docs.
82
+
83
+ ## 0.4.0
84
+
85
+ ### Minor Changes
86
+
87
+ - 3f3ce38: Replace the Phase-0 placeholder with the Phase-1 typed surface:
88
+ `defineIndicator` / `defineAlert` constructors, the `ta` / `plot` / `alert`
89
+ callable holes the compiler retargets at the runtime, the frozen
90
+ `STATEFUL_PRIMITIVES` registry, and every §4.3 type. Nothing executes —
91
+ `core` ships types and callable surfaces only; the runtime ships the real
92
+ implementations in Tasks 5-8.
93
+ - 38fb475: Phase 2 — `0.2` full indicator parity.
94
+
95
+ - 81 new `ta.*` primitives (6 cross-functional + 75 §9.2 ports);
96
+ `TA_REGISTRY` cardinality 9 -> 90; `STATEFUL_PRIMITIVES`
97
+ cardinality 12 -> 93.
98
+ - 5 new chained-MA helpers + 5 new stats/volatility helpers in
99
+ `packages/runtime/src/ta/lib/`.
100
+ - 6 new `PlotKind`s (histogram, bars, area, filled-band, label,
101
+ marker) + canvas2d renderers + `validateEmission` arms.
102
+ - `Bar` extended with `hl2` / `hlc3` / `ohlc4` / `hlcc4` derived
103
+ source fields — runtime already pre-computes on `BarView`.
104
+ - `Scenario` extended with `inlineSource?: string` so Phase-2
105
+ scenarios stay self-contained without bloating
106
+ `examples/scripts/`.
107
+ - `STATEFUL_PRIMITIVES` shape widened from `ReadonlySet<string>`
108
+ to `ReadonlySet<{ name: string; slot: boolean }>` to support
109
+ `ta.nz` (the only stateless `ta.*`).
110
+ - Universal `opts.offset` honoured on every `ta.*` primitive
111
+ (Phase-1 backfill in Task 29).
112
+ - `chartlang docs` subcommand generates
113
+ `docs/primitives/ta/<id>.md` per primitive.
114
+ - `PHASE_2_INDICATORS` + `PHASE_5_DEFERRED` inventories exported
115
+ from `@invinite-org/chartlang-conformance` and pinned by
116
+ `phase2Coverage.test.ts` (Task 30).
117
+ - 100% coverage maintained across every published package.
118
+ - `apiVersion: 1` script header unchanged; Phase 2 is additive
119
+ at runtime.
120
+
121
+ - 38fb475: Phase-2 Task 5 — cross-functional `ta.*` primitives + `STATEFUL_PRIMITIVES`
122
+ shape evolution.
123
+
124
+ Ships six new Pine-canonical `ta.*` primitives under
125
+ `packages/runtime/src/ta/`:
126
+
127
+ - `ta.nz(value, replacement?)` — stateless NaN-replacement.
128
+ - `ta.highest(source, length)` — rolling max (monotone deque + window
129
+ recompute).
130
+ - `ta.lowest(source, length)` — rolling min (mirror of `highest`).
131
+ - `ta.change(source, opts)` — first-difference `source[0] − source[length]`.
132
+ - `ta.valuewhen(condition, source, occurrence)` — source value at the
133
+ n-th most recent matching bar.
134
+ - `ta.barssince(condition)` — bars since the last `condition === true`.
135
+
136
+ Each primitive ships the §22.10 set: impl + unit + property + golden +
137
+ bench pair + conformance scenario (using the Phase-2 `inlineSource`
138
+ extension from Task 1) + auto-generated `docs/primitives/ta/<id>.md`.
139
+
140
+ `STATEFUL_PRIMITIVES` widens from `ReadonlySet<string>` to
141
+ `ReadonlySet<{ name: string; slot: boolean }>` so `ta.nz` (the only
142
+ stateless cross-functional primitive) can opt out of compiler slot-id
143
+ injection. Phase-1 entries flip to `slot: true`; `ta.nz` is the only
144
+ `slot: false` entry; the set cardinality grows from 12 → 18. The shape
145
+ update cascades through every compiler consumer
146
+ (`packages/compiler/src/api.ts`, `program.ts`,
147
+ `analysis/statefulCallInLoop.ts`, `transformers/callsiteIdInjection.ts`,
148
+ and their tests). The `statefulCallInLoop` analysis still flags every
149
+ entry inside a loop body — `slot: false` primitives are forbidden in
150
+ loops by Pine-parity convention.
151
+
152
+ `TA_REGISTRY` cardinality grows from 9 → 15. `RuntimeTaNamespace`
153
+ mirrors core's `TaNamespace` 1:1 with the standard `slotId` first-arg
154
+ on every method except `nz` (which carries the script-author signature
155
+ verbatim).
156
+
157
+ Compiler change is `patch`-level — the public API surface is
158
+ unchanged; only the internal `STATEFUL_PRIMITIVES` parameter shape
159
+ widens. Core/runtime/conformance bump `minor` for the new exports and
160
+ the new scenarios.
161
+
162
+ - 38fb475: Phase-2 Task 7 — MA ports (`ta.dema`, `ta.tema`, `ta.kama`, `ta.alma`).
163
+
164
+ Adds four chained / adaptive moving averages on top of the Phase-1
165
+ EMA primitive + the Task-6 MA backbone. DEMA / TEMA compose EMA
166
+ sub-slots through `TA_REGISTRY` (`${slotId}/ema1` / `/ema2` / `/ema3`);
167
+ KAMA is Kaufman's adaptive MA with an efficiency-ratio-driven
168
+ smoothing constant; ALMA is the Arnaud Legoux MA with a precomputed
169
+ Gaussian weight kernel.
170
+
171
+ Each primitive ships the §22.10 set (impl + four test layers +
172
+ conformance scenario + auto-generated docs page). ALMA's `offset`
173
+ opt is the Gaussian-centre position in `[0, 1]` (default `0.85`) —
174
+ distinct from the universal bar-shift, which lives on `opts.barShift`
175
+ for ALMA only.
176
+
177
+ Compiler patch: the ambient shim mirrors the four new
178
+ `TaNamespace` methods + opt bags.
179
+
180
+ - 38fb475: Phase-2 Task 8 — final §9.2 MA ports (`ta.lsma`, `ta.mcginley`, `ta.maRibbon`).
181
+
182
+ Closes out the §9.2 moving-averages list. `ta.lsma` is the linear-
183
+ regression value at the trailing window (reuses Task-4's
184
+ `linearRegression` helper for the property-test reference);
185
+ `ta.mcginley` is the McGinley Dynamic recurrence with NaN-correct
186
+ zero-anchor handling; `ta.maRibbon` is a fan of K MAs at different
187
+ lengths, dispatched per-bar through `TA_REGISTRY`'s registered MA
188
+ primitives (`sma` / `ema` / `wma` / `smma`) via sub-slot ids
189
+ `${slotId}/ma_<length>`.
190
+
191
+ `MaRibbonResult` is a dynamic-keyed record `{ ma_<length>:
192
+ Series<number> }`. The exported `maRibbonOutputKeys(opts)` helper
193
+ returns the ordered keys for stable iteration. `maRibbon` is
194
+ registry-tagged as multi-output via `TA_REGISTRY_METADATA` with its
195
+ default `primarySeriesKey: "ma_50"` + default visible keys
196
+ `["ma_10", "ma_20", "ma_30", "ma_40", "ma_50"]` + `{ kind: "auto" }`
197
+ y-domain — runtime metadata for legend chips and pane axes.
198
+
199
+ Core also adds the `MaTypeNoVolume` string-literal union (parallel to
200
+ the runtime's `lib/maTypes.ts` alias) so script authors can type the
201
+ `maType` opt directly. Each primitive ships the §22.10 set (impl +
202
+ four test layers + conformance scenario + auto-generated docs page).
203
+
204
+ Compiler patch: the ambient shim mirrors the three new `TaNamespace`
205
+ methods + opt bags + `MaTypeNoVolume` alias + `MaRibbonResult` type.
206
+
207
+ - 38fb475: Phase-2 Task 6 — MA ports (`ta.wma`, `ta.vwma`, `ta.hma`, `ta.smma`).
208
+
209
+ Adds four moving-average primitives on top of the Task-3 chained-MA
210
+ helpers. `ta.wma` is a linear-weighted MA over the trailing window;
211
+ `ta.vwma` is the volume-weighted variant; `ta.smma` is Wilder's
212
+ smoothed MA (α = 1/N); `ta.hma` is the Hull MA composed via three WMA
213
+ sub-slots derived from the parent slot id (`${slotId}/half`,
214
+ `${slotId}/full`, `${slotId}/final`).
215
+
216
+ Each primitive ships the §22.10 set (impl + four test layers +
217
+ conformance scenario + auto-generated docs page). The opts bags
218
+ (`WmaOpts`, `VwmaOpts`, `HmaOpts`, `SmmaOpts`) carry the universal
219
+ `offset` + `lineStyle` fields — typed surface only; the runtime
220
+ wiring lands in Task 29's universal-offset backfill.
221
+
222
+ Compiler patch: the ambient shim mirrors the four new
223
+ `TaNamespace` methods + opt bags.
224
+
225
+ - 38fb475: Phase-2 Task 13 — momentum ports (`ta.ao`, `ta.cmo`, `ta.momentum`,
226
+ `ta.roc`).
227
+
228
+ Ships four new momentum primitives under `packages/runtime/src/ta/`:
229
+
230
+ - `ta.ao(opts?)` — Awesome Oscillator. `SMA(hl2, fastLength) − SMA(hl2,
231
+ slowLength)`. Defaults to Pine-canonical `5` / `34`. Composes two
232
+ `ta.sma` sub-slots (`${slotId}/fastSma`, `${slotId}/slowSma`); a fix
233
+ to `sma` flows in for free.
234
+ - `ta.cmo(source, length, opts?)` — Chande Momentum Oscillator. Range
235
+ `[-100, 100]`. Trailing-window of per-bar gain / loss diffs with
236
+ incremental sum maintenance + flat-line (zero-denominator) NaN
237
+ guard.
238
+ - `ta.momentum(source, length, opts?)` — Pine `mom`. First-difference
239
+ `source[0] − source[length]`. Implemented as a thin shim around
240
+ `ta.change` (`${slotId}/change` sub-slot) — no private subtraction
241
+ loop.
242
+ - `ta.roc(source, length, opts?)` — Rate of Change. `100 ×
243
+ (source[0] − source[length]) / source[length]`. Zero lookback →
244
+ NaN.
245
+
246
+ Each primitive ships the §22.10 set: impl + unit + property + golden +
247
+ bench pair + conformance scenario (using the Phase-2 `inlineSource`
248
+ extension from Task 1) + auto-generated `docs/primitives/ta/<id>.md`.
249
+
250
+ `STATEFUL_PRIMITIVES` gains four new `slot: true` entries; `TaNamespace`
251
+
252
+ - `RuntimeTaNamespace` gain four new methods; `TA_REGISTRY` gains four
253
+ new entries. Four new opts types exported from core: `AoOpts`,
254
+ `CmoOpts`, `MomentumOpts`, `RocOpts` (each carries `offset?: number` and
255
+ `lineStyle?: PlotLineStyle` forward-compat fields; `AoOpts` also
256
+ carries `fastLength?: number`, `slowLength?: number`).
257
+
258
+ Provenance: ported from `invinite/src/components/trading-chart/
259
+ indicators/{ao,cmo,momentum,roc}.ts` at commit
260
+ `078f41fe2569d659d5aba726da8bcb5d3e2ced02`.
261
+
262
+ - 38fb475: Phase-2 Task 14 — momentum ports (`ta.pmo`, `ta.smi`, `ta.tsi`).
263
+
264
+ Ships three double-smoothed momentum primitives under
265
+ `packages/runtime/src/ta/`:
266
+
267
+ - `ta.pmo(source, opts?)` — Carl Swenlin's Price Momentum Oscillator
268
+ (`{ pmo, signal }`). Three-pass smoothing of the 1-bar ROC, scaled
269
+ to PMO's characteristic ±10 swing range. The two inner stages use
270
+ a non-canonical "Swenlin EMA" factor (`α = 2 / length`) instead of
271
+ the standard `α = 2 / (length + 1)`; the signal-line EMA composes
272
+ the canonical `ta.ema` via a `${slotId}/signal` sub-slot. Defaults
273
+ `(firstSmoothing, secondSmoothing, signalLength) = (35, 20, 10)`
274
+ per TradingView's published formula.
275
+ - `ta.smi(opts?)` — William Blau's Stochastic Momentum Index
276
+ (`{ smi, signal }`). Composes `ta.highest` over `bar.high` and
277
+ `ta.lowest` over `bar.low` (`kLength` window) for the rolling
278
+ midpoint and range, then double-EMA-smooths both numerator
279
+ (`bar.close − midpoint`) and denominator (`range / 2`) through two
280
+ EMA layers each, then computes `100 × numSmoothed / denSmoothed`
281
+ and feeds it through a signal EMA. Bounded `[-100, 100]` (flat
282
+ range → NaN at smi). Defaults `(kLength, firstSmoothing,
283
+ secondSmoothing, dLength) = (10, 3, 5, 3)`.
284
+ - `ta.tsi(source, opts?)` — William Blau's True Strength Index
285
+ (momentum-class; `{ tsi, signal }`). Double-EMA-smoothed ratio of
286
+ one-bar price changes vs their absolute values, scaled ×100.
287
+ Bounded `[-100, 100]` (flat input → NaN at tsi). Defaults
288
+ `(firstSmoothing, secondSmoothing, signalLength) = (25, 13, 13)`
289
+ per TradingView's published TSI study. Note: this is the
290
+ **momentum**-class TSI; the **trend**-class True Strength Index
291
+ ships in Task 17 as `ta.trendStrengthIndex`.
292
+
293
+ Each primitive ships the §22.10 set: impl + unit + property +
294
+ golden + bench pair + conformance scenario (using the Phase-2
295
+ `inlineSource` extension from Task 1) + auto-generated
296
+ `docs/primitives/ta/<id>.md` page.
297
+
298
+ `TA_REGISTRY_METADATA` extends with three new entries: `pmo` and
299
+ `tsi` advertise `primarySeriesKey` + `visibleSeriesKeys` with
300
+ `yDomain: { kind: "auto" }`; `smi` is `{ kind: "fixed", min: -100,
301
+ max: 100 }`.
302
+
303
+ Core surface widens with `PmoOpts` / `PmoResult`, `SmiOpts` /
304
+ `SmiResult`, `TsiOpts` / `TsiResult` plus matching `TaNamespace`
305
+ methods and throw-sentinel stubs. `STATEFUL_PRIMITIVES` extends
306
+ with `ta.pmo` / `ta.smi` / `ta.tsi` (all `slot: true`).
307
+
308
+ Three conformance scenarios (`taPmo.scenario.ts`,
309
+ `taSmi.scenario.ts`, `taTsi.scenario.ts`) registered against
310
+ `ALL_SCENARIOS` via the Task-1 `inlineSource` extension.
311
+ Plot-hash pinning deferred to Phase-2 closeout (Task 30) per the
312
+ established cross-functional scenario convention.
313
+
314
+ Provenance: ported from `invinite/src/components/trading-chart/
315
+ indicators/{pmo,smi,tsi}.ts` at commit
316
+ `078f41fe2569d659d5aba726da8bcb5d3e2ced02`.
317
+
318
+ - 38fb475: Phase-2 Task 29 — Universal `opts.offset` backfill on Phase-1 primitives.
319
+
320
+ Wires the universal `opts.offset` (PLAN.md §9.1) onto every Phase-1
321
+ `ta.*` primitive: `sma`, `ema`, `stdev`, `bb`, `rsi`, `macd`, `atr`,
322
+ `crossover`, `crossunder`. Positive `offset` shifts the returned
323
+ series so `series.current` reads the value `offset` bars ago
324
+ (matching `lib/applyOffset`'s `out[i] = values[i − offset]`
325
+ semantics); negative `offset` reads into the future (NaN /
326
+ undefined at the head). `offset === 0` is the strict identity
327
+ fast path — returns the slot's cached un-shifted Series with the
328
+ same reference as before this change (existing identity-pinned
329
+ tests continue to pass).
330
+
331
+ Surface expansion (core, minor):
332
+
333
+ - `offset?: number` added to `SmaOpts`, `EmaOpts`, `StdevOpts`,
334
+ `BbOpts`, `RsiOpts`, `MacdOpts`, `AtrOpts` (Phase-1 opts types
335
+ that previously had no `offset` field).
336
+ - New `CrossoverOpts` / `CrossunderOpts` types (the two cross
337
+ primitives previously took no opts bag); `TaNamespace.crossover`
338
+ / `crossunder` signatures gain an optional 3rd opts arg.
339
+ - New `makeShiftedSeriesView` runtime helper next to
340
+ `makeSeriesView` (in `packages/runtime/src/seriesView.ts`,
341
+ re-exported from the runtime barrel) — wraps a `RingBufferLike<T>`
342
+ in a Proxy that adjusts `at(n)` reads by `offset`.
343
+
344
+ Composite primitives (`bb`, `macd`) shift all outputs in lockstep
345
+ under a single `offset` value, returning a frozen result record
346
+ cached per offset on the slot. Sub-slot outputs (sma's middle,
347
+ ema's signal) are accessed through their captured ring-buffer
348
+ reference so the parent primitive doesn't re-enter the sub-slot's
349
+ compute on the shifted-view lookup.
350
+
351
+ Compiler patch: the ambient shim in `packages/compiler/src/program.ts`
352
+ mirrors the core type changes (new `offset?` fields + new
353
+ `CrossoverOpts` / `CrossunderOpts` types + extended `TaNamespace`
354
+ signatures).
355
+
356
+ Goldens, bench thresholds, and conformance scenarios are
357
+ unchanged — `offset === 0` is the default and exercises the
358
+ existing code paths. New per-primitive `<id>.test.ts` and
359
+ `<id>.property.test.ts` cases cover positive, negative, zero, and
360
+ identity-cache behaviour for offset.
361
+
362
+ - 38fb475: Phase-2 Task 9 — oscillator ports: `ta.cci`, `ta.stoch`, `ta.williamsR`.
363
+
364
+ Ships three foundational momentum / oscillator primitives under
365
+ `packages/runtime/src/ta/`:
366
+
367
+ - `ta.cci(source, length, opts?)` — Commodity Channel Index over a
368
+ configurable source (typically `bar.hlc3`). Lambert constant
369
+ `scaling = 0.015` hard-coded; flat-window (`meanDev === 0`) emits
370
+ `NaN`. Unbounded by construction.
371
+ - `ta.stoch(opts?)` — Stochastic Oscillator (`%K` + `%D`) over
372
+ `bar.high` / `bar.low` / `bar.close`. Composes `ta.highest` +
373
+ `ta.lowest` + two chained `ta.sma` smoothing layers via sub-slot
374
+ ids. Bounded `[0, 100]` (or `NaN`). Defaults `(kLength=14,
375
+ kSmoothing=3, dLength=3)`.
376
+ - `ta.williamsR(length, opts?)` — Williams %R over `bar.high` /
377
+ `bar.low` / `bar.close`. Composes `ta.highest` + `ta.lowest`.
378
+ Bounded `[-100, 0]` (or `NaN`).
379
+
380
+ Each primitive ships the §22.10 set: impl + unit + property + golden
381
+
382
+ - bench pair + conformance scenario (inlined per Task 1) +
383
+ auto-generated `docs/primitives/ta/<id>.md`.
384
+
385
+ Introduces a new metadata layer on the runtime registry:
386
+
387
+ - `TA_REGISTRY_METADATA: Readonly<Partial<Record<keyof typeof
388
+ TA_REGISTRY, PrimitiveMetadata>>>` — per-primitive `primarySeriesKey`,
389
+ `visibleSeriesKeys`, and `yDomain` hints for renderers (pane layout,
390
+ legend ordering, y-axis scaling). `ta.stoch` records
391
+ `primarySeriesKey: "k"`, `visibleSeriesKeys: ["k", "d"]`,
392
+ `yDomain: { kind: "fixed", min: 0, max: 100 }`; `ta.williamsR`
393
+ records `yDomain: { kind: "fixed", min: -100, max: 0 }`. Unbounded
394
+ primitives (e.g. `ta.cci`, `ta.sma`) carry no metadata entry —
395
+ consumers default to `auto`.
396
+
397
+ Core surface widens with `CciOpts`, `StochOpts`, `WilliamsROpts` opts
398
+ bags + `StochResult` two-output type, plus the matching `TaNamespace`
399
+ methods and throw-sentinel stubs. `STATEFUL_PRIMITIVES` extends with
400
+ `ta.cci` / `ta.stoch` / `ta.williamsR` (all `slot: true`). Compiler
401
+ shim mirrors the new core surface.
402
+
403
+ Three conformance scenarios (`taCci.scenario.ts`, `taStoch.scenario.ts`,
404
+ `taWilliamsR.scenario.ts`) registered against `ALL_SCENARIOS` via
405
+ the Task-1 `inlineSource` extension. Plot-hash pinning deferred to
406
+ Phase-2 closeout (Task 30) per the established cross-functional
407
+ scenario convention.
408
+
409
+ DIVERGENCE from invinite reference (`stoch.ts`): the spec requires
410
+ flat-window (`hh === ll`) → `NaN` at `k`, whereas invinite falls back
411
+ to the prior valid kRaw (or 50 on the first slot, per TradingView).
412
+ The task spec overrides; documented in the impl's provenance header.
413
+
414
+ `CciOpts` intentionally narrows away invinite's `scaling` knob —
415
+ chartlang hard-codes the canonical Lambert constant.
416
+
417
+ - 38fb475: Phase-2 Task 12 — oscillator ports: `ta.kst`, `ta.fisher`,
418
+ `ta.klinger`, `ta.rvgi`.
419
+
420
+ Ships four more multi-output oscillator primitives under
421
+ `packages/runtime/src/ta/`:
422
+
423
+ - `ta.kst(source, opts?)` — Know Sure Thing (Martin Pring, 1992).
424
+ Weighted sum of four SMA-smoothed percentage ROCs plus an SMA
425
+ signal line. Composes 4 `ta.sma` sub-slots for the per-ROC
426
+ smoothing plus one `ta.sma` for the signal; the four percentage
427
+ ROCs are computed inline against a shared `sourceWindow` ring
428
+ (mirrors `ta.coppock` — `ta.change` emits absolute deltas, while
429
+ KST needs percentage rate-of-change). Defaults
430
+ `(10, 15, 20, 30, 10, 10, 10, 15, 9)`.
431
+ - `ta.fisher(length, opts?)` — John Ehlers' Fisher Transform over
432
+ rolling `bar.hl2`. Composes `ta.highest` + `ta.lowest` sub-slots;
433
+ the clamp / atanh / EMA-blend recurrence is bespoke. The `trigger`
434
+ output is the prior bar's `fisher` value (1-bar lag); first bar's
435
+ `trigger` is NaN. Diverges from invinite's ±0.999 clamp per task
436
+ spec — when the recurrence would drive `|x| ≥ 1` we emit NaN at
437
+ `fisher` and hold the recurrence state.
438
+ - `ta.klinger(opts?)` — Klinger Volume Oscillator. Per-bar Volume
439
+ Force accumulator drives the difference of two `ta.ema` sub-slots
440
+ (`fastLength` / `slowLength`); the `signal` is a third
441
+ `ta.ema(klinger, signalLength)`. Defaults `(34, 55, 13)`.
442
+ - `ta.rvgi(opts?)` — Relative Vigor Index (John Ehlers, 2002).
443
+ 4-bar `(1, 2, 2, 1) / 6` weighted numerator (`close − open`) and
444
+ denominator (`high − low`), each smoothed via `ta.sma` sub-slot;
445
+ `rvgi = numSma / denSma`. Signal is a 4-bar weighted sum of the
446
+ rvgi line. Defaults `length = 10`; flat-range bars emit NaN.
447
+
448
+ Each primitive ships the §22.10 set: impl + unit + property + golden
449
+
450
+ - bench pair + conformance scenario (inlined per Task 1) +
451
+ auto-generated `docs/primitives/ta/<id>.md`.
452
+
453
+ Extends `TA_REGISTRY_METADATA` with four new entries (all
454
+ `primarySeriesKey` + `visibleSeriesKeys`; all `yDomain: { kind:
455
+ "auto" }` per task §5):
456
+
457
+ - `kst`: `primarySeriesKey: "kst"`, `visibleSeriesKeys: ["kst", "signal"]`.
458
+ - `fisher`: `primarySeriesKey: "fisher"`, `visibleSeriesKeys: ["fisher", "trigger"]`.
459
+ - `klinger`: `primarySeriesKey: "klinger"`, `visibleSeriesKeys: ["klinger", "signal"]`.
460
+ - `rvgi`: `primarySeriesKey: "rvgi"`, `visibleSeriesKeys: ["rvgi", "signal"]`.
461
+
462
+ Core surface widens with `KstOpts`, `FisherOpts`, `KlingerOpts`,
463
+ `RvgiOpts` opts bags + `KstResult`, `FisherResult`, `KlingerResult`,
464
+ `RvgiResult` two-output types, plus the matching `TaNamespace`
465
+ methods and throw-sentinel stubs. `STATEFUL_PRIMITIVES` extends
466
+ with `ta.kst` / `ta.fisher` / `ta.klinger` / `ta.rvgi` (all
467
+ `slot: true`). Compiler shim mirrors the new core surface.
468
+
469
+ Four conformance scenarios (`taKst.scenario.ts`,
470
+ `taFisher.scenario.ts`, `taKlinger.scenario.ts`,
471
+ `taRvgi.scenario.ts`) registered against `ALL_SCENARIOS` via the
472
+ Task-1 `inlineSource` extension. Plot-hash pinning deferred to
473
+ Phase-2 closeout (Task 30) per the established multi-output scenario
474
+ convention.
475
+
476
+ - 38fb475: Phase-2 Task 10 — oscillator ports: `ta.ppo`, `ta.dpo`,
477
+ `ta.connorsRsi`.
478
+
479
+ Ships three derived oscillator primitives under
480
+ `packages/runtime/src/ta/`:
481
+
482
+ - `ta.ppo(source, opts?)` — Percentage Price Oscillator, the
483
+ scale-invariant cousin of MACD. Three outputs (`{ ppo, signal,
484
+ hist }`) over `100 * (fastEma - slowEma) / slowEma`. Composes three
485
+ `ta.ema` sub-slots (`${slotId}/fast`, `${slotId}/slow`,
486
+ `${slotId}/signal`) per §9.4 — folds invinite's private EMA copy
487
+ onto the canonical primitive. Defaults `(12, 26, 9)`. `slow === 0`
488
+ emits `NaN` at the PPO line; signal can still be defined off prior
489
+ values.
490
+ - `ta.dpo(source, length, opts?)` — Detrended Price Oscillator
491
+ (non-centered, TradingView default). `dpo[i] = source[i -
492
+ displacement] - sma[i]` with `displacement = floor(length / 2) +
493
+ 1`. Composes one `ta.sma` sub-slot plus a per-slot source-window
494
+ Float64RingBuffer for the O(1) per-bar shifted-source lookup.
495
+ - `ta.connorsRsi(source, opts?)` — Connors RSI, a `[0, 100]`-bounded
496
+ blend of `RSI(source, rsiLength)`, `RSI(streak, streakLength)`,
497
+ and `PercentRank(ROC(source, 1), rocLength)`. Composes two
498
+ `ta.rsi` sub-slots — no private RSI math duplication. Defaults
499
+ `(3, 2, 100)`. Sub-component NaN → component skipped in the
500
+ average (per task spec §6, diverges from invinite's stricter
501
+ all-finite requirement to align with the Pine semantic).
502
+
503
+ Each primitive ships the §22.10 set: impl + unit + property +
504
+ golden + bench pair + conformance scenario (inlined per Task 1) +
505
+ auto-generated `docs/primitives/ta/<id>.md`.
506
+
507
+ `TA_REGISTRY_METADATA` extends with:
508
+
509
+ - `ppo`: `primarySeriesKey: "ppo"`, `visibleSeriesKeys: ["ppo",
510
+ "signal", "hist"]`, `yDomain: { kind: "auto" }`.
511
+ - `connorsRsi`: `yDomain: { kind: "fixed", min: 0, max: 100 }`.
512
+ - `dpo`: no metadata entry (unbounded — consumers default to
513
+ `auto`).
514
+
515
+ Core surface widens with `PpoOpts`, `DpoOpts`, `ConnorsRsiOpts`
516
+ opts bags + `PpoResult` three-output type, plus the matching
517
+ `TaNamespace` methods and throw-sentinel stubs.
518
+ `STATEFUL_PRIMITIVES` extends with `ta.ppo` / `ta.dpo` /
519
+ `ta.connorsRsi` (all `slot: true`). Compiler shim mirrors the new
520
+ core surface.
521
+
522
+ Three conformance scenarios (`taPpo.scenario.ts`,
523
+ `taDpo.scenario.ts`, `taConnorsRsi.scenario.ts`) registered against
524
+ `ALL_SCENARIOS` via the Task-1 `inlineSource` extension.
525
+ Plot-hash pinning deferred to Phase-2 closeout (Task 30) per the
526
+ established cross-functional scenario convention.
527
+
528
+ DEVIATIONS from invinite reference (commit
529
+ `078f41fe2569d659d5aba726da8bcb5d3e2ced02`):
530
+
531
+ - `ppo.ts` — invinite carries a private EMA copy
532
+ (`computeMaSeries(oscillatorMaType, ...)`); chartlang routes
533
+ through the canonical `ta.ema` primitive via sub-slot
534
+ composition (matches `ta.macd`). §9.4 fold satisfied.
535
+ - `dpo.ts` — only the non-centered (TradingView default) render
536
+ mode is shipped. Invinite's `centered: true` mode emits
537
+ `dpo[i] = src[i] - sma[i + displacement]`, which depends on the
538
+ future SMA; chartlang's append-only ring-buffer contract can't
539
+ backfill, so that mode is deferred. Documented in the impl's
540
+ provenance header.
541
+ - `connorsRsi.ts` — invinite requires all three components finite
542
+ for the CRSI line to define; the task spec (§6) overrides with
543
+ "sub-component NaN → component skipped in the average". We
544
+ follow the spec, which tightens alignment with the Pine
545
+ `ta.connorsRsi` semantic where streak-RSI warmup doesn't gate
546
+ the rsi-on-close component.
547
+
548
+ - 38fb475: Phase-2 Task 11 — oscillator ports: `ta.stochRsi`, `ta.ultimateOsc`,
549
+ `ta.coppock`.
550
+
551
+ Ships three more oscillator primitives under
552
+ `packages/runtime/src/ta/`:
553
+
554
+ - `ta.stochRsi(source, opts?)` — Stochastic RSI (`%K` + `%D`).
555
+ Composes `ta.rsi` + `ta.highest` + `ta.lowest` + two chained
556
+ `ta.sma` smoothing layers via sub-slot ids. Bounded `[0, 100]`
557
+ (or `NaN`). Defaults `(rsiLength=14, stochLength=14, kSmoothing=3,
558
+ dSmoothing=3)`. Flat-RSI-window (`hh === ll`) emits `NaN` at `k`
559
+ — diverges from invinite's prev-or-50 fallback per task spec.
560
+ - `ta.ultimateOsc(opts?)` — Larry Williams' Ultimate Oscillator over
561
+ `bar.high` / `bar.low` / `bar.close`. Weighted average of three
562
+ buying-pressure / true-range ratios across `shortLength` /
563
+ `mediumLength` / `longLength` windows (defaults `7` / `14` / `28`).
564
+ Bounded `[0, 100]` (or `NaN`); zero-TR window emits `NaN`.
565
+ - `ta.coppock(source, opts?)` — Edwin Coppock's Curve.
566
+ `WMA(ROC(source, roc1Length) + ROC(source, roc2Length),
567
+ wmaLength)` over percentage ROC. Defaults `(11, 14, 10)`. Unbounded;
568
+ zero-crossings are the canonical signal. Inlines the percentage-ROC
569
+ computation against its own `sourceWindow` (the spec's hint to
570
+ compose `ta.change` does not fit — `ta.change` emits absolute
571
+ deltas, not percentages).
572
+
573
+ Each primitive ships the §22.10 set: impl + unit + property + golden
574
+
575
+ - bench pair + conformance scenario (inlined per Task 1) +
576
+ auto-generated `docs/primitives/ta/<id>.md`.
577
+
578
+ Extends `TA_REGISTRY_METADATA` with two new bounded-oscillator
579
+ entries:
580
+
581
+ - `stochRsi`: `primarySeriesKey: "k"`, `visibleSeriesKeys: ["k", "d"]`,
582
+ `yDomain: { kind: "fixed", min: 0, max: 100 }`.
583
+ - `ultimateOsc`: `yDomain: { kind: "fixed", min: 0, max: 100 }`.
584
+
585
+ `ta.coppock` is unbounded — no metadata entry; consumers default to
586
+ `auto`.
587
+
588
+ Core surface widens with `StochRsiOpts`, `UltimateOscOpts`,
589
+ `CoppockOpts` opts bags + `StochRsiResult` two-output type, plus the
590
+ matching `TaNamespace` methods and throw-sentinel stubs.
591
+ `STATEFUL_PRIMITIVES` extends with `ta.stochRsi` / `ta.ultimateOsc` /
592
+ `ta.coppock` (all `slot: true`). Compiler shim mirrors the new core
593
+ surface.
594
+
595
+ Three conformance scenarios (`taStochRsi.scenario.ts`,
596
+ `taUltimateOsc.scenario.ts`, `taCoppock.scenario.ts`) registered
597
+ against `ALL_SCENARIOS` via the Task-1 `inlineSource` extension.
598
+ Plot-hash pinning deferred to Phase-2 closeout (Task 30) per the
599
+ established cross-functional scenario convention.
600
+
601
+ - 38fb475: Phase-2 Task 1 — three foundational widenings every subsequent
602
+ Phase-2 port depends on:
603
+
604
+ 1. **`PlotKind` expansion (3 → 9).** Adds `histogram`, `bars`,
605
+ `area`, `filled-band`, `label`, `marker` per PLAN.md §7.3. The
606
+ `PlotStyle` discriminated union in
607
+ `@invinite-org/chartlang-adapter-kit` extends in lockstep; the
608
+ `validateEmission` switch grows matching arms with per-kind
609
+ payload rules; the `capabilities` builder gains `histogram()` /
610
+ `bars()` / `area()` / `filledBand()` / `label()` / `marker()` /
611
+ `allPhase2Plots()`. The canvas2d reference adapter ships six new
612
+ pure-on-`RenderCtx` renderers (`render/histogram.ts`, `bars.ts`,
613
+ `area.ts`, `filledBand.ts`, `label.ts`, `marker.ts`) and flips
614
+ `CANVAS2D_CAPABILITIES.plots` to `capabilities.allPhase2Plots()`
615
+ (9 kinds). `RenderCtx` + `MockCanvas2DContext` extend with
616
+ `fillText`, `globalAlpha`, `font`, `textAlign`, `textBaseline`.
617
+
618
+ 2. **`Bar` derived sources.** Extends the script-facing `Bar`
619
+ (`packages/core/src/types.ts`) with the four pre-computed derived
620
+ sources `hl2` / `hlc3` / `ohlc4` / `hlcc4`. The runtime's
621
+ `BarView` (`packages/runtime/src/streamState.ts`) already
622
+ populates these on every close — Phase 2 surfaces them so authors
623
+ can write `ta.cci(bar.hlc3, 20)` like Pine. No runtime change.
624
+
625
+ 3. **`Scenario.inlineSource`.** Extends the conformance `Scenario`
626
+ type (`packages/conformance/src/runConformanceSuite.ts`) with an
627
+ optional `inlineSource?: string` field that is mutually exclusive
628
+ with the existing `scriptPath?: string`. `runConformanceSuite`
629
+ writes the inline source to the existing `.cache/` tmp file and
630
+ compiles + imports it exactly like the `scriptPath` branch, with
631
+ a virtual `<inline:${id}>.chart.ts` `sourcePath` so callsite-id
632
+ injection produces stable, pinnable slot ids. Phase-2 ports use
633
+ this to carry their `defineIndicator` source inline rather than
634
+ spawning 80+ files in `examples/scripts/`.
635
+
636
+ The new `PLOT_KIND_COVERAGE_SCENARIO` exercises the `inlineSource`
637
+ path + the wider capability surface end-to-end (one inline
638
+ `plot(bar.close)` + `hline(50)` script; asserts no
639
+ `unsupported-plot-kind` and no `malformed-emission` diagnostics
640
+ fire). Per-port Phase-2 tasks (Tasks 21+) each add their own
641
+ scenario asserting the specific new kind's drained emissions once
642
+ the runtime acquires the matching emission path.
643
+
644
+ No runtime / host-worker source-level changes in this task —
645
+ `BarView` already carries the four derived fields, and the
646
+ `PlotKind` expansion is additive at every consumer.
647
+
648
+ - 38fb475: Phase 2 quality-pass fixes (cross-cutting).
649
+
650
+ - `@invinite-org/chartlang-core`: new `STATEFUL_PRIMITIVES_BY_NAME`
651
+ export — a `ReadonlyMap<string, StatefulPrimitiveEntry>` derived
652
+ from the same canonical entry list as `STATEFUL_PRIMITIVES`. Lets
653
+ the compiler look up entries by callee name in O(1) instead of an
654
+ O(n) scan over the 93-entry set on every visited call site.
655
+ - `@invinite-org/chartlang-compiler`: `callsiteIdInjection` and
656
+ `statefulCallInLoop` now consume `STATEFUL_PRIMITIVES_BY_NAME`
657
+ via a `statefulByName: ReadonlyMap<string, StatefulPrimitiveEntry>`
658
+ parameter (was `statefulSet: ReadonlySet<StatefulPrimitiveEntry>`).
659
+ Internal-only API change — neither pass is publicly exported from
660
+ `packages/compiler/src/index.ts`. The per-pass `hasName` /
661
+ `findEntry` helpers are dropped.
662
+ - `@invinite-org/chartlang-runtime`: `ta/lib/maTypes.ts` re-exports
663
+ `MaTypeNoVolume` from `@invinite-org/chartlang-core` instead of
664
+ re-declaring it locally — keeps the two definitions from drifting
665
+ when a 6th MA kind is added. `MaType` (which adds `"vwma"`) stays
666
+ local since core has no equivalent. `__fixtures__/syntheticBars.ts`
667
+ and `nanTick.test.ts`'s inline `Bar` literals now carry the
668
+ `hl2` / `hlc3` / `ohlc4` / `hlcc4` fields the Phase-2 `Bar`
669
+ extension made required (the per-package tsconfig had been hiding
670
+ the typecheck miss).
671
+
672
+ Also: `examples/canvas2d-adapter` — extracted the duplicated
673
+ `dashPattern(LineStyle)` from `render/area.ts` + `render/horizontalLine.ts`
674
+ into `render/lineDash.ts`, re-exported from `render/index.ts`. No
675
+ behaviour change.
676
+
677
+ - 38fb475: Phase-2 Task 26 — S/R ports: `ta.chandelier`, `ta.chandeKrollStop`,
678
+ `ta.williamsFractal`.
679
+
680
+ Ships three new S/R `ta.*` primitives under
681
+ `packages/runtime/src/ta/`:
682
+
683
+ - `ta.chandelier(opts?)` — Chandelier Exit returning
684
+ `{ long, short }`. Composes Phase-1 `ta.atr` plus Task-5
685
+ `ta.highest` / `ta.lowest` at sub-slots `${slotId}/atr` /
686
+ `${slotId}/highHigh` / `${slotId}/lowLow`. `long = highest(high,
687
+ length) − multiplier · atr(length)`; `short` symmetric. Defaults
688
+ `length = 22`, `multiplier = 3` per Pine canonical. Source
689
+ hard-coded to `bar.high` / `bar.low` (deliberate divergence from
690
+ invinite's `source` parameter — matches Pine `ta.chandelier_exit`).
691
+
692
+ - `ta.chandeKrollStop(opts?)` — Chande Kroll Stop returning
693
+ `{ long, short }`. Two-pass smoothed trailing stop: first pass
694
+ computes `firstHigh = highest(high, length) − multiplier · atr` /
695
+ `firstLow = lowest(low, length) + multiplier · atr` (composed via
696
+ `ta.atr` + `ta.highest` / `ta.lowest` sub-slots); second pass
697
+ walks a slot-owned `Float64RingBuffer` of size `smoothingLength`
698
+ for the rolling max / min. Defaults `length = 10`, `multiplier = 1`,
699
+ `smoothingLength = 9` (matches Chande Kroll's 1995 paper).
700
+
701
+ - `ta.williamsFractal(opts?)` — Williams Fractal returning
702
+ `{ up, down }` as **price-level series** (NaN when no fractal,
703
+ `bar.high(centre)` for up-fractal, `bar.low(centre)` for down).
704
+ Self-contained centred-window scan over a `2 · length + 1` ring
705
+ buffer per side. Output is centred: at live bar `t`, the value
706
+ emitted reflects bar `t − length`'s fractal status (when bar `t`
707
+ closes, we now have the right-window bars to confirm bar
708
+ `t − length`). Default `length = 2` (5-bar window). Strict
709
+ comparison: tied highs/lows in the window → no fractal.
710
+
711
+ Deviation from the task spec's literal `Series<boolean>` wording:
712
+ emits price levels instead so the `marker` plot has a meaningful
713
+ y-anchor. Matches invinite's `upFractals[i] = high` shape.
714
+
715
+ Each primitive ships the §22.10 set (impl + unit + property + golden
716
+ hash + bench pair) plus a `taChandelier.scenario.ts`,
717
+ `taChandeKrollStop.scenario.ts`, and `taWilliamsFractal.scenario.ts`
718
+ conformance scenario. JSDoc per §17.2 with `@formula`, `@warmup`,
719
+ `@since 0.2`, `@experimental`, `@example`, and `@anchors`.
720
+
721
+ **`PlotOptsStyle` marker widening (core + runtime + compiler shim).**
722
+ Adds the `marker` variant to `PlotOptsStyle` in core (mirrors the
723
+ adapter-kit's `PlotStyle.marker` shape declared by Task 1), the
724
+ matching dispatch branch to `buildStyle` in
725
+ `packages/runtime/src/emit/plot.ts`, and the same widening in the
726
+ compiler's ambient shim. The Williams Fractal scenario is the first
727
+ to exercise the marker plot kind end-to-end. The cap-gated dispatch
728
+ path is unit-covered in `plot.test.ts`'s new marker case.
729
+
730
+ `TaNamespace` (core) and `RuntimeTaNamespace` (runtime) extend with
731
+ three new methods + matching opts / result types. `STATEFUL_PRIMITIVES`
732
+ appends three new `slot: true` entries. `TA_REGISTRY` adds three
733
+ entries plus `TA_REGISTRY_METADATA` records for each
734
+ (`primarySeriesKey` / `visibleSeriesKeys` / `yDomain = auto`).
735
+
736
+ Auto-generated documentation pages land at
737
+ `docs/primitives/ta/{chandelier,chandeKrollStop,williamsFractal}.md`
738
+ via the Task-2 generator. The `docs/primitives/ta/index.md` carries a
739
+ new "S/R (Task 26)" subsection.
740
+
741
+ - 38fb475: Phase-2 Task 25 — S/R ports: `ta.psar` and `ta.supertrend`.
742
+
743
+ Ships two new flagship trend-following S/R `ta.*` primitives under
744
+ `packages/runtime/src/ta/`:
745
+
746
+ - `ta.psar(opts?)` — Wilder Parabolic SAR returning
747
+ `{ sar, direction }`. Self-contained state machine over
748
+ `bar.high` / `bar.low` / `bar.close` with extreme-point +
749
+ acceleration-factor tracking and trend-flip semantics. Defaults
750
+ `accelerationStart = 0.02`, `accelerationStep = 0.02`,
751
+ `accelerationMax = 0.2` per Pine / Wilder. Bar 0 emits the seed
752
+ (`sar = bar.low`, `direction = +1`); bar 1 decides the initial
753
+ direction from `close[1] >= close[0]`; bar 2+ runs the standard
754
+ recurrence with the lower/upper-bound clamps against the prior
755
+ two bars' lows/highs.
756
+ - `ta.supertrend(opts?)` — ATR-driven trailing-stop trend follower
757
+ returning `{ line, direction }`. Composes Phase-1 `ta.atr` at
758
+ sub-slot `${slotId}/atr`, so a fix to ATR flows in for free.
759
+ Defaults `length = 10`, `multiplier = 3`. Reads `bar.hl2` for the
760
+ band midpoint (Pine-canonical). The final-band persistence rule
761
+ carries the prior band forward unless the prior close pierced it;
762
+ direction flips when the current close crosses the prior
763
+ `finalUpper` / `finalLower`.
764
+
765
+ Both primitives suspend their recurrence state on NaN OHLC so the
766
+ next finite bar resumes from the prior state. `replaceHead`
767
+ correctness is asserted via append-vs-replaceHead property tests
768
+ over adversarial sharp-reversal sequences — both implementations
769
+ snapshot the state at the start of each bar BEFORE the close-side
770
+ recurrence advances so a final tick replays from the seed.
771
+
772
+ Each primitive ships the §22.10 set: impl + unit + property +
773
+ golden + bench pair + conformance scenario (using the Phase-2
774
+ `inlineSource` extension from Task 1) + auto-generated
775
+ `docs/primitives/ta/<id>.md`. `TA_REGISTRY_METADATA` carries the
776
+ multi-output / y-domain hints (`psar: { primarySeriesKey: "sar",
777
+ visibleSeriesKeys: ["sar", "direction"], yDomain: auto }`,
778
+ `supertrend: { primarySeriesKey: "line", visibleSeriesKeys:
779
+ ["line", "direction"], yDomain: auto }`).
780
+
781
+ Core adds `PsarOpts`, `PsarResult`, `SupertrendOpts`,
782
+ `SupertrendResult` exports + the two `TaNamespace` methods.
783
+ `STATEFUL_PRIMITIVES` grows by 2 (`ta.psar`, `ta.supertrend`; both
784
+ `slot: true`). `TA_REGISTRY` mirrors with the leading
785
+ `slotId: string` on each method.
786
+
787
+ - 38fb475: Phase-2 Task 27 — S/R ports: `ta.zigZag`, `ta.pivotsHighLow`,
788
+ `ta.pivotsStandard`, and `ta.volatilityStop` (closes §9.2's S/R
789
+ list).
790
+
791
+ Ships four new S/R `ta.*` primitives under
792
+ `packages/runtime/src/ta/`:
793
+
794
+ - `ta.zigZag(opts?)` — streaming swing-pivot detector. Walks the
795
+ close series tracking a running candidate pivot; confirms a new
796
+ pivot when the price has reversed by ≥ `deviation %` AND `depth`
797
+ bars have elapsed. Returns `{ value, direction }` where `value`
798
+ carries the most-recently-confirmed pivot price (held constant
799
+ between confirmations, NaN before the first) and `direction` is
800
+ `+1` / `-1` / NaN. Defaults `deviation = 5`, `depth = 10`.
801
+ Streaming adaptation of invinite's batch ZigZag — invinite's
802
+ linear-interpolation rendering between pivots isn't representable
803
+ in the append-only `Series` model, so the output is the closest
804
+ surface (a "trailing reference level").
805
+ - `ta.pivotsHighLow(opts?)` — centred-window swing-pivot detector
806
+ with asymmetric `(leftLength, rightLength)` confirmation windows.
807
+ Returns `{ high, low }` (price-level series — `bar.high(centre)`
808
+ or `bar.low(centre)` when a pivot confirms, NaN otherwise).
809
+ Mirrors invinite's tie-break: strict-greater on the left window,
810
+ geq on the right (matches Pine `ta.pivothigh`). Defaults
811
+ `leftLength = rightLength = 4` (9-bar window).
812
+ - `ta.pivotsStandard(opts?)` — classical daily pivot-point levels
813
+ (P, R1..R3, S1..S3) derived from the previous UTC-day's HLC.
814
+ Returns seven `Series<number>` (`{ pp, r1, s1, r2, s2, r3, s3 }`).
815
+ Four formula systems: `"classic"` (default), `"fibonacci"`,
816
+ `"camarilla"`, `"woodie"`. UTC-day boundary detection via
817
+ `Math.floor(bar.time / 86_400_000)`. R4 / R5 / S4 / S5 levels
818
+ (Camarilla's full table) and DeMark / Traditional systems
819
+ intentionally defer per the Phase-2 README "Deferred / Follow-Up
820
+ Work" footnote.
821
+ - `ta.volatilityStop(opts?)` — PSAR-like trend-following stop
822
+ driven by ATR. Composes Phase-1 `ta.atr` at sub-slot
823
+ `${slotId}/atr`. Returns `{ value, direction }` (`+1` uptrend →
824
+ stop is BELOW price; `-1` downtrend → stop ABOVE). Defaults
825
+ `length = 20`, `multiplier = 2`. Source hard-coded to `bar.close`
826
+ (Pine `ta.vstop` convention; invinite's `source` field is
827
+ omitted, a `source` opt could land in a follow-up).
828
+
829
+ All four primitives suspend their recurrence state on NaN OHLC so
830
+ the next finite bar resumes from the prior state. `replaceHead`
831
+ correctness is asserted via append-vs-replaceHead property tests
832
+ over `arbBar` fixtures — ZigZag and Volatility Stop snapshot their
833
+ state-machine state at the start of each bar BEFORE the close-side
834
+ recurrence advances so a final tick replays from the seed
835
+ (mirrors Task 25's PSAR / Supertrend pattern).
836
+
837
+ Each primitive ships the §22.10 set: impl + unit + property +
838
+ golden + bench pair + conformance scenario (using the Phase-2
839
+ `inlineSource` extension from Task 1) + auto-generated
840
+ `docs/primitives/ta/<id>.md`. `TA_REGISTRY_METADATA` carries the
841
+ multi-output / y-domain hints (all four use `yDomain: { kind:
842
+ "auto" }`).
843
+
844
+ Core adds `ZigZagOpts`, `ZigZagResult`, `PivotsHighLowOpts`,
845
+ `PivotsHighLowResult`, `PivotsStandardOpts`,
846
+ `PivotsStandardResult`, `PivotsStandardSystem`,
847
+ `VolatilityStopOpts`, and `VolatilityStopResult` exports + four
848
+ `TaNamespace` methods. `STATEFUL_PRIMITIVES` grows by 4 (all
849
+ `slot: true`). `TA_REGISTRY` mirrors with the leading
850
+ `slotId: string` on each method.
851
+
852
+ Compiler patch: the ambient shim mirrors the four new methods +
853
+ nine new types.
854
+
855
+ - 38fb475: Phase-2 Task 28 — statistical `ta.*` ports: `ta.median`, `ta.adr`,
856
+ `ta.ulcerIndex`.
857
+
858
+ Ships three new statistical primitives under
859
+ `packages/runtime/src/ta/`:
860
+
861
+ - `ta.median(source, length, opts?)` — rolling median over the
862
+ trailing `length` source values. Odd-`length` → middle value;
863
+ even-`length` → mean of the two middle values. NaN slots are
864
+ dropped from the sort (window length effectively shrinks). Range
865
+ invariant pinned: `min(window) ≤ out ≤ max(window)`. Tick-mode
866
+ substitutes the head value before sorting (closed window
867
+ unchanged).
868
+ - `ta.adr(opts?)` — Average Daily Range. SMA of `high − low` over
869
+ the trailing `length` (default `14`) completed UTC calendar days.
870
+ Reads `bar.high` / `bar.low` / `bar.time` directly (no `source`
871
+ param). Phase-2 keys "daily" on the UTC midnight boundary
872
+ (`Math.floor(bar.time / 86_400_000)`); Phase 4 lifts this onto
873
+ `syminfo.session`. The in-progress (currently-aggregating) day is
874
+ NEVER included in the average — matches invinite's "completed N
875
+ daily bars" semantics. Tick mode emits the cached SMA (no day-
876
+ boundary advance per the runtime tick invariant).
877
+ - `ta.ulcerIndex(source, length, opts?)` — drawdown-based volatility
878
+ (rolling RMS of percent declines from the rolling-window high).
879
+ Composes `ta.highest` via sub-slot id `${slotId}/highest`. Range
880
+ invariant pinned: `out ≥ 0`. NaN source → NaN output (window
881
+ unchanged).
882
+
883
+ Each primitive ships the §22.10 set: impl + unit + property +
884
+ golden + bench pair + conformance scenario (inline-source per Task
885
+ 1's extension) + auto-generated `docs/primitives/ta/<id>.md`.
886
+
887
+ `STATEFUL_PRIMITIVES` grows by `+3` (`ta.median`, `ta.adr`,
888
+ `ta.ulcerIndex` — all `slot: true`). `TA_REGISTRY` grows by `+3`.
889
+ `TaNamespace` and `RuntimeTaNamespace` extend in lockstep with
890
+ `MedianOpts`, `AdrOpts` (`{ length?: number; offset?: number;
891
+ lineStyle?: PlotLineStyle }`), and `UlcerIndexOpts`.
892
+
893
+ `ALL_SCENARIOS` (conformance) grows by `+3`. The three new
894
+ scenarios assert `alert-count: 0` + the standard
895
+ `lookback-exceeded` / `malformed-emission` diagnostic-absent gates
896
+ (no `plot-hash` — the rolling primitives' outputs are pinned
897
+ elsewhere via the runtime golden tests).
898
+
899
+ - 38fb475: Phase-2 Task 16 — trend ports: `ta.adx`, `ta.dmi`, `ta.trix`.
900
+
901
+ Ships three new trend `ta.*` primitives under
902
+ `packages/runtime/src/ta/`:
903
+
904
+ - `ta.adx(length, opts?)` — Wilder's Average Directional Index
905
+ (single Series bounded `[0, 100]`). Reads `bar.high` /
906
+ `bar.low` / `bar.close` directly (mirrors Pine's `ta.adx(length)`
907
+ — no source param). Composes the same Wilder DI recurrence
908
+ `ta.dmi` runs, then folds DX through a second
909
+ Wilder-smoothing window of length `opts.smoothing ?? 14`.
910
+ - `ta.dmi(length, opts?)` — Wilder's Directional Movement Index
911
+ (`{ plusDi, minusDi }`, both ∈ [0, 100]). Reads OHLC directly
912
+ per Pine's `ta.dmi(length)`. Incremental `wilderStep` over
913
+ `+DM` / `−DM` / TR; output validated against the
914
+ full-recompute reference `lib/wilderDirectional`.
915
+ - `ta.trix(source, length, opts?)` — TRIX triple-smoothed EMA
916
+ rate-of-change with an EMA-signal line (`{ trix, signal }`).
917
+ Composes three EMA sub-slots (`${slotId}/ema1` / `/ema2` /
918
+ `/ema3`) for the triple chain + a fourth `${slotId}/signal`
919
+ EMA, mirroring the MACD sub-slot composition pattern.
920
+
921
+ Each primitive ships the §22.10 set: impl + unit + property +
922
+ golden + bench pair + conformance scenario (using the Phase-2
923
+ `inlineSource` extension from Task 1) + auto-generated
924
+ `docs/primitives/ta/<id>.md`. `TA_REGISTRY_METADATA` carries
925
+ y-domain + multi-output hints (`adx: { yDomain: fixed 0-100 }`,
926
+ `dmi: { primarySeriesKey: "plusDi", visibleSeriesKeys: ["plusDi",
927
+ "minusDi"], yDomain: fixed 0-100 }`, `trix: { primarySeriesKey:
928
+ "trix", visibleSeriesKeys: ["trix", "signal"], yDomain: auto }`).
929
+
930
+ ADX / DMI reuse Phase-1 `lib/wilderSmoothing` (`wilderStep`) for
931
+ the per-bar Wilder recurrence and Wave-3 Task-4
932
+ `lib/wilderDirectional` + `lib/adxFromDi` as the property-test
933
+ reference (Float64Array-in / Float64Array-out full-recompute).
934
+ TRIX reuses Phase-1 `lib/emaFloat64` (`computeEmaOfFloat64`) as
935
+ the property-test reference plus the runtime `ta.ema` primitive
936
+ for the four composed sub-slots.
937
+
938
+ Core adds `AdxOpts`, `DmiOpts`, `DmiResult`, `TrixOpts`,
939
+ `TrixResult` exports plus three new methods on `TaNamespace`.
940
+ `STATEFUL_PRIMITIVES` grows by 3 (`ta.adx`, `ta.dmi`, `ta.trix`;
941
+ all `slot: true`). `TA_REGISTRY` mirrors with the leading
942
+ `slotId: string` on each method.
943
+
944
+ - 38fb475: Phase-2 Task 15 — trend ports: `ta.aroon` and `ta.aroonOsc`.
945
+
946
+ Ships two new trend `ta.*` primitives under
947
+ `packages/runtime/src/ta/`:
948
+
949
+ - `ta.aroon(length, opts?)` — Aroon Up / Down (`{ up, down }`,
950
+ both ∈ [0, 100]). Reads `bar.high` / `bar.low` directly per
951
+ Pine's `ta.aroon(length)` signature (no source param). Scans the
952
+ trailing `length + 1` window per close for the argmax / argmin
953
+ using strict `>` / `<` so the most-recent tied bar wins
954
+ (TradingView convention). Tick replay substitutes the head value
955
+ without mutating the closed window.
956
+ - `ta.aroonOsc(length, opts?)` — `aroon.up − aroon.down`, bounded
957
+ in [-100, 100]. Composes `ta.aroon` at sub-slot
958
+ `${slotId}/aroon` so a fix to Aroon flows in for free.
959
+
960
+ Each primitive ships the §22.10 set: impl + unit + property + golden
961
+
962
+ - bench pair + conformance scenario (using the Phase-2 `inlineSource`
963
+ extension from Task 1) + auto-generated `docs/primitives/ta/<id>.md`.
964
+ `TA_REGISTRY_METADATA` carries the multi-output / y-domain hints
965
+ (`aroon: { primarySeriesKey: "up", visibleSeriesKeys: ["up", "down"],
966
+ yDomain: fixed 0-100 }`, `aroonOsc: { yDomain: fixed -100-100 }`).
967
+
968
+ Core adds `AroonOpts`, `AroonOscOpts`, `AroonResult` exports + the
969
+ two `TaNamespace` methods. `STATEFUL_PRIMITIVES` grows by 2
970
+ (`ta.aroon`, `ta.aroonOsc`; both `slot: true`). `TA_REGISTRY`
971
+ mirrors with the leading `slotId: string` on each method.
972
+
973
+ - 38fb475: Phase-2 Task 17 — trend ports: `ta.vortex`, `ta.trendStrengthIndex`,
974
+ `ta.ichimoku`.
975
+
976
+ Ships three new trend `ta.*` primitives under
977
+ `packages/runtime/src/ta/`:
978
+
979
+ - `ta.vortex(length, opts?)` — Botes & Siepman (2010) Vortex
980
+ Indicator. Reads `bar.high` / `bar.low` / `bar.close` directly
981
+ (mirrors Pine's `ta.vortex(length)` — no source param). Returns
982
+ `{ plus, minus }` (the +VI / −VI lines). Maintains rolling
983
+ running-sum windows over per-bar `vmPlus`, `vmMinus`, and TR for
984
+ O(1) per-bar updates. NaN-on-zero-TR semantic per chartlang task
985
+ spec §6 (invinite emits 0 on zero TR; chartlang surfaces the
986
+ degenerate window).
987
+ - `ta.trendStrengthIndex(source, length, opts?)` — TradingView's
988
+ Trend Strength Index: Pearson correlation between `source` and
989
+ bar index over each trailing `length`-bar window. Bounded
990
+ `[-1, +1]`. Default `length = 20` (chartlang task spec; invinite
991
+ default is 14). Distinct from `ta.tsi` (Task 14's True Strength
992
+ Index momentum oscillator) — name collision avoided via the
993
+ longer `trendStrengthIndex` surface.
994
+ - `ta.ichimoku(opts?)` — Ichimoku Cloud (Tenkan / Kijun / Senkou A
995
+ / Senkou B / Chikou). Defaults `(conversionLength=9, baseLength=
996
+ 26, leadingSpanBLength=52, displacement=26)`. Composes six
997
+ `ta.highest` / `ta.lowest` sub-slots (one pair each for Tenkan /
998
+ Kijun / Senkou B) — the same composition seam `ta.donchian` uses
999
+ — so a fix to either rolling-extreme primitive flows in for free.
1000
+ Forward-displaced Senkou A / Senkou B and backward-displaced
1001
+ Chikou are produced via per-slot delay ring buffers of capacity
1002
+ `displacement + 1`. `chikou.current` returns `close[t −
1003
+ displacement]` (the backward-shifted close — programmatic
1004
+ semantic for script-author conditionals).
1005
+
1006
+ Each primitive ships the §22.10 set: impl + unit + property +
1007
+ golden + bench pair + conformance scenario (using the Phase-2
1008
+ `inlineSource` extension from Task 1) + auto-generated
1009
+ `docs/primitives/ta/<id>.md`. `TA_REGISTRY_METADATA` carries
1010
+ y-domain + multi-output hints:
1011
+
1012
+ - `vortex: { primarySeriesKey: "plus", visibleSeriesKeys:
1013
+ ["plus", "minus"], yDomain: auto }`
1014
+ - `trendStrengthIndex: { yDomain: fixed [-1, 1] }`
1015
+ - `ichimoku: { primarySeriesKey: "tenkan", visibleSeriesKeys:
1016
+ ["tenkan", "kijun", "senkouA", "senkouB", "chikou"], yDomain:
1017
+ auto }` (the cloud renders via the Task-1 `filled-band` PlotKind
1018
+ between `senkouA` and `senkouB` — script-author drives the
1019
+ styling in their `plot()` call).
1020
+
1021
+ Reuse:
1022
+
1023
+ - Vortex's property test uses Phase-1 `lib/trSeries.ts`
1024
+ (`computeTrSeries`) as the per-bar TR reference; golden test
1025
+ pins the per-output hashes of a 100-bar Mulberry32 fixture.
1026
+ - TrendStrengthIndex's property test uses Wave-3 `lib/pearson.ts`
1027
+ against a linear bar-index series as the reference.
1028
+ - Ichimoku's property test uses Wave-3 `lib/donchianMid.ts` as the
1029
+ per-line reference (Tenkan / Kijun / SenkouB raw all share the
1030
+ same Donchian-midpoint math).
1031
+
1032
+ Core adds `VortexOpts`, `VortexResult`, `TrendStrengthIndexOpts`,
1033
+ `IchimokuOpts`, `IchimokuResult` exports plus three new methods
1034
+ on `TaNamespace` + three throwing stubs on the `ta` const.
1035
+ `STATEFUL_PRIMITIVES` grows by 3 (`ta.vortex`,
1036
+ `ta.trendStrengthIndex`, `ta.ichimoku`; all `slot: true`) — final
1037
+ Phase-2 size 93. `TA_REGISTRY` grows by 3 — final size 90.
1038
+ Conformance scenarios + `ALL_SCENARIOS` array grow by 3.
1039
+
1040
+ - 38fb475: Phase-2 Task 18 — volatility ports: `ta.bbPercentB`, `ta.bbw`, and
1041
+ `ta.donchian`.
1042
+
1043
+ Ships three new volatility `ta.*` primitives under
1044
+ `packages/runtime/src/ta/`:
1045
+
1046
+ - `ta.bbPercentB(source, length, opts?)` — Bollinger %B,
1047
+ `(src − lower) / (upper − lower)` over the BB envelope. NaN
1048
+ when the band collapses (zero width). Composes `ta.bb` via
1049
+ sub-slot `${slotId}/bb` so a fix to the envelope flows in for
1050
+ free. Default `multiplier = 2`.
1051
+ - `ta.bbw(source, length, opts?)` — Bollinger BandWidth,
1052
+ `(upper − lower) / middle` over the BB envelope. Raw ratio
1053
+ scale (multiply by 100 in the script for TradingView-parity
1054
+ display). NaN on zero middle. Composes `ta.bb` via the same
1055
+ sub-slot pattern. Default `multiplier = 2`.
1056
+ - `ta.donchian(length, opts?)` — Donchian Channels,
1057
+ `{ upper, middle, lower }` over a fixed `length`-bar window.
1058
+ `upper = highest(bar.high, length)` and `lower =
1059
+ lowest(bar.low, length)` via sub-slots `${slotId}/highest` /
1060
+ `${slotId}/lowest` — the slot-aware composition of the
1061
+ registered Task-5 primitives; equivalent to `lib/donchianMid`
1062
+ but routed through the registry so a fix flows in for free.
1063
+ Mid = `(upper + lower) / 2`.
1064
+
1065
+ Each primitive ships the §22.10 set: impl + unit + property +
1066
+ golden + bench pair + conformance scenario (using the Phase-2
1067
+ `inlineSource` extension from Task 1) + auto-generated
1068
+ `docs/primitives/ta/<id>.md`. `TA_REGISTRY_METADATA.donchian`
1069
+ records the multi-output hints (`primarySeriesKey: "middle"`,
1070
+ `visibleSeriesKeys: ["upper", "middle", "lower"]`,
1071
+ `yDomain: { kind: "auto" }`).
1072
+
1073
+ Core adds `BbPercentBOpts`, `BbwOpts`, `DonchianOpts`,
1074
+ `DonchianResult` exports + the three `TaNamespace` methods.
1075
+ `STATEFUL_PRIMITIVES` grows by 3 (all three `slot: true`).
1076
+ `TA_REGISTRY` mirrors with the leading `slotId: string` on each
1077
+ method.
1078
+
1079
+ - 38fb475: Phase-2 Task 20 — volatility ports: `ta.historicalVolatility`,
1080
+ `ta.rvi`, and `ta.massIndex`.
1081
+
1082
+ Ships three new volatility `ta.*` primitives under
1083
+ `packages/runtime/src/ta/`:
1084
+
1085
+ - `ta.historicalVolatility(source, length, opts?)` — annualised
1086
+ stddev of log returns ×100. Default `annualisationFactor = 365`
1087
+ (TradingView's "Crypto" convention; use `252` for trading-day
1088
+ equity series). NaN through `[0, length − 1]` warmup; non-positive
1089
+ or non-finite source short-circuits log returns to NaN.
1090
+ - `ta.rvi(source, length, opts?)` — Relative Volatility Index, the
1091
+ RSI-style oscillator that uses rolling stddev of the source as
1092
+ the magnitude instead of absolute close changes. Bounded `[0, 100]`.
1093
+ Composes `ta.ema` via sub-slots `${slotId}/upEma` and
1094
+ `${slotId}/downEma` so a fix to EMA's recurrence flows in for
1095
+ free. Warmup `2 · length − 1`. NaN on zero-denominator (both EMA
1096
+ arms zero).
1097
+ - `ta.massIndex(opts?)` — sub-pane volatility line that tracks the
1098
+ range-EMA "bulge" ratio to flag trend-reversal setups via the
1099
+ canonical 27 threshold. Reads `bar.high − bar.low` directly (no
1100
+ source param). Composes two chained `ta.ema` sub-slots
1101
+ (`${slotId}/ema1`, `${slotId}/ema2`). Defaults `emaLength = 9`,
1102
+ `sumLength = 25`. Warmup `2 · emaLength + sumLength − 3`.
1103
+
1104
+ Adds the §22.10 five-file set per primitive (impl + unit + property
1105
+
1106
+ - golden + bench pair) and a conformance scenario per primitive
1107
+ under `packages/conformance/src/scenarios/`.
1108
+
1109
+ Extends core's `TaNamespace` + `STATEFUL_PRIMITIVES` and the
1110
+ runtime's `RuntimeTaNamespace` + `TA_REGISTRY` accordingly. Three
1111
+ auto-generated docs pages under `docs/primitives/ta/` ship via the
1112
+ Task-2 `chartlang docs` generator.
1113
+
1114
+ Provenance: ported from invinite at commit
1115
+ `078f41fe2569d659d5aba726da8bcb5d3e2ced02`. The RVI math follows
1116
+ invinite's TradingView-reference shape (EMA-smoothed up/down stddev
1117
+ arms), not the spec's draft Wilder-smoothing description.
1118
+
1119
+ - 38fb475: Phase-2 Task 19 — volatility ports: `ta.keltner`, `ta.envelope`, and
1120
+ `ta.chop`.
1121
+
1122
+ Ships three new volatility `ta.*` primitives under
1123
+ `packages/runtime/src/ta/`:
1124
+
1125
+ - `ta.keltner(opts?)` — Keltner Channels overlay envelope.
1126
+ `middle = MA(close, length, maType)` with `upper / lower =
1127
+ middle ± multiplier · ATR(length)`. Defaults `length = 20`,
1128
+ `multiplier = 2`, `maType = "ema"` (TradingView / Linda Raschke
1129
+ canonical form). Composes `ta.atr` via sub-slot `${slotId}/atr`
1130
+ and the registered MA primitive (`sma` / `ema` / `wma` / `smma`)
1131
+ via sub-slot `${slotId}/<maType>` — fixes to either flow in for
1132
+ free.
1133
+ - `ta.envelope(source, opts?)` — price-percent envelope overlay.
1134
+ `middle = MA(source, length, maType)` with `upper / lower =
1135
+ middle · (1 ± percent / 100)`. Defaults `length = 20`,
1136
+ `percent = 10`, `maType = "sma"`. Composes the registered MA
1137
+ primitive via sub-slot `${slotId}/<maType>` so fixes flow in
1138
+ for free.
1139
+ - `ta.chop(length, opts?)` — Choppiness Index sub-pane regime
1140
+ gauge. `chop = 100 · log10(sumTR(length) / (highest(high,
1141
+ length) − lowest(low, length))) / log10(length)`, clamped to
1142
+ `[0, 100]`. High values flag sideways / choppy markets; low
1143
+ values flag strong trends. Composes `ta.highest` / `ta.lowest`
1144
+ via sub-slots; the TR-sum numerator is a sliding-window sum
1145
+ inside the slot (same internal TR math as `ta.atr`, but raw —
1146
+ Pine `ta.chop` does NOT use the Wilder-smoothed ATR).
1147
+
1148
+ Each primitive ships the §22.10 set: impl + unit + property +
1149
+ golden + bench pair + conformance scenario (using the Phase-2
1150
+ `inlineSource` extension from Task 1) + auto-generated
1151
+ `docs/primitives/ta/<id>.md`. `TA_REGISTRY_METADATA.keltner` and
1152
+ `.envelope` record the multi-output hints
1153
+ (`primarySeriesKey: "middle"`,
1154
+ `visibleSeriesKeys: ["upper", "middle", "lower"]`,
1155
+ `yDomain: { kind: "auto" }`); `TA_REGISTRY_METADATA.chop` pins
1156
+ the bounded `{ yDomain: { kind: "fixed", min: 0, max: 100 } }`
1157
+ oscillator range.
1158
+
1159
+ Core adds `KeltnerOpts`, `KeltnerResult`, `EnvelopeOpts`,
1160
+ `EnvelopeResult`, `ChopOpts` exports + the three `TaNamespace`
1161
+ methods. `STATEFUL_PRIMITIVES` grows by 3 (all three `slot: true`).
1162
+ `TA_REGISTRY` mirrors with the leading `slotId: string` on each
1163
+ method.
1164
+
1165
+ - 38fb475: Phase-2 Task 23 — volume ports `ta.chaikinOsc`, `ta.mfi`,
1166
+ `ta.netVolume`, `ta.pvo`.
1167
+
1168
+ Ports four volume primitives from invinite (commit
1169
+ `078f41fe2569d659d5aba726da8bcb5d3e2ced02`) onto the chartlang
1170
+ runtime — each lands the §22.10 five-file set (impl + unit +
1171
+ property + golden + bench pair) alongside an inline conformance
1172
+ scenario and an auto-generated docs page:
1173
+
1174
+ - `ta.chaikinOsc(opts?)` — Chaikin Oscillator, `EMA(ADL, fast) −
1175
+ EMA(ADL, slow)`. Defaults `(3, 10)`. Composes one `ta.adl`
1176
+ sub-slot + two `ta.ema` sub-slots; a fix to either flows in for
1177
+ free. Warmup `slowLength − 1`.
1178
+ - `ta.mfi(length, opts?)` — Money Flow Index, volume-weighted RSI
1179
+ over a trailing window of typical-price comparisons. Bounded
1180
+ `[0, 100]`; emits 100 on perfect upflow, 0 on perfect downflow,
1181
+ NaN on zero total flow (invinite's zero-denominator guard).
1182
+ Warmup `length + 1`.
1183
+ - `ta.netVolume(opts?)` — cumulative `sign(close − prevClose) ·
1184
+ volume`. **Math is identical to `ta.obv`** (both primitives
1185
+ exist in invinite under their own names; chartlang mirrors the
1186
+ public surface for naming parity). Property-tested for
1187
+ hash-equality against `ta.obv` over a 100-bar synthetic walk.
1188
+ Warmup 1 (bar 0 emits 0).
1189
+ - `ta.pvo(opts?)` — Percentage Volume Oscillator, MACD shape on
1190
+ `bar.volume`. Defaults `(12, 26, 9)`. Composes three `ta.ema`
1191
+ sub-slots over volume. Multi-output `{ pvo, signal, hist }`;
1192
+ `TA_REGISTRY_METADATA.pvo` records `primarySeriesKey: "pvo"`,
1193
+ `visibleSeriesKeys: ["pvo", "signal", "hist"]`, `yDomain: {
1194
+ kind: "auto" }`. Warmup `slowLength + signalLength − 2`.
1195
+
1196
+ Surface deltas:
1197
+
1198
+ - `TaNamespace` extends with the four new methods + opts types
1199
+ (`ChaikinOscOpts`, `MfiOpts`, `NetVolumeOpts`, `PvoOpts` +
1200
+ `PvoResult`).
1201
+ - `STATEFUL_PRIMITIVES` grows by four `slot: true` entries.
1202
+ - `TA_REGISTRY` + `RuntimeTaNamespace` mirror the same delta;
1203
+ `TA_REGISTRY_METADATA.pvo` carries the multi-series metadata;
1204
+ `ALL_SCENARIOS` grows by four inline scenarios.
1205
+
1206
+ All four primitives carry the §16.6 100% coverage gate via their
1207
+ five-file test set; golden hashes pinned against `syntheticBars(100,
1208
+ 42)` (placeholder pin in the initial commit — repinned on first
1209
+ deterministic green). Per-port bench thresholds reuse the
1210
+ `THRESHOLD_MS = 300` ceiling from the existing volume primitives.
1211
+
1212
+ - 38fb475: Phase-2 Task 22 — volume ports `ta.obv`, `ta.adl`, `ta.bop`, `ta.cmf`.
1213
+
1214
+ Ports four volume primitives from invinite (commit
1215
+ `078f41fe2569d659d5aba726da8bcb5d3e2ced02`) onto the chartlang
1216
+ runtime — each lands the §22.10 five-file set (impl + unit +
1217
+ property + golden + bench pair) alongside an inline conformance
1218
+ scenario and an auto-generated docs page:
1219
+
1220
+ - `ta.obv()` — On-Balance Volume, cumulative `sign(close − prevClose) ·
1221
+ volume`. Warmup 1 (bar 0 emits 0). Slot snapshots
1222
+ `prevClosedCumObv` / `prevClosedPrevClose` for tick-mode replay.
1223
+ NaN volume carries the accumulator forward without an update.
1224
+ - `ta.adl()` — Accumulation / Distribution Line, cumulative
1225
+ `((C − L) − (H − C)) / (H − L) · volume`. Warmup 0. Zero-range
1226
+ bars (`high === low`) contribute 0 (matches invinite's CLV
1227
+ guard); NaN OHLC / volume contributes 0.
1228
+ - `ta.bop()` — Balance of Power, raw per-bar `(C − O) / (H − L)`.
1229
+ Warmup 0; stateless math, output buffer only.
1230
+ - `ta.cmf(length)` — Chaikin Money Flow, trailing-window
1231
+ `Σ MFV / Σ volume`. Warmup `length − 1`; bounded `[-1, 1]`.
1232
+ Tick-mode substitutes the head slot's contribution without
1233
+ mutating the rolling window (matches `ulcerIndex`'s shape).
1234
+
1235
+ Surface deltas:
1236
+
1237
+ - `TaNamespace` extends with the four new methods + opts types
1238
+ (`ObvOpts`, `AdlOpts`, `BopOpts`, `CmfOpts` — each `{ offset?;
1239
+ lineStyle? }`).
1240
+ - `STATEFUL_PRIMITIVES` grows by four `slot: true` entries.
1241
+ - `TA_REGISTRY` + `RuntimeTaNamespace` mirror the same delta;
1242
+ `ALL_SCENARIOS` grows by four inline scenarios.
1243
+
1244
+ All four primitives carry the §16.6 100% coverage gate via their
1245
+ five-file test set; golden hashes pinned against `syntheticBars(100,
1246
+ 42)`. Per-port bench thresholds reuse the `THRESHOLD_MS = 300`
1247
+ ceiling from the existing volume primitives.
1248
+
1249
+ - 38fb475: Phase-2 Task 24 — volume ports `ta.pvt`, `ta.eom`, `ta.nvi`,
1250
+ `ta.pvi`. Closes the §9.2 volume list (excluding the 4 volume-
1251
+ profile primitives deferred to Phase 5).
1252
+
1253
+ Ports four volume primitives from invinite (commit
1254
+ `078f41fe2569d659d5aba726da8bcb5d3e2ced02`) onto the chartlang
1255
+ runtime — each lands the §22.10 five-file set (impl + unit +
1256
+ property + golden + bench pair) alongside an inline conformance
1257
+ scenario and an auto-generated docs page:
1258
+
1259
+ - `ta.pvt(opts?)` — Price Volume Trend, cumulative `volume ·
1260
+ (close − prevClose) / prevClose`. First bar emits 0;
1261
+ zero-prevClose bars emit NaN AND carry the accumulator forward;
1262
+ NaN volume contributes 0. Warmup 1.
1263
+ - `ta.eom(length, opts?)` — Ease of Movement, `length`-bar SMA of
1264
+ per-bar `((midpointMove) / boxRatio)` where `boxRatio = (volume
1265
+ / 10000) / (high − low)`. Hard-codes invinite's default divisor
1266
+ of 10000. Zero-range / zero-volume / NaN-input bars propagate
1267
+ NaN through the trailing window (forces a clean restart after
1268
+ any defective bar). Warmup `length`.
1269
+ - `ta.nvi(opts?)` — Negative Volume Index, cumulative
1270
+ close-pct-change on bars whose volume is strictly LOWER than the
1271
+ prior bar's; bars with equal-or-higher volume carry the prior
1272
+ value unchanged. Seeded at 1000 (anchor pinned by property
1273
+ test). Warmup 1.
1274
+ - `ta.pvi(opts?)` — Positive Volume Index, mirror of NVI on bars
1275
+ whose volume is strictly HIGHER than the prior bar's. Seeded at 1000. Warmup 1.
1276
+
1277
+ Surface deltas:
1278
+
1279
+ - `TaNamespace` extends with the four new methods + opts types
1280
+ (`PvtOpts`, `EomOpts`, `NviOpts`, `PviOpts`). All four opts
1281
+ bags share the `{ offset?: number; lineStyle?: PlotLineStyle }`
1282
+ shape.
1283
+ - `STATEFUL_PRIMITIVES` grows by four `slot: true` entries
1284
+ (86 → 90; `slot: true` count 85 → 89).
1285
+ - `TA_REGISTRY` + `RuntimeTaNamespace` mirror the same delta
1286
+ (83 → 87). No new `TA_REGISTRY_METADATA` rows — all four are
1287
+ single-output `Series<number>` with auto y-domain.
1288
+ - `ALL_SCENARIOS` grows by four inline scenarios.
1289
+
1290
+ All four primitives carry the §16.6 100% coverage gate via their
1291
+ five-file test set; golden hashes pinned against
1292
+ `syntheticBars(100, 42)` (placeholder pin in the initial commit —
1293
+ repinned on first deterministic green). Per-port bench thresholds
1294
+ reuse the `THRESHOLD_MS = 300` ceiling from the existing volume
1295
+ primitives.
1296
+
1297
+ - 38fb475: Phase-2 Task 21 — port the three foundational volume primitives:
1298
+
1299
+ - **`ta.vol(opts?)`** — passthrough of `bar.volume` as a `Series<number>`.
1300
+ Warmup 0; NaN volume propagates to NaN output.
1301
+ - **`ta.vwap(opts?)`** — session-anchored VWAP keyed on the UTC
1302
+ calendar-day boundary (`floor(bar.time / 86_400_000)`). Phase 4
1303
+ lifts the session detection to `syminfo.session.regularStart` per
1304
+ invinite; until then `ta.vwap` is a UTC-day-anchored VWAP.
1305
+ Source defaults to `"hlc3"` per Pine; accepts `"close"` / `"hl2"` /
1306
+ `"ohlc4"` / `"hlcc4"`.
1307
+ - **`ta.anchoredVwap(anchorTime, opts?)`** — anchored VWAP that
1308
+ starts accumulating at the first bar with `bar.time >= anchorTime`
1309
+ and never resets. The anchor is sticky (captured on the first
1310
+ call; later anchor args are ignored). Phase 4's `input.time()`
1311
+ lifts the anchor to a runtime user input.
1312
+
1313
+ All three carry the §22.10 five-file set + JSDoc with
1314
+ `@formula`/`@warmup`/`@since 0.2`/`@experimental`/`@example`; all
1315
+ register in `STATEFUL_PRIMITIVES` as `slot: true` and in
1316
+ `TA_REGISTRY` / `RuntimeTaNamespace`.
1317
+
1318
+ ### `PlotOpts.style?` widening
1319
+
1320
+ To exercise the Task-1 `histogram` PlotKind end-to-end on
1321
+ `ta.vol`, this PR widens the script-facing `PlotOpts` with an
1322
+ optional `style?: PlotOptsStyle` discriminated-union field
1323
+ (`{ kind: "line" }` | `{ kind: "step-line" }` |
1324
+ `{ kind: "histogram"; baseline?: number }`). The runtime's
1325
+ `plot()` impl honours the field; the canvas2d reference adapter
1326
+ dispatches `kind: "histogram"` through Task-1's `drawHistogram`
1327
+ renderer. Backward-compatible — omitting `opts.style` keeps the
1328
+ existing `kind: "line"` default.
1329
+
1330
+ Future ports adding their own PlotKind (e.g. MACD-hist in Task 16,
1331
+ `bars` / `area` / `filled-band` / `label` / `marker` in their
1332
+ consumer ports) extend this same `PlotOptsStyle` union additively
1333
+ and add their dispatch arm to `createCanvas2dAdapter.applyPlot`.
1334
+
1335
+ ### Conformance scenarios
1336
+
1337
+ - `taVol.scenario.ts` — `plot(ta.vol(), { style: { kind: "histogram", baseline: 0 } })`.
1338
+ - `taVwap.scenario.ts` — `plot(ta.vwap({ source: "hlc3" }))`.
1339
+ - `taAnchoredVwap.scenario.ts` — `plot(ta.anchoredVwap(1_700_000_000_000))`.
1340
+
1341
+ ### Provenance
1342
+
1343
+ All three ports trace to `invinite/src/components/trading-chart/
1344
+ indicators/{vol,vwap,anchored-vwap}.ts` at commit
1345
+ `078f41fe2569d659d5aba726da8bcb5d3e2ced02`.
1346
+
1347
+ - b0d296b: Phase 3 closeout — `0.3` "Full Drawing Parity".
1348
+
1349
+ 61 drawing kinds across 13 categories ship under `draw.*` with the
1350
+ full §22.10 set per kind (impl + property + golden + bench + JSDoc
1351
+
1352
+ - conformance scenario + auto-generated docs page). 5-bucket
1353
+ `DrawingCounts` budget, per-kind capability gating, `DrawingHandle`
1354
+ across-bar stability, real-impl `validateEmission` + `decodeDrawing`,
1355
+ `drawing-hash` conformance assertion variant, 13 category + 1
1356
+ umbrella capability builders, canvas2d reference adapter renders
1357
+ every kind, `defineDrawing` constructor for interactive tools.
1358
+
1359
+ Final cardinalities: `STATEFUL_PRIMITIVES.size === 154` (93 Phase-2
1360
+
1361
+ - 61 Phase-3 `draw.*` entries); `DRAWING_KINDS.length === 61`.
1362
+
1363
+ Per-bucket kind tally pinned by `bucketFor` (6 + 5 + 6 + 25 + 19 = 61):
1364
+
1365
+ - `lines` (6): `line`, `horizontal-line`, `horizontal-ray`,
1366
+ `vertical-line`, `cross-line`, `trend-angle`.
1367
+ - `boxes` (5): `rectangle`, `rotated-rectangle`, `triangle`,
1368
+ `circle`, `ellipse`.
1369
+ - `labels` (6): `marker`, `text`, `arrow`, `arrow-marker`,
1370
+ `arrow-mark-up`, `arrow-mark-down`.
1371
+ - `polylines` (25): `polyline`, `path`, `arc`, `curve`,
1372
+ `double-curve`, `pen`, `highlighter`, `brush`,
1373
+ `trend-channel`, `flat-top-bottom`, `disjoint-channel`,
1374
+ `regression-trend`, `pitchfork`, `pitchfan`, `xabcd-pattern`,
1375
+ `cypher-pattern`, `head-and-shoulders`, `abcd-pattern`,
1376
+ `triangle-pattern`, `three-drives-pattern`,
1377
+ `elliott-impulse-wave`, `elliott-correction-wave`,
1378
+ `elliott-triangle-wave`, `elliott-double-combo`,
1379
+ `elliott-triple-combo`.
1380
+ - `other` (19): 10 `fib-*` + 4 `gann-*` + 3 cycles
1381
+ (`cyclic-lines`, `time-cycles`, `sine-line`) + 2 containers
1382
+ (`group`, `frame`).
1383
+
1384
+ Conformance scenarios: 61 per-kind + 12 task bundles +
1385
+ `drawAll61` + `drawBudgetOverflow` + `drawUnsupportedKind` = **76**.
1386
+ Docs: 61 auto-generated `docs/primitives/draw/<kind>.md` pages +
1387
+ 1 hand-written `index.md`.
1388
+
1389
+ Variant collapses pinned in Task 1 (carried forward unchanged):
1390
+
1391
+ - `pitchfork.variant: "standard" | "schiff" | "modified-schiff" | "inside"`
1392
+ collapses the 4 invinite pitchfork tools.
1393
+ - `line.{extendLeft, extendRight}` collapses the `ray` /
1394
+ `extended-line` tools.
1395
+ - `cypherPattern` ships as a `defineDrawing`-only kind (no
1396
+ standalone interactive tool).
1397
+
1398
+ Compiler: `callsiteIdInjection` recognises every `draw.*` callable
1399
+ via the widened 154-entry `STATEFUL_PRIMITIVES`;
1400
+ `statefulCallInLoop` flags `draw.*` in unbounded loops with the
1401
+ existing `stateful-call-inside-loop` error.
1402
+
1403
+ Bench thresholds (re-verified post-Phase-3 on Apple-silicon):
1404
+
1405
+ - `pushDrawing.bench.test.ts` — 10 000 line drawings under 2 000ms
1406
+ wall-clock (`ceil(median × 3)` per §22.10; no drift across
1407
+ Tasks 4–18 — the budget/validate path is independent of
1408
+ per-kind canvas renderers). `pnpm bench:ci` median ~180ms.
1409
+ - The Phase-2 ta / ringBuffer / seriesView / onBarClose /
1410
+ plot / hline bench thresholds were bumped from the
1411
+ `200/250/300/400/500/600ms` solo-run pins to a uniform `1500ms`
1412
+ (3000ms for plot + hline) to absorb the parallel-worker
1413
+ scheduling overhead during workspace `pnpm test` (665 test
1414
+ files in parallel). Solo `pnpm bench:ci` medians remain in the
1415
+ 10–200ms range — well under both old and new thresholds — so
1416
+ this is a noise-floor adjustment, not a perf-regression
1417
+ accommodation.
1418
+
1419
+ `apiVersion: 1` script header unchanged; Phase 3 is additive at
1420
+ runtime.
1421
+
1422
+ - b0d296b: Phase-3 Task 1 — `draw.*` type surface foundation.
1423
+
1424
+ Adds the canonical Phase-3 type surface to `@invinite-org/chartlang-core`:
1425
+
1426
+ - `DrawingKind` — 61-entry kebab-case discriminated union (lines /
1427
+ boxes / curves / freehand / annotations / channels / fib / gann /
1428
+ pitchforks / patterns / elliott / cycles / containers). The
1429
+ kebab-case wire format is the source-of-truth; the camelCase
1430
+ TypeScript surface (`draw.horizontalLine`, `draw.fibRetracement`,
1431
+ …) is pinned via the `KIND_CAMELCASE` / `KIND_KEBABCASE` bijection.
1432
+ - `DRAWING_KINDS` — iterable form of `DrawingKind` in canonical
1433
+ declaration order.
1434
+ - `WorldPoint` + `AnchorPair` / `AnchorTriple` / `AnchorQuad` /
1435
+ `AnchorQuint` / `AnchorHept` helpers.
1436
+ - `DrawingState` — discriminated union with one variant per kind.
1437
+ Geometry + style fields only; collab-only fields (Yjs ids,
1438
+ layerIds, intervals, parentGroupId/FrameId, createdAt, authorId)
1439
+ from the invinite source are stripped per PLAN.md §10.4. Variants
1440
+ are minimal shells in this task; Tasks 5–18 refine per-category
1441
+ payloads.
1442
+ - Per-kind style bag types: `LineDrawStyle`, `ShapeStyle`,
1443
+ `HighlighterStyle`, `BrushStyle`, `TextOpts`, `ArrowOpts`,
1444
+ `ArrowMarkerOpts`, `PathOpts`, `FibOpts`, `RegressionTrendOpts`,
1445
+ `FrameOpts`.
1446
+ - `DrawingHandle` — script-facing handle returned by every
1447
+ `draw.<kind>(...)` call. Impl lives in the runtime (Task 3).
1448
+ - `DrawNamespace` + `FibSubNamespace` / `GannSubNamespace` /
1449
+ `ElliottSubNamespace` / `PatternSubNamespace` — the type the
1450
+ runtime swaps the throwing-stub `draw` Proxy for at boot. The
1451
+ stub mirrors the `plot` / `hline` / `alert` pattern from
1452
+ `plot/plot.ts`.
1453
+ - `DrawingBucket` + `KIND_BUCKET` + `bucketFor(kind)` — canonical
1454
+ kind → bucket map (`lines` / `labels` / `boxes` / `polylines` /
1455
+ `other`). Consumed by the runtime budget enforcer (Task 3) and
1456
+ by adapters that pre-budget.
1457
+ - `DrawingCounts` — moved here from `@invinite-org/chartlang-adapter-kit`
1458
+ so `ScriptManifest.maxDrawings?: DrawingCounts` and
1459
+ `Capabilities.maxDrawingsPerScript` pin the same shape without
1460
+ introducing a `core → adapter-kit` dependency cycle. The
1461
+ `adapter-kit` `DrawingCounts` export is now a type re-export of
1462
+ the core declaration — no public-surface drift, no consumer-visible
1463
+ change.
1464
+ - `ScriptManifest.maxDrawings?: DrawingCounts` + matching
1465
+ `DefineIndicatorOpts.maxDrawings?: DrawingCounts` propagation.
1466
+
1467
+ Extends `STATEFUL_PRIMITIVES` by 61 `draw.<camelKind>` entries (all
1468
+ `slot: true`). Cardinality grows from **93 → 154**. The new entries
1469
+ follow the canonical `DRAWING_KINDS` order. The compiler's
1470
+ `callsiteIdInjection` + `statefulCallInLoop` passes pick them up by
1471
+ name automatically.
1472
+
1473
+ No runtime behavior change in this task — `draw` is a throwing-stub
1474
+ Proxy until Task 3 wires the runtime emit infra. Phase-3 downstream
1475
+ tasks (2–22) all import from this surface.
1476
+
1477
+ - b0d296b: Phase 3 Task 11 — Fibonacci A (`fibRetracement` / `fibTrendExtension`
1478
+ / `fibChannel` / `fibTimeZone` / `fibWedge`).
1479
+
1480
+ - **core** — `DrawNamespace` flattened: the four sub-namespace types
1481
+ (`FibSubNamespace`, `GannSubNamespace`, `ElliottSubNamespace`,
1482
+ `PatternSubNamespace`) are removed; every kind now lives as a flat
1483
+ method directly on `DrawNamespace` matching the canonical
1484
+ `STATEFUL_PRIMITIVES` names (`draw.fibRetracement(...)`,
1485
+ `draw.gannBox(...)`, `draw.elliottImpulseWave(...)`,
1486
+ `draw.xabcdPattern(...)`, etc.). The throwing-stub `draw` Proxy
1487
+ drops the sub-namespace branch. Script authors use the flat
1488
+ Pine/invinite-parity surface; the compiler resolves callsites
1489
+ through its existing 2-segment property-access path. The 30
1490
+ not-yet-ported method signatures (Tasks 12–18 fib-B / gann /
1491
+ pitchfork / pattern / elliott / cycle / container kinds) are
1492
+ declared as flat stubs so Tasks 12–18 only need to extend the
1493
+ runtime `KIND_IMPLS` map. **BREAKING** for any consumer that
1494
+ referenced `draw.fib.retracement(...)` or one of the four
1495
+ sub-namespace types — none currently exist outside Phase-3 work.
1496
+ - **adapter-kit** — 5 new per-kind validators
1497
+ (`validateFibRetracementState`, `validateFibTrendExtensionState`,
1498
+ `validateFibChannelState`, `validateFibTimeZoneState`,
1499
+ `validateFibWedgeState`) + 1 file-local style helper
1500
+ (`validateFibOpts`) covering FibOpts (`levels` finite-array,
1501
+ `showLabels` / `color` / `extendLeft` / `extendRight`).
1502
+ - **runtime** — 5 new emit functions under
1503
+ `packages/runtime/src/emit/draw/fibA/` wired into `DRAW_NAMESPACE`
1504
+ as flat methods. `fibRetracement` / `fibTimeZone` use the 4-arg
1505
+ form `(slotId, a, b, opts?)`; the other 3 use the 3-arg
1506
+ `(slotId, anchors, opts?)` form. No new sub-namespace wiring.
1507
+ - **canvas2d-adapter** — 5 new renderers reusing Task-4's
1508
+ `FIB_LEVELS` + `formatLevel` and Task-5's `extendLineSegment` for
1509
+ the `fib-retracement` viewport extension. Default colour
1510
+ `"#facc15"` (warm yellow) per invinite's fib-tool palette.
1511
+ - **conformance** — 6 new scenarios (5 per-kind + 1
1512
+ `drawFibA` bundle) with pinned `drawing-hash` assertions.
1513
+ Conformance + scenarios test-capability fixtures grow `other`
1514
+ bucket from 0 to 100 and add the 5 fib-A kebab kinds.
1515
+
1516
+ Divergences flagged in `tasks/phase-3-drawing-parity/11-fibonacci-a.plan.md`:
1517
+
1518
+ - `fib-time-zone` uses the canonical ratio array (`FIB_LEVELS`),
1519
+ NOT the integer Fibonacci sequence; `fibSequence.ts` helper is
1520
+ NOT created (Task-1 reshape follow-up).
1521
+ - `fib-wedge` rays are drawn with a fixed length
1522
+ `max(pxWidth, pxHeight) * 2` rather than via a directional
1523
+ `extendLineSegment` variant.
1524
+ - Per-kind property / golden test files deferred to the pragmatic
1525
+ 1-file-per-emit + 1-file-per-renderer set, mirroring Tasks 5–10.
1526
+
1527
+ See `tasks/phase-3-drawing-parity/11-fibonacci-a.plan.md` for the
1528
+ full audit + divergence list.
1529
+
1530
+ - b0d296b: Phase 3 Task 20 — `defineDrawing` constructor + interactive-tool
1531
+ conformance scenarios.
1532
+
1533
+ - **core** — new `defineDrawing(opts)` constructor + `DefineDrawingOpts`
1534
+ type. Mirrors `defineIndicator` structurally; the only differences are
1535
+ `manifest.kind === "drawing"` and `manifest.capabilities ===
1536
+ ["drawings"]`. The runtime treats indicator and drawing scripts
1537
+ identically at the per-bar level — the discriminator is a host-side
1538
+ hint the editor uses to distinguish drawing scripts in the
1539
+ script-picker UI (PLAN.md §4.1). The constructor accepts the same
1540
+ Phase-3 `maxDrawings?: DrawingCounts` per-bucket cap propagation as
1541
+ `defineIndicator`.
1542
+ - **compiler** — `analysis/structuralChecks.ts` widens its recognised
1543
+ constructor set to include `defineDrawing` and maps it to
1544
+ `manifest.kind === "drawing"`. `StructuralCheckResult.kind` widens
1545
+ to `"indicator" | "drawing" | "alert"` (matches `buildManifest`'s
1546
+ existing type). The in-memory ambient `.d.ts` shim in `program.ts`
1547
+ declares `DefineDrawingOpts` + `defineDrawing` so a `defineDrawing`
1548
+ script type-checks under the host-machine-independent program.
1549
+ `extractCapabilities` now takes a `kind` parameter and seeds with
1550
+ `"drawings"` (or `"alerts"`) when the script is a `defineDrawing`
1551
+ (or `defineAlert`) — previously every script unconditionally
1552
+ declared `"indicators"`. Error messages on
1553
+ `missing-default-export` / `api-version-mismatch` now mention all
1554
+ three constructor names.
1555
+ - **conformance** — three new bundled scenarios, all default-exporting
1556
+ through `defineDrawing`:
1557
+
1558
+ - `DEFINE_DRAWING_BASIC_SCENARIO` — single `draw.fibRetracement(...)`
1559
+ emission on bar 0 through the new constructor. Verifies the
1560
+ constructor + compiler structural-check + capability extraction
1561
+ - runtime emit path end-to-end. Pinned `drawing-hash`:
1562
+ `eae59a6d44c41ef3b08b20728a9ee723bf0a0cd62e1107c9ab19aa4efa27b488`.
1563
+ - `DRAW_INTERACTIVE_UPDATE_SCENARIO` — captures the
1564
+ `draw.horizontalLine(bar.close)` handle in module-level state
1565
+ on bar 0, then calls `handle.update({ price: bar.close })` on
1566
+ every subsequent bar across the 10 000-bar goldenBars stream.
1567
+ Pins handle-id stability + the full emission sequence (1
1568
+ `create` + 9 999 `update`s). Pinned `drawing-hash`:
1569
+ `797d159809da91f43fc32149998da9e5d71b011134564d42c3e5da2027c22e6f`.
1570
+ - `DRAW_HANDLE_REMOVE_SCENARIO` — creates a `draw.text(...)` on
1571
+ bar 0, calls `handle.remove()` on bar 100 (= time
1572
+ `1_708_640_000_000`; goldenBars are 1-day intervals). Pinned
1573
+ `drawing-hash` captures both the `op: "create"` and
1574
+ `op: "remove"` emissions; `drawing-budget-exceeded` absent.
1575
+ Pinned `drawing-hash`:
1576
+ `b742d39fe5d03cb211b57bc26f0d24a89f9db966c481279368cc083932394a09`.
1577
+
1578
+ Scenario cardinality after Task 20: \*\*61 per-kind + 12 task-bundles
1579
+
1580
+ - 3 (smoke + budget + capability) + 3 (Task-20 constructor) = 79\*\*,
1581
+ of which 78 are in `ALL_SCENARIOS` (the Task-19
1582
+ `DRAW_UNSUPPORTED_KIND_SCENARIO` remains opt-in only).
1583
+
1584
+ ### Divergences from spec (`tasks/phase-3-drawing-parity/20-define-drawing.md`)
1585
+
1586
+ 1. **Spec § Requirements §1 sketches a `compute` shape and a separate
1587
+ `onCreate(ctx, anchors)` / `onUpdate(handle, ctx, anchors)`
1588
+ callback pair.** Per the team-lead brief + the spec's own example
1589
+ (lines 53–58, which uses `compute`), Phase 3 ships the
1590
+ `compute`-based shape only. The `onCreate`/`onUpdate` interactive-
1591
+ editor callbacks are Phase 4 sugar layered on top of the
1592
+ constructor (PLAN.md §10.1.1).
1593
+ 2. **Spec § Requirements §4.2 asks for a new `manifest-kind`
1594
+ `ScenarioAssertion` variant.** Deferred — adding a new assertion
1595
+ variant is a runner-API change out of scope here. The
1596
+ `manifest.kind === "drawing"` contract is covered by unit tests:
1597
+ `defineDrawing.test.ts` (constructor side), `manifest.test.ts`
1598
+ (compiler-builder side), `structuralChecks.test.ts` (AST-walk
1599
+ side), and `compile.test.ts` (end-to-end compile of a
1600
+ `defineDrawing` script). Flag as a Phase-4 follow-up if
1601
+ downstream adapter authors accumulate similar capability/manifest
1602
+ assertions.
1603
+ 3. **Spec § Files lists `defineDrawing.types.test.ts`.** Not created.
1604
+ The sibling `defineIndicator.ts` / `defineAlert.ts` don't have
1605
+ `.types.test.ts` files; the typings are covered through the
1606
+ runtime tests' `script.manifest.kind` access.
1607
+ 4. **Spec § Requirements §6 mentions a "manifest extractor test in
1608
+ compiler package".** Covered by widening
1609
+ `structuralChecks.test.ts` (which captures `kind` from the
1610
+ AST) + extending `manifest.test.ts` + adding the `compile.test.ts`
1611
+ end-to-end row. No new file needed.
1612
+ 5. **`extractCapabilities` widening was not in the original task
1613
+ list** — but is required so a `defineDrawing` script emits
1614
+ `capabilities: ["drawings"]` instead of `["indicators"]`. The
1615
+ change is backwards-compatible (the new `kind` parameter
1616
+ defaults to `"indicator"`) and pinned with new test rows.
1617
+
1618
+ - b0d296b: Phase-3 Task 3 — runtime `draw.*` emission infrastructure.
1619
+
1620
+ **Runtime** — new `packages/runtime/src/emit/draw/` subtree:
1621
+
1622
+ - `createDrawingHandle(slotId, subId, kind, initialState)` allocates
1623
+ a per-handle slot in `ctx.drawingSlots` keyed by `slotId#subId`,
1624
+ emits the first `op: "create"`, and returns a `DrawingHandle`
1625
+ whose `update(patch)` re-emits the FULL merged state under
1626
+ `op: "update"` (PLAN.md §10.3 full-state semantic) and whose
1627
+ `remove()` emits one final `op: "remove"` and flags the slot
1628
+ `removed: true`. Subsequent `update` / `remove` calls on a removed
1629
+ handle no-op. Cross-bar re-entry at the same `slotId#subId`
1630
+ resurrects the slot and emits `op: "update"`.
1631
+ - `pushDrawing(ctx, e)` enforces capability gating
1632
+ (`unsupported-drawing-kind`), wire-shape validation
1633
+ (`malformed-emission`), per-bucket budget on
1634
+ `op: "create"`/`"remove"` against
1635
+ `min(scriptMaxDrawings, adapter.maxDrawingsPerScript)`
1636
+ (`drawing-budget-exceeded`, clamped at zero on remove), and
1637
+ per-bar `(handleId, bar)` dedup (last-write-wins).
1638
+ - `nextSubId(ctx, slotId)` / `resetSubIdCounters(ctx)` —
1639
+ per-callsite per-bar counter; reset at the top of every
1640
+ `onBarClose` / `onBarTick` so iteration `i` at the same callsite
1641
+ yields the same `slotId#i` across bars.
1642
+ - `draw` re-exports core's throwing-stub Proxy verbatim. Per-kind
1643
+ Tasks 5–18 swap real impls into this seam (mirroring how the
1644
+ Phase-2 `ta` re-export switched to `TA_REGISTRY`).
1645
+
1646
+ `RuntimeContext` widens with four new fields: `drawingSlots`,
1647
+ `drawingSubIdCounters`, `drawingBucketCounters`, `scriptMaxDrawings`.
1648
+ `createScriptRunner` initialises them and reads
1649
+ `compiled.manifest.maxDrawings` for the script-side cap. `dispose`
1650
+ clears the slots and resets counters.
1651
+
1652
+ `buildComputeContext` now injects `draw` into the `ComputeContext`
1653
+ the runner hands the compiled script.
1654
+
1655
+ **Core** — `ComputeContext.draw: DrawNamespace` field added (the
1656
+ script-facing surface). Phase-1/-2 scripts that do not consume
1657
+ `draw` keep compiling unchanged; new scripts pick up the namespace
1658
+ through the same destructure pattern as `ta` / `plot` / `hline` /
1659
+ `alert`.
1660
+
1661
+ **Conformance** — `ScenarioAssertion` grows a sixth `drawing-hash`
1662
+ variant. `BufferedRun.drawings` carries the per-bar drained
1663
+ emissions; `hashDrawingSeries(drawings, handleId?)` hashes
1664
+ JSON-stringified `{ handleId, kind, op, state, bar }` tuples in
1665
+ emission order. Failure messages mirror `plot-hash`:
1666
+ `drawing-hash[<label>]: expected <pinned>, actual <computed>
1667
+ (<N> emissions)` — copy `actual` to re-pin.
1668
+
1669
+ No behaviour change for Phase-1/-2 scenarios — the runtime still
1670
+ emits no drawings until the per-kind ports (Tasks 5–18) land.
1671
+
1672
+ - Phase 4 - Editor + Inputs + Timeframes + Tier-1 Pine parity.
1673
+ Adds: input._ builders, state._ / state.tick.\* slots,
1674
+ barstate / syminfo / timeframe views, request.security typed
1675
+ surface (NaN fallback), defineIndicator overrides,
1676
+ Capabilities triad (intervals / multiTimeframe / subPanes /
1677
+ symInfoFields / maxDrawingsPerScript / alertConditions / logs),
1678
+ language-service hover registry + LSP-style API, CodeMirror 6
1679
+ editor shell + /react sub-export, Inputs UI ViewModel + React
1680
+ form. See tasks/phase-4-editor-tier1/README.md.
1681
+ - Add the Phase 4 `input.*` builder namespace and typed input descriptor
1682
+ surface. `InputSchema` now carries `InputDescriptor<unknown>` values,
1683
+ and the compiler ambient shim mirrors the new core declarations so
1684
+ script type resolution stays in lockstep.
1685
+ - Add the Phase 4 `state.*` and `state.tick.*` slot builder surface,
1686
+ including the `MutableSlot<T>` handle type and `ComputeContext.state`.
1687
+ The state builders are compile-time callable holes until the runtime
1688
+ slot implementation lands, and `STATEFUL_PRIMITIVES` now tracks the
1689
+ 8 new slot-id-aware state builders.
1690
+ - Add Phase 4 `barstate`, `syminfo`, and `timeframe` core view exports with matching `ComputeContext` types.
1691
+ - Add Phase 4 script override fields to core define options and compiler manifests.
1692
+ - Add the Phase 4 `request.security` core type surface and sentinel callable hole.
1693
+ - Add compiler extraction for static `request.security` intervals and `requiresIntervals`, and register `request.security` for callsite slot ids.
1694
+ - Resolve runtime `input.*` overrides at mount, add adapter input resolver wiring, and audit universal `ta.*` offset support.