@@ -174,6 +174,16 @@ public(package) fun remove_range(
174174 );
175175}
176176
177+ #[test_only]
178+ public (package ) fun debug_contains_node (tree: &StrikePayoutTree , tick: u64 ): bool {
179+ tree.nodes.contains (tick)
180+ }
181+
182+ #[test_only]
183+ public (package ) fun debug_node_count (tree: &StrikePayoutTree ): u64 {
184+ debug_node_count_subtree (&tree.nodes, tree.root)
185+ }
186+
177187fun apply_range (
178188 tree: &mut StrikePayoutTree ,
179189 lower_tick: u64 ,
@@ -210,7 +220,7 @@ fun apply_boundary_delta(
210220 is_start,
211221 add,
212222 );
213- tree.root = option:: some ( new_root) ;
223+ tree.root = new_root;
214224}
215225
216226fun apply_at (
@@ -220,12 +230,12 @@ fun apply_at(
220230 terms: PayoutTerms ,
221231 is_start: bool ,
222232 add: bool ,
223- ): u64 {
233+ ): Option < u64 > {
224234 if (root.is_none ()) {
225235 assert ! (add, EInsufficientPayoutTerms );
226236 let leaf = new_leaf (tick, terms, is_start);
227237 nodes.add (tick, leaf);
228- return tick
238+ return option:: some ( tick)
229239 };
230240
231241 let root_tick = *root.borrow ();
@@ -237,8 +247,12 @@ fun apply_at(
237247 } else {
238248 apply_terms_delta (&mut node.local_end, terms, add);
239249 };
250+ if (is_empty_node (node)) {
251+ let _removed = nodes.remove (root_tick);
252+ return merge_subtrees (nodes, node.left, node.right)
253+ };
240254 resummarize (nodes, root_tick, node);
241- return root_tick
255+ return option:: some ( root_tick)
242256 };
243257
244258 if (tick < root_tick) {
@@ -250,11 +264,15 @@ fun apply_at(
250264 is_start,
251265 add,
252266 );
253- let left_node = nodes[new_left];
254- if (left_node.priority > node.priority) {
255- return rotate_right (nodes, root_tick, node, new_left, left_node)
267+ if (add && new_left.is_some ()) {
268+ let left_tick = *new_left.borrow ();
269+ let left_node = nodes[left_tick];
270+ if (left_node.priority > node.priority) {
271+ let rotated = rotate_right (nodes, root_tick, node, left_tick, left_node);
272+ return option::some (rotated)
273+ };
256274 };
257- node.left = option:: some ( new_left) ;
275+ node.left = new_left;
258276 } else {
259277 let new_right = apply_at (
260278 nodes,
@@ -264,15 +282,19 @@ fun apply_at(
264282 is_start,
265283 add,
266284 );
267- let right_node = nodes[new_right];
268- if (right_node.priority > node.priority) {
269- return rotate_left (nodes, root_tick, node, new_right, right_node)
285+ if (add && new_right.is_some ()) {
286+ let right_tick = *new_right.borrow ();
287+ let right_node = nodes[right_tick];
288+ if (right_node.priority > node.priority) {
289+ let rotated = rotate_left (nodes, root_tick, node, right_tick, right_node);
290+ return option::some (rotated)
291+ };
270292 };
271- node.right = option:: some ( new_right) ;
293+ node.right = new_right;
272294 };
273295
274296 resummarize (nodes, root_tick, node);
275- root_tick
297+ option:: some ( root_tick)
276298}
277299
278300fun new_leaf (tick: u64 , terms: PayoutTerms , is_start: bool ): PayoutNode {
@@ -327,6 +349,29 @@ fun rotate_left(
327349 right_tick
328350}
329351
352+ fun merge_subtrees (
353+ nodes: &mut Table <u64 , PayoutNode >,
354+ left: Option <u64 >,
355+ right: Option <u64 >,
356+ ): Option <u64 > {
357+ if (left.is_none ()) return right;
358+ if (right.is_none ()) return left;
359+
360+ let left_tick = *left.borrow ();
361+ let right_tick = *right.borrow ();
362+ let mut left_node = nodes[left_tick];
363+ let mut right_node = nodes[right_tick];
364+ if (left_node.priority > right_node.priority) {
365+ left_node.right = merge_subtrees (nodes, left_node.right, right);
366+ resummarize (nodes, left_tick, left_node);
367+ option::some (left_tick)
368+ } else {
369+ right_node.left = merge_subtrees (nodes, left, right_node.left);
370+ resummarize (nodes, right_tick, right_node);
371+ option::some (right_tick)
372+ }
373+ }
374+
330375fun settlement_prefix_terms (
331376 nodes: &Table <u64 , PayoutNode >,
332377 root: Option <u64 >,
@@ -362,8 +407,7 @@ fun settlement_prefix_terms(
362407/// Two collapses keep the eval count down:
363408/// - skip-zero-delta: when `local_start.quantity == local_end.quantity` the two
364409/// sides contribute the same `P·q` and cancel in `walk_linear`'s top-level
365- /// subtraction, so the `up_price` eval is skipped (exact; also drops the
366- /// fully-redeemed boundaries the treap never GCs).
410+ /// subtraction, so the `up_price` eval is skipped (exact).
367411/// - bounded interpolation, only when `tolerance > 0`: if the subtree's exact
368412/// price span (`up_price(min_tick·ts) - up_price(max_tick·ts)`, monotone so
369413/// non-negative) is within `tolerance`, the whole subtree is priced at the
@@ -496,6 +540,24 @@ fun payout_terms(quantity: u64, floor_shares: u64): PayoutTerms {
496540 PayoutTerms { quantity, floor_shares }
497541}
498542
543+ fun is_zero_terms (terms: PayoutTerms ): bool {
544+ terms.quantity == 0 && terms.floor_shares == 0
545+ }
546+
547+ fun is_empty_node (node: PayoutNode ): bool {
548+ is_zero_terms (node.local_start) && is_zero_terms (node.local_end)
549+ }
550+
551+ #[test_only]
552+ fun debug_node_count_subtree (nodes: &Table <u64 , PayoutNode >, root: Option <u64 >): u64 {
553+ if (root.is_none ()) return 0 ;
554+ let tick = *root.borrow ();
555+ let node = nodes[tick];
556+ 1
557+ + debug_node_count_subtree (nodes, node.left)
558+ + debug_node_count_subtree (nodes, node.right)
559+ }
560+
499561fun apply_terms_delta (value: &mut PayoutTerms , delta: PayoutTerms , add: bool ) {
500562 if (add) {
501563 value.quantity = value.quantity + delta.quantity;
0 commit comments