You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
wrap_non_matching_in_previous (src/simlin-engine/src/ltm_augment.rs) freezes a non-live subscripted read whose index is itself a model dependency as PREVIOUS(q[PREVIOUS(ctr, ctr)]). The outer PREVIOUS is a capture of q[PREVIOUS(ctr)] evaluated at t-1, so the read is q_{t-1}[ctr_{t-2}]. The paper's partial (docs/reference/ltm--loops-that-matter.md section 3.1, Delta_x(z) = f(x_current, y_previous) - z_previous) reads every non-isolated input at the previous step, i.e. q_{t-1}[ctr_{t-1}] -- and the anchor z_previous was itself computed with ctr_{t-1}. So whenever ctr moves, the frozen read and the anchor see different elements of q, and the difference is credited to the isolated source.
Where it is pinned today
db::ltm_tests::pinned_double_lag_residual_is_not_a_structural_zero pins the residual as values: on the colliding_index_name_model fixture, the pop[nyc] -> share score's boston slot -- an arm whose every occurrence is frozen and which should be inert -- is -1.0588, 0.7297, -1.0286, 0.8182 at steps 2..5. The rustdoc on colliding_index_boston_series records that the semantics question was deferred, and that a crude probe (disabling the whole index pass) takes the fixture to exactly 0 while redding 5 tests -- an upper bound on the narrow fix's cost, not a measurement of it.
ltm_augment_zero_slot.rs (reach_of, the BuiltinReach::LagsOneStep arm) recognizes the shape (PREVIOUS(q[PREVIOUS(ctr, ctr)]) "reads q at t-1 indexed by ctr at t-2, where the anchor indexed at t-1") and refuses to classify the arm as PREVIOUS(target). That half is settled and must stay while the lowering is what it is: the arm is NOT a structural zero today precisely because of the double lag (GH ltm: link-score generation materializes one full guard-form arm per element of an arrayed target (quadratic in element count) #977's omission must not claim it).
The clock case was fixed; this is the sibling left in place
Commit f5e0325 ("engine: freeze the clock inside the ceteris-paribus partial", branch ltm-fidelity) found the identical double lag for the clock: z = x + arr[TIME] was lowered to PREVIOUS(arr[PREVIOUS(TIME)]) = arr_{t-1}[TIME_{t-2}], and the twice-lagged clock flipped the link's sign from the second scored step (pinned in tests/integration/ltm_frozen_clock.rs: 0.1667, 0.1379, 0.1213, 0.1118 at t = 2..5 in the once-lagged form). The fix leaves a time-dependent call verbatim inside a subtree the wrap is about to freeze (the frozen flag), because the enclosing capture already lags the whole read once. The wrap_non_matching_in_previous rustdoc says explicitly that a model dependency in that position IS still lagged twice and "does not join" the clock fix -- the A3 review of that commit left it in place as pre-existing.
Fix shape
When the enclosing read is being frozen whole (frozen == true), leave a model-dependency index read verbatim, exactly as f5e0325 does for a time-dependent call: PREVIOUS(q[ctr]) is q_{t-1}[ctr_{t-1}], the paper's value. Keep straight:
The static-selector guard is separate and must not move: an_index_naming_the_axis_own_element_stays_a_static_selector pins that q[s1] (an element of the axis) stays unwrapped and qualified.
Once the lowering is PREVIOUS(q[ctr]), the zero-slot predicate's LagsOneStep arm may legitimately classify such an arm as PREVIOUS(target). The pinned values in pinned_double_lag_residual_is_not_a_structural_zero will move (possibly to an exact 0 on that fixture, whose arm reads no live occurrence); the test must be re-derived from the new lowering rather than re-captured, and its series[2].abs() > 1.0 soundness assertion is specifically a statement about the OLD lowering.
Needs a value test on a model with a genuinely dynamic index (q[ctr], ctr changing every step) where the paper's partial is computed by hand from the recorded series, alongside the existing fixture.
Cost: small (a few lines in the wrap's Expr0::Subscript arm plus re-derived pins).
LTM fidelity work of 2026-09-07..09 (branches ltm-fidelity / ltm-fidelity-post); surfaced by the A3 review of commit f5e0325, which fixed the clock case and left this one as pre-existing.
Problem
wrap_non_matching_in_previous(src/simlin-engine/src/ltm_augment.rs) freezes a non-live subscripted read whose index is itself a model dependency asPREVIOUS(q[PREVIOUS(ctr, ctr)]). The outerPREVIOUSis a capture ofq[PREVIOUS(ctr)]evaluated at t-1, so the read isq_{t-1}[ctr_{t-2}]. The paper's partial (docs/reference/ltm--loops-that-matter.mdsection 3.1,Delta_x(z) = f(x_current, y_previous) - z_previous) reads every non-isolated input at the previous step, i.e.q_{t-1}[ctr_{t-1}]-- and the anchorz_previouswas itself computed withctr_{t-1}. So wheneverctrmoves, the frozen read and the anchor see different elements ofq, and the difference is credited to the isolated source.Where it is pinned today
db::ltm_tests::pinned_double_lag_residual_is_not_a_structural_zeropins the residual as values: on thecolliding_index_name_modelfixture, thepop[nyc] -> sharescore'sbostonslot -- an arm whose every occurrence is frozen and which should be inert -- is -1.0588, 0.7297, -1.0286, 0.8182 at steps 2..5. The rustdoc oncolliding_index_boston_seriesrecords that the semantics question was deferred, and that a crude probe (disabling the whole index pass) takes the fixture to exactly 0 while redding 5 tests -- an upper bound on the narrow fix's cost, not a measurement of it.ltm_augment_zero_slot.rs(reach_of, theBuiltinReach::LagsOneSteparm) recognizes the shape (PREVIOUS(q[PREVIOUS(ctr, ctr)])"readsqatt-1indexed byctratt-2, where the anchor indexed att-1") and refuses to classify the arm asPREVIOUS(target). That half is settled and must stay while the lowering is what it is: the arm is NOT a structural zero today precisely because of the double lag (GH ltm: link-score generation materializes one full guard-form arm per element of an arrayed target (quadratic in element count) #977's omission must not claim it).wrap_index_non_matching_in_previous's GH ltm-augment: partial builders PREVIOUS-wrap dimension-name subscript indices (Wildcard and Bare shapes), silently stubbing scores #759 comment already calls reading an index two steps back "semantically wrong for a genuinely-dynamic index".The clock case was fixed; this is the sibling left in place
Commit f5e0325 ("engine: freeze the clock inside the ceteris-paribus partial", branch
ltm-fidelity) found the identical double lag for the clock:z = x + arr[TIME]was lowered toPREVIOUS(arr[PREVIOUS(TIME)])=arr_{t-1}[TIME_{t-2}], and the twice-lagged clock flipped the link's sign from the second scored step (pinned intests/integration/ltm_frozen_clock.rs: 0.1667, 0.1379, 0.1213, 0.1118 at t = 2..5 in the once-lagged form). The fix leaves a time-dependent call verbatim inside a subtree the wrap is about to freeze (thefrozenflag), because the enclosing capture already lags the whole read once. Thewrap_non_matching_in_previousrustdoc says explicitly that a model dependency in that position IS still lagged twice and "does not join" the clock fix -- the A3 review of that commit left it in place as pre-existing.Fix shape
When the enclosing read is being frozen whole (
frozen == true), leave a model-dependency index read verbatim, exactly as f5e0325 does for a time-dependent call:PREVIOUS(q[ctr])isq_{t-1}[ctr_{t-1}], the paper's value. Keep straight:an_index_naming_the_axis_own_element_stays_a_static_selectorpins thatq[s1](an element of the axis) stays unwrapped and qualified.frozen == false) the index still takes its own freeze (arr[PREVIOUS(idx)]for the isolated source read through a dynamic index; GH ltm: first-live-step NaN in dynamic-index per-element link scores (uninitialized PREVIOUS(index) capture helper) #975's head-lag pinper_element_dynamic_index_scores_preserve_head_lag). This issue is only about an index inside an already-frozen read.PREVIOUS(q[ctr]), the zero-slot predicate'sLagsOneSteparm may legitimately classify such an arm asPREVIOUS(target). The pinned values inpinned_double_lag_residual_is_not_a_structural_zerowill move (possibly to an exact 0 on that fixture, whose arm reads no live occurrence); the test must be re-derived from the new lowering rather than re-captured, and itsseries[2].abs() > 1.0soundness assertion is specifically a statement about the OLD lowering.q[ctr],ctrchanging every step) where the paper's partial is computed by hand from the recorded series, alongside the existing fixture.Cost: small (a few lines in the wrap's
Expr0::Subscriptarm plus re-derived pins).Components affected
src/simlin-engine/src/ltm_augment.rs--wrap_non_matching_in_previous(Expr0::Subscriptarm,frozenflag),wrap_index_non_matching_in_previoussrc/simlin-engine/src/ltm_augment_zero_slot.rs--reach_of(LagsOneSteparm)src/simlin-engine/src/db/ltm_tests.rs--pinned_double_lag_residual_is_not_a_structural_zero,colliding_index_boston_seriessrc/simlin-engine/src/db/ltm_char_tests.rs-- the GH ltm: first-live-step NaN in dynamic-index per-element link scores (uninitialized PREVIOUS(index) capture helper) #975 head-lag pins (intent unchanged; confirm they stay green)Related
PREVIOUS(idx)capture helper for a LIVE dynamic-index readDiscovery context
LTM fidelity work of 2026-09-07..09 (branches
ltm-fidelity/ltm-fidelity-post); surfaced by the A3 review of commit f5e0325, which fixed the clock case and left this one as pre-existing.