Problem
wrap_non_matching_in_previous (src/simlin-engine/src/ltm_augment.rs, the LOOKUP / LOOKUP_FORWARD / LOOKUP_BACKWARD arm around line 685) holds the TABLE argument (child 0) verbatim and only wraps the index argument(s).
Holding the table reference verbatim is correct and deliberate: a graphical-function table is static data with no value slot, so lookup(PREVIOUS(table), ...) cannot compile, and wrapping it silently zeroes the whole link-score fragment. That is the failure mode behind WRLD3's identically-zero table-mediated link scores, and the in-code comment says so.
The defect is that the arm holds the whole argument verbatim, including any subscript index expressions inside it -- and those are genuine runtime value reads:
LOOKUP(g[idx], x) -- `idx` is a variable read
LOOKUP(pop[Region, other], x) -- likewise
compiler::codegen::extract_table_info (src/simlin-engine/src/compiler/codegen.rs:610) evaluates such an index to select the table element at runtime: its Expr::Subscript arm computes an element offset from the index expressions (offset = sum(index_i * stride_i), built out of the live index Exprs), while its Expr::Var / Expr::StaticSubscript arms resolve a static one to a constant.
So for a target whose equation contains LOOKUP(g[idx], x), every link-score partial of that target varies with the current-step value of idx, even though ceteris paribus requires everything except the live source occurrence to be frozen. The score misattributes idx's influence to whatever source is being isolated.
Scope
This affects all link-score partials, not just the PerElement ones. For the non-PerElement callers the wrap's ctx.pin is None, so the table argument is returned completely untouched (the None => a branch) -- no pin descent, no freeze, nothing.
It predates branch roundtrips-track-a2.
How it was found
A codex review pass on roundtrips-track-a2 flagged the same defect in the narrow PerElement path, where that branch had just made the fragment reach compilability. Commit fb0863b0 ("engine: gate the LTM table-index refusal on the enclosing freeze") fixes only the PerElement path: it declines a bare table argument carrying a runtime index (pin_dimension_name_indices returns discharged = false, which becomes WrapOutcome::missing_occurrence, i.e. a warned skip) while keeping one that is already inside a freeze (a pre-existing PREVIOUS / INIT, a whole-frozen reducer), since a freeze lags the index read too.
The general case outside that path is untouched -- hence this issue.
Why it matters
Correctness of link scores, and silently so: the partial simply reports a wrong number. There is no compile failure and no diagnostic; the score just attributes the table index's per-step movement to the isolated source. This is the "degraded score masquerades as a real one" hazard class (#546 / #548 / #587 / #759).
Components affected
src/simlin-engine/src/ltm_augment.rs -- wrap_non_matching_in_previous (the LOOKUP arm), wrap_index_non_matching_in_previous, qualify_element_index
src/simlin-engine/src/compiler/codegen.rs -- extract_table_info (why the index is a runtime read)
src/simlin-engine/src/db/ltm_char_golden/ -- 16 char goldens that may need regeneration
Suggested fix direction
Give the wrap's own index pass (wrap_index_non_matching_in_previous) the table argument's index expressions, instead of holding the entire argument verbatim. That keeps the table head static (still no PREVIOUS around the table itself) while freezing the index reads like any other other-dep.
Two things a fix must handle:
- The index pass already carries the element / dimension-name guards (
qualify_element_index, the element-of-any-dimension and dimension-name arms), so a static element selector must stay static -- reuse those guards rather than reimplementing them.
- The change could alter generated equation text, so the 16 char goldens in
src/simlin-engine/src/db/ltm_char_golden/ should be checked. None currently contains a LOOKUP inside a partial, so it may well be a no-op for them.
Note on the deliberate decision
"Hold the table argument verbatim" is a deliberate decision with a real motivation (it fixed WRLD3's identically-zero table-mediated link scores). It should therefore be revisited on its own terms -- with a test that pins both halves (the table head stays unwrapped AND the index gets frozen) -- rather than changed as a side effect of some other refactor.
Related: #983 (sibling LTM finding filed from the same branch). Mechanically adjacent but distinct from #759, which is the opposite error (over-wrapping a dimension name inside a subscript index); this one is under-wrapping a genuine runtime index because it never reaches the index pass at all.
Problem
wrap_non_matching_in_previous(src/simlin-engine/src/ltm_augment.rs, theLOOKUP/LOOKUP_FORWARD/LOOKUP_BACKWARDarm around line 685) holds the TABLE argument (child 0) verbatim and only wraps the index argument(s).Holding the table reference verbatim is correct and deliberate: a graphical-function table is static data with no value slot, so
lookup(PREVIOUS(table), ...)cannot compile, and wrapping it silently zeroes the whole link-score fragment. That is the failure mode behind WRLD3's identically-zero table-mediated link scores, and the in-code comment says so.The defect is that the arm holds the whole argument verbatim, including any subscript index expressions inside it -- and those are genuine runtime value reads:
compiler::codegen::extract_table_info(src/simlin-engine/src/compiler/codegen.rs:610) evaluates such an index to select the table element at runtime: itsExpr::Subscriptarm computes an element offset from the index expressions (offset = sum(index_i * stride_i), built out of the live indexExprs), while itsExpr::Var/Expr::StaticSubscriptarms resolve a static one to a constant.So for a target whose equation contains
LOOKUP(g[idx], x), every link-score partial of that target varies with the current-step value ofidx, even though ceteris paribus requires everything except the live source occurrence to be frozen. The score misattributesidx's influence to whatever source is being isolated.Scope
This affects all link-score partials, not just the
PerElementones. For the non-PerElementcallers the wrap'sctx.pinisNone, so the table argument is returned completely untouched (theNone => abranch) -- no pin descent, no freeze, nothing.It predates branch
roundtrips-track-a2.How it was found
A codex review pass on
roundtrips-track-a2flagged the same defect in the narrowPerElementpath, where that branch had just made the fragment reach compilability. Commitfb0863b0("engine: gate the LTM table-index refusal on the enclosing freeze") fixes only thePerElementpath: it declines a bare table argument carrying a runtime index (pin_dimension_name_indicesreturnsdischarged = false, which becomesWrapOutcome::missing_occurrence, i.e. a warned skip) while keeping one that is already inside a freeze (a pre-existingPREVIOUS/INIT, a whole-frozen reducer), since a freeze lags the index read too.The general case outside that path is untouched -- hence this issue.
Why it matters
Correctness of link scores, and silently so: the partial simply reports a wrong number. There is no compile failure and no diagnostic; the score just attributes the table index's per-step movement to the isolated source. This is the "degraded score masquerades as a real one" hazard class (#546 / #548 / #587 / #759).
Components affected
src/simlin-engine/src/ltm_augment.rs--wrap_non_matching_in_previous(theLOOKUParm),wrap_index_non_matching_in_previous,qualify_element_indexsrc/simlin-engine/src/compiler/codegen.rs--extract_table_info(why the index is a runtime read)src/simlin-engine/src/db/ltm_char_golden/-- 16 char goldens that may need regenerationSuggested fix direction
Give the wrap's own index pass (
wrap_index_non_matching_in_previous) the table argument's index expressions, instead of holding the entire argument verbatim. That keeps the table head static (still noPREVIOUSaround the table itself) while freezing the index reads like any other other-dep.Two things a fix must handle:
qualify_element_index, the element-of-any-dimension and dimension-name arms), so a static element selector must stay static -- reuse those guards rather than reimplementing them.src/simlin-engine/src/db/ltm_char_golden/should be checked. None currently contains aLOOKUPinside a partial, so it may well be a no-op for them.Note on the deliberate decision
"Hold the table argument verbatim" is a deliberate decision with a real motivation (it fixed WRLD3's identically-zero table-mediated link scores). It should therefore be revisited on its own terms -- with a test that pins both halves (the table head stays unwrapped AND the index gets frozen) -- rather than changed as a side effect of some other refactor.
Related: #983 (sibling LTM finding filed from the same branch). Mechanically adjacent but distinct from #759, which is the opposite error (over-wrapping a dimension name inside a subscript index); this one is under-wrapping a genuine runtime index because it never reaches the index pass at all.