@aiquants/spreadsheet 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,25 +1,82 @@
1
1
  /**
2
- * @fileoverview Frozen-axis coordinate partition (v0.5 plan §2 = columns; v0.6 plan §2 = rows).
3
- * The engine paints frozen leading cells at their raw tree coordinate and scroll-band cells at
4
- * tree − scroll, so a viewport ↔ index mapping splits at the frozen prefix size F_px: the
5
- * forward map is `i < F ? offsetOf(i) : offsetOf(i) − scroll`, the inverse map (guarded on
6
- * F_px > 0) is `F_px > 0 && p < F_px ? indexAt(p) : indexAt(p + scroll)`. Pure, AXIS-NEUTRAL
7
- * internals (frozenSizeOf / toViewportPos / viewportPosToIndex) with column- and row-named
8
- * thin wrappers — the freshness discipline (F/scroll from the handle) stays at the call sites.
2
+ * @fileoverview Frozen-axis coordinate partition, three-way (v0.9 plan §2.1; leading columns =
3
+ * v0.5 plan §2, leading rows = v0.6 plan §2). The engine paints leading frozen cells at their
4
+ * raw tree coordinate, trailing frozen cells anchored to the viewport far edge (extent), and
5
+ * scroll-band cells at tree − scroll — so the viewport ↔ index mapping splits three ways with
6
+ * the branch precedence LEADING > TRAILING > MIDDLE (ADR-19-4: at overlap a pixel resolves to
7
+ * the band that paints there). Pure, AXIS-NEUTRAL internals (frozenSizeOf / trailingSizeOf /
8
+ * toViewportPos / viewportPosToIndex) with column- and row-named thin wrappers taking one
9
+ * REQUIRED per-axis context object (AxisForwardCtx / AxisInverseCtx — no optionals, no
10
+ * defaults; clean cut-over from the v0.8 positional form).
9
11
  *
10
- * 凍結軸の座標パーティション (v0.5 計画 §2 = 列、v0.6 計画 §2 = 行)。エンジンは先頭凍結セルを
11
- * 木座標の生値・スクロール帯セルを 木座標 − scroll で描くため、viewport ↔ index の写像は凍結
12
- * 接頭辞幅 F_px で分割される。純関数の**軸中立**内部 (frozenSizeOf / toViewportPos /
13
- * viewportPosToIndex) に列 / 行の名前付き薄ラッパーを重ねる — 鮮度規律 (F / scroll はハンドル
14
- * 読み) は呼び出しサイトの責務。
12
+ * Registered rules (v0.9 plan §2.1 — binding on every call site):
13
+ * 1. Ctx objects are built FRESH per judgment from `handle.getFrozenSize()` trailing fields +
14
+ * `handle.getViewportSize()`, INLINE at each judgment site — a shared cross-site ctx-builder
15
+ * helper is prohibited (the §5.3 census scanner enforces this mechanically).
16
+ * 2. The canonical source of `AxisInverseCtx.trailingSize` is the TREE field
17
+ * (`getFrozenSize().trailingWidth` / `.trailingHeight`) — never the vis field. The two are
18
+ * classification-equivalent in the inverse, which is exactly why the choice is NAMED here:
19
+ * the §5.3 scanner pins the source field text-level.
20
+ * 3. Extent-0 degenerate — explicit, not emergent: the trailing branch of BOTH maps carries
21
+ * `extent > 0`. Pre-measure / hidden-host judgments (`getViewportSize()` = {0, 0}) leave the
22
+ * trailing branches inert, so the inverse resolves `p ≥ F_px` scroll-relative and the
23
+ * forward maps trailing indices scroll-relative (never to the negative far-edge anchor
24
+ * 0 − tail suffix) — byte-matching v0.8. This is a degenerate-state definition in the style
25
+ * of the `frozenSize > 0` guard, NOT a fallback; the leading branch never reads extent and
26
+ * is untouched.
27
+ * 4. Behaviour pins: `p ≥ extent` with trailingSize > 0 clamps into the LAST trailing track
28
+ * (Excel nearest-visible mirror of the negative-p leading clamp); at trailingSize = 0 every
29
+ * p keeps the v0.4 / v0.5 scroll-relative form (T = 0 identity twin of the F_px = 0 guard);
30
+ * the boundary pixel `p = extent − trailingSize` classifies TRAILING (the `>=`, mirroring
31
+ * the leading p = W_F scroll-side pin — FZ21 class); round-trip domain exclusion: scroll
32
+ * tracks whose viewport px ≥ extent − T_px are outside the partition's domain (mirror of
33
+ * the `p < W_F` exclusion).
34
+ *
35
+ * 凍結軸の 3 分岐座標パーティション (v0.9 計画 §2.1。先頭列 = v0.5 計画 §2、先頭行 = v0.6
36
+ * 計画 §2)。エンジンは先頭凍結セルを木座標の生値、末尾凍結セルを viewport 遠端 (extent)
37
+ * アンカー、スクロール帯セルを 木座標 − scroll で描くため、viewport ↔ index の写像は
38
+ * 先頭 > 末尾 > 中央 の分岐優先順で 3 分割される (ADR-19-4: overlap 画素はそこに描画する帯へ
39
+ * 解決)。純関数の**軸中立**内部に、必須 ctx オブジェクト (AxisForwardCtx / AxisInverseCtx —
40
+ * optional / default なし、v0.8 位置引数形からのクリーンカットオーバー) を取る列 / 行の
41
+ * 名前付き薄ラッパーを重ねる構成。登記規則 4 点: (1) ctx は判断毎にハンドル読みからその場
42
+ * インライン構築 (跨サイト共有ビルダ禁止 — §5.3 スキャナが機械強制)、(2) 逆写像 trailingSize
43
+ * の正準は TREE フィールド (`trailingWidth` / `trailingHeight` — vis 代替は分類等価ゆえ命名で
44
+ * ピン)、(3) extent-0 縮退は明示の縮退状態定義 (フォールバックではない — 順逆両写像の末尾分岐
45
+ * のみ不活性で順写像の末尾 index も scroll 相対、leading は extent を読まない)、(4) 挙動ピン
46
+ * (p ≥ extent は最終末尾トラックへクランプ /
47
+ * T = 0 恒等 / 境界画素は末尾分類 / 往復のドメイン除外)。
15
48
  */
16
49
 
17
50
  import type { AxisModel } from "./axisModel.ts"
18
51
 
19
52
  /**
20
- * Axis-neutral tree-prefix size of the frozen band (0 when frozen <= 0 or empty axis; totalSize
21
- * when frozen covers every track). Both frozenWidthOf and frozenHeightOf delegate here.
22
- * 軸中立の凍結帯木接頭辞サイズ (frozen ≤ 0 / 空軸は 0、全トラック凍結は totalSize)。
53
+ * REQUIRED forward-map context — count-named fields carry the unit (tracks, not px), keeping
54
+ * the type structurally disjoint from AxisInverseCtx (a wrong-object pass is a tsc error; a
55
+ * value transposition inside a correctly-typed literal is caught by the §5.2 mutation set +
56
+ * asymmetric-fixture discipline, not by the type system — stated honestly, v0.9 plan §1.3).
57
+ * `extent` is the viewport size on this axis from `handle.getViewportSize()` at judgment time.
58
+ * 必須の順写像 ctx — 件数系フィールド名が単位 (トラック数) を運び、AxisInverseCtx と構造的に
59
+ * 不交 (オブジェクト取り違えは tsc エラー。正しく型付いたリテラル内の値転置は §5.2 の変異集合 +
60
+ * 非対称フィクスチャ規律が守る — 型システムではない、v0.9 計画 §1.3)。`extent` は判断時
61
+ * `handle.getViewportSize()` のこの軸の viewport 寸法。
62
+ */
63
+ export type AxisForwardCtx = { scroll: number; frozenCount: number; trailingCount: number; extent: number }
64
+
65
+ /**
66
+ * REQUIRED inverse-map context — size-named fields carry the unit (logical px), structurally
67
+ * disjoint from AxisForwardCtx. `trailingSize` is canonically the TREE field
68
+ * (`getFrozenSize().trailingWidth` / `.trailingHeight` — registered rule 2 above); `extent` is
69
+ * the judgment-time viewport size on this axis.
70
+ * 必須の逆写像 ctx — 寸法系フィールド名が単位 (論理 px) を運び、AxisForwardCtx と構造的に不交。
71
+ * `trailingSize` の正準は TREE フィールド (`getFrozenSize().trailingWidth` / `.trailingHeight` —
72
+ * 上記登記規則 2)。`extent` は判断時のこの軸の viewport 寸法。
73
+ */
74
+ export type AxisInverseCtx = { scroll: number; frozenSize: number; trailingSize: number; extent: number }
75
+
76
+ /**
77
+ * Axis-neutral tree-prefix size of the leading frozen band (0 when frozen <= 0 or empty axis;
78
+ * totalSize when frozen covers every track). Both frozenWidthOf and frozenHeightOf delegate here.
79
+ * 軸中立の先頭凍結帯木接頭辞サイズ (frozen ≤ 0 / 空軸は 0、全トラック凍結は totalSize)。
23
80
  * frozenWidthOf / frozenHeightOf の共通実体。
24
81
  */
25
82
  const frozenSizeOf = (axis: AxisModel, frozen: number): number => {
@@ -30,71 +87,138 @@ const frozenSizeOf = (axis: AxisModel, frozen: number): number => {
30
87
  }
31
88
 
32
89
  /**
33
- * Axis-neutral forward map: index to viewport position. Frozen leading tracks are static; scroll
34
- * tracks subtract the scroll offset.
35
- * 軸中立の順写像: index → viewport 位置。先頭凍結トラックは静的、スクロールトラックは scroll を引く。
90
+ * Axis-neutral tree-suffix size of the trailing frozen band (0 when trailing <= 0 or empty
91
+ * axis; totalSize when trailing covers every track; else the tail prefix sum — hidden tracks
92
+ * contribute 0 px through the prefix-sum arithmetic). Both trailingWidthOf and
93
+ * trailingHeightOf delegate here (v0.9 plan §2.1).
94
+ * 軸中立の末尾凍結帯木接尾辞サイズ (trailing ≤ 0 / 空軸は 0、全トラック凍結は totalSize、
95
+ * それ以外は末尾接頭和 — 非表示トラックは接頭和算術を通じて 0 寄与)。trailingWidthOf /
96
+ * trailingHeightOf の共通実体 (v0.9 計画 §2.1)。
36
97
  */
37
- const toViewportPos = (axis: AxisModel, index: number, scroll: number, frozen: number): number => (index < frozen ? axis.offsetOf(index) : axis.offsetOf(index) - scroll)
98
+ const trailingSizeOf = (axis: AxisModel, trailing: number): number => {
99
+ if (trailing <= 0 || axis.count === 0) {
100
+ return 0
101
+ }
102
+ // 末尾接尾和 = totalSize − offsetOf(count − trailing) (末尾 T トラックの Σ — 導出: §2.1)
103
+ return trailing >= axis.count ? axis.totalSize() : axis.totalSize() - axis.offsetOf(axis.count - trailing)
104
+ }
38
105
 
39
106
  /**
40
- * Axis-neutral inverse map: viewport position to index. The frozen branch exists ONLY when
41
- * F_px > 0 — below F_px it resolves at the raw tree coordinate (negative p clamps into the
42
- * leading frozen track, Excel-like nearest visible); at/after F_px the scroll band continues at
43
- * tree = p + scroll. At F_px = 0 every p (incl. negative) keeps the v0.4 scroll-relative form
44
- * (guard prevents an unguarded p < 0 from sinking into indexAt(p) = track 0).
45
- * 軸中立の逆写像: viewport 位置 → index。凍結分岐は F_px > 0 のときのみ — F_px 未満は木座標の
46
- * 生値 (負 p は先頭凍結トラックへクランプ)、以上はスクロール帯 (木 = p + scroll)。F_px = 0 は
47
- * 負 p も含め全域 v0.4 形 (無ガードの p < 0 → indexAt(p) = トラック 0 の吸込を防ぐ)。
107
+ * Axis-neutral three-way forward map: index to viewport position (v0.9 plan §2.1). Leading
108
+ * frozen tracks are static at the raw tree offset; trailing frozen tracks are static anchored
109
+ * to the far edge (extent − tail suffix — scroll-independent); scroll tracks subtract scroll.
110
+ * Branch precedence LEADING > TRAILING > MIDDLE. trailingCount = 0 short-circuits to the exact
111
+ * v0.8 two-branch form (T = 0 identity as a capability branch); the trailing branch also
112
+ * carries `extent > 0` (registered rule 3 — pre-measure {0, 0} keeps trailing indices
113
+ * scroll-relative, byte-identical to v0.8, instead of the negative far-edge anchor).
114
+ * 軸中立の 3 分岐順写像: index → viewport 位置 (v0.9 計画 §2.1)。先頭凍結トラックは木座標の
115
+ * 生値で静的、末尾凍結トラックは遠端アンカー (extent − 末尾接尾和 — scroll 非依存) で静的、
116
+ * スクロールトラックは scroll を引く。分岐優先順は 先頭 > 末尾 > 中央。trailingCount = 0 は
117
+ * v0.8 の 2 分岐形へそのまま短絡 (T = 0 恒等 = 能力分岐)。末尾分岐は `extent > 0` も持つ
118
+ * (登記規則 3 — 未測定 {0, 0} では末尾 index も scroll 相対で v0.8 と byte 一致、負の
119
+ * 遠端アンカーへは写らない)。
48
120
  */
49
- const viewportPosToIndex = (axis: AxisModel, p: number, scroll: number, frozenSize: number): number => (frozenSize > 0 && p < frozenSize ? axis.indexAt(p) : axis.indexAt(p + scroll))
121
+ const toViewportPos = (axis: AxisModel, index: number, { scroll, frozenCount, trailingCount, extent }: AxisForwardCtx): number =>
122
+ index < frozenCount
123
+ ? // 先頭: 木座標の生値 (scroll 非依存の静的位置)
124
+ axis.offsetOf(index)
125
+ : trailingCount > 0 && extent > 0 && index >= axis.count - trailingCount
126
+ ? // 末尾: extent − 末尾接尾和 (totalSize − offsetOf(index) = index 以降の Σ)。遠端アンカーで静的 (ADR-19-4)
127
+ extent - (axis.totalSize() - axis.offsetOf(index))
128
+ : // 中央: 木座標 − scroll (v0.8 恒等形)
129
+ axis.offsetOf(index) - scroll
50
130
 
51
131
  /**
52
- * Tree-prefix width of the frozen band on this axis (0 when F <= 0; totalSize when F covers
53
- * every column). The render-surface twin of the engine's getFrozenSize().width — the pair-call
54
- * discipline keeps the two equal.
55
- * この軸の凍結帯の木接頭辞幅 (F ≤ 0 は 0、全列凍結は totalSize)。エンジン getFrozenSize().width
56
- * の render 面双子 — 対呼び出し規律が両者を一致させる。
132
+ * Axis-neutral three-way inverse map: viewport position to index (v0.9 plan §2.1). Precedence
133
+ * LEADING > TRAILING > MIDDLE (registered rule: at overlap the pixel resolves to the band that
134
+ * paints there). The leading branch exists only when frozenSize > 0 (negative p clamps into the
135
+ * leading track — Excel nearest visible); the trailing branch only when trailingSize > 0 AND
136
+ * extent > 0 (registered rule 3 — pre-measure {0, 0} keeps v0.8 conduct byte-identical; without
137
+ * the term every p ≥ −T_px would claim trailing); the boundary pixel p = extent − trailingSize
138
+ * classifies TRAILING (`>=` — FZ21 mirror), and p ≥ extent clamps into the LAST trailing track
139
+ * through indexAt's tree clamp (tree = totalSize + (p − extent) ≥ totalSize). Everything else
140
+ * continues scroll-relative at tree = p + scroll.
141
+ * 軸中立の 3 分岐逆写像: viewport 位置 → index (v0.9 計画 §2.1)。優先順は 先頭 > 末尾 > 中央
142
+ * (overlap 画素はそこに描画する帯へ解決 — 登記規則)。先頭分岐は frozenSize > 0 のときのみ
143
+ * (負 p は先頭トラックへクランプ — Excel 最寄り可視)、末尾分岐は trailingSize > 0 かつ
144
+ * extent > 0 のときのみ (登記規則 3 — 未測定 {0, 0} では v0.8 挙動と byte 一致。この項が
145
+ * なければ全 p ≥ −T_px が末尾を主張する)。境界画素 p = extent − trailingSize は末尾に分類
146
+ * (`>=` — FZ21 ミラー)、p ≥ extent は 木 = totalSize + (p − extent) ≥ totalSize となり
147
+ * indexAt の木クランプで最終末尾トラックへ着地。残余は 木 = p + scroll のスクロール相対。
148
+ */
149
+ const viewportPosToIndex = (axis: AxisModel, p: number, { scroll, frozenSize, trailingSize, extent }: AxisInverseCtx): number =>
150
+ frozenSize > 0 && p < frozenSize
151
+ ? // 先頭が最優先: 木座標の生値 (負 p は indexAt の下限クランプで先頭トラックへ)
152
+ axis.indexAt(p)
153
+ : trailingSize > 0 && extent > 0 && p >= extent - trailingSize
154
+ ? // 末尾が第二 (ADR-19-4): 木 = totalSize − (extent − p) (遠端からの残距離を木末尾から引く)
155
+ axis.indexAt(axis.totalSize() - (extent - p))
156
+ : // 中央が最後: 木 = p + scroll (v0.8 恒等形)
157
+ axis.indexAt(p + scroll)
158
+
159
+ /**
160
+ * Tree-prefix width of the leading frozen band on this axis (0 when F <= 0; totalSize when F
161
+ * covers every column). The render-surface twin of the engine's getFrozenSize().width — the
162
+ * pair-call discipline keeps the two equal.
163
+ * この軸の先頭凍結帯の木接頭辞幅 (F ≤ 0 は 0、全列凍結は totalSize)。エンジン
164
+ * getFrozenSize().width の render 面双子 — 対呼び出し規律が両者を一致させる。
57
165
  */
58
166
  export const frozenWidthOf = (axis: AxisModel, frozenCols: number): number => frozenSizeOf(axis, frozenCols)
59
167
 
60
168
  /**
61
- * Frozen-band tree-prefix HEIGHT H_F (row twin of frozenWidthOf — v0.6 plan §2).
62
- * 凍結帯の木接頭辞高 H_F (frozenWidthOf の行双子 — v0.6 計画 §2)。
169
+ * Leading frozen-band tree-prefix HEIGHT H_F (row twin of frozenWidthOf — v0.6 plan §2).
170
+ * 先頭凍結帯の木接頭辞高 H_F (frozenWidthOf の行双子 — v0.6 計画 §2)。
63
171
  */
64
172
  export const frozenHeightOf = (axis: AxisModel, frozenRows: number): number => frozenSizeOf(axis, frozenRows)
65
173
 
66
174
  /**
67
- * Forward map: column index to viewport x (the left edge). Frozen columns are static; scroll
68
- * columns subtract hx (v0.5 plan §2).
69
- * 順写像: 列 index → viewport x (左端)。凍結列は静的、スクロール列は hx を引く。
175
+ * Tree-suffix WIDTH W_T of the trailing frozen band (v0.9 plan §1.3). The render-surface twin
176
+ * of the engine's getFrozenSize().trailingWidth — feed it the clamped effective count
177
+ * (min(max(0, T), count − effectiveLeading) — leading wins, byte-matching the engine clamp).
178
+ * 末尾凍結帯の木接尾辞幅 W_T (v0.9 計画 §1.3)。エンジン getFrozenSize().trailingWidth の
179
+ * render 面双子 — クランプ済み実効件数 (min(max(0, T), count − effectiveLeading) —
180
+ * leading 勝ち、エンジンクランプと byte 一致) を渡す。
181
+ */
182
+ export const trailingWidthOf = (axis: AxisModel, trailingCols: number): number => trailingSizeOf(axis, trailingCols)
183
+
184
+ /**
185
+ * Trailing frozen-band tree-suffix HEIGHT H_T (row twin of trailingWidthOf — v0.9 plan §1.3).
186
+ * 末尾凍結帯の木接尾辞高 H_T (trailingWidthOf の行双子 — v0.9 計画 §1.3)。
187
+ */
188
+ export const trailingHeightOf = (axis: AxisModel, trailingRows: number): number => trailingSizeOf(axis, trailingRows)
189
+
190
+ /**
191
+ * Forward map: column index to viewport x (the left edge), three-way (v0.9 plan §2.1). Leading
192
+ * frozen columns static; trailing frozen columns anchored to ctx.extent; scroll columns
193
+ * subtract ctx.scroll. Takes the REQUIRED AxisForwardCtx built fresh at the judgment site
194
+ * (the extent > 0 term keeps pre-measure conduct byte-identical to v0.8 — registered rule 3).
195
+ * 順写像: 列 index → viewport x (左端) の 3 分岐 (v0.9 計画 §2.1)。先頭凍結列は静的、末尾
196
+ * 凍結列は ctx.extent アンカー、スクロール列は ctx.scroll を引く。判断サイトでその場構築した
197
+ * 必須 AxisForwardCtx を取る (extent > 0 項が未測定挙動を v0.8 と byte 一致に保つ — 登記
198
+ * 規則 3)。
70
199
  */
71
- export const colToViewportX = (axis: AxisModel, col: number, hx: number, frozenCols: number): number => toViewportPos(axis, col, hx, frozenCols)
200
+ export const colToViewportX = (axis: AxisModel, col: number, ctx: AxisForwardCtx): number => toViewportPos(axis, col, ctx)
72
201
 
73
202
  /**
74
- * Forward map: row index to viewport y (the top edge). Frozen rows static; scroll rows subtract vy (v0.6 plan §2).
75
- * 順写像: 行 index → viewport y (上端)。凍結行は静的、スクロール行は vy を引く (v0.6 計画 §2)。
203
+ * Forward map: row index to viewport y (the top edge) — row twin of colToViewportX (v0.9 plan §2.1).
204
+ * 順写像: 行 index → viewport y (上端) — colToViewportX の行双子 (v0.9 計画 §2.1)。
76
205
  */
77
- export const rowToViewportY = (axis: AxisModel, row: number, vy: number, frozenRows: number): number => toViewportPos(axis, row, vy, frozenRows)
206
+ export const rowToViewportY = (axis: AxisModel, row: number, ctx: AxisForwardCtx): number => toViewportPos(axis, row, ctx)
78
207
 
79
208
  /**
80
- * Inverse map: viewport x to column index. The frozen branch exists ONLY when W_F > 0: below
81
- * W_F the frozen band resolves at the raw tree coordinate (negative px clamps into the leading
82
- * frozen column — Excel-like nearest visible); at or right of W_F the scroll band continues at
83
- * tree = px + hx. At W_F = 0 every px — including negative (pointer left of the cells area
84
- * during a leftward drag) — keeps the v0.4 hx-relative form (R2 fix: an unguarded `px < 0`
85
- * would swallow negative px into indexAt(px) → column 0, breaking the F = 0 identity).
86
- * 逆写像: viewport x → 列 index。凍結分岐は W_F > 0 のときのみ存在: W_F 未満は凍結帯
87
- * (木座標の生値 — 負の px は先頭凍結列へクランプ。Excel 同型の最寄り可視)、W_F 以上は
88
- * スクロール帯 (木座標 = px + hx)。W_F = 0 は負の px (左ドラッグでセル領域外へ出た
89
- * ポインタ) も含め全域が v0.4 の hx 相対形 (R2 是正: 無ガードの `px < 0` は負 px を
90
- * indexAt(px) = 列 0 へ吸い込み、F = 0 恒等を破る)。
209
+ * Inverse map: viewport x to column index, three-way with precedence LEADING > TRAILING >
210
+ * MIDDLE (v0.9 plan §2.1). Takes the REQUIRED AxisInverseCtx built fresh at the judgment site
211
+ * (trailingSize from the TREE field — registered rule 2; extent from the judgment-time
212
+ * getViewportSize() read — the extent > 0 term keeps pre-measure conduct byte-identical to v0.8).
213
+ * 逆写像: viewport x → 列 index の 3 分岐 (優先順 先頭 > 末尾 > 中央 — v0.9 計画 §2.1)。判断
214
+ * サイトでその場構築した必須 AxisInverseCtx を取る (trailingSize は TREE フィールド — 登記
215
+ * 規則 2。extent は判断時 getViewportSize() 読み — extent > 0 項が未測定挙動を v0.8 と byte
216
+ * 一致に保つ)。
91
217
  */
92
- export const viewportXToCol = (axis: AxisModel, px: number, hx: number, frozenWidth: number): number => viewportPosToIndex(axis, px, hx, frozenWidth)
218
+ export const viewportXToCol = (axis: AxisModel, px: number, ctx: AxisInverseCtx): number => viewportPosToIndex(axis, px, ctx)
93
219
 
94
220
  /**
95
- * Inverse map: viewport y to row index (row twin of viewportXToCol — v0.6 plan §2). Guarded on
96
- * H_F > 0; H_F = 0 keeps the v0.4 vy-relative form for all py including negative.
97
- * 逆写像: viewport y → 行 index (viewportXToCol の行双子 — v0.6 計画 §2)。H_F > 0 ガードつき、
98
- * H_F = 0 は負 py も含め v0.4 の vy 相対形。
221
+ * Inverse map: viewport y to row index — row twin of viewportXToCol (v0.9 plan §2.1).
222
+ * 逆写像: viewport y → 行 index — viewportXToCol の行双子 (v0.9 計画 §2.1)。
99
223
  */
100
- export const viewportYToRow = (axis: AxisModel, py: number, vy: number, frozenHeight: number): number => viewportPosToIndex(axis, py, vy, frozenHeight)
224
+ export const viewportYToRow = (axis: AxisModel, py: number, ctx: AxisInverseCtx): number => viewportPosToIndex(axis, py, ctx)