Summary
For an arrayed (multi-dimensional) source, Opcode::VectorSortOrder returns global flat indices into the entire array instead of per-iterated-slice 0-based ranks. When the resulting "order" is fed to VECTOR ELM MAP, the out-of-range global indices index a single-row slice -> out-of-bounds -> NaN. This is a runtime-numeric correctness bug (the model compiles and runs to FINAL TIME).
Concrete C-LEARN manifestation
C-LEARN (test/xmutil_test_models/C-LEARN v77 for Vensim.mdl):
Target Order[COP,Target] = VECTOR SORT ORDER(Effective Target Year[COP,Target], ASCENDING) ; line ~19565
sorted target year[COP,Target] = VECTOR ELM MAP(Effective Target Year[COP,t1], Target Order[COP,Target]) ; line ~19486
Target has 3 elements; COP has 7 rows. Opcode::VectorSortOrder (src/simlin-engine/src/vm.rs:~2334-2377) iterates the whole input view (for i in 0..size, size = input_view.size()) and writes orig_idx -- the global flat index -- into the result. So for the 7th COP row it emits [18,19,20] instead of the per-row 0-based ranks [0,1,2].
VECTOR ELM MAP (src/simlin-engine/src/vm_vector_elm_map.rs:~107, the flat_i = base_i + offset_val path with the [0, full_len) -> NaN guard) then uses [18,19,20] to index the 7-element single-column slice -> out of bounds -> NaN. (The ELM MAP OOB->NaN behavior is the correct genuine-Vensim semantics from the Phase 5 work; the bug is the bad indices coming from VectorSortOrder, not the guard.)
Observed: target_order[cop_developing_b] = [18,19,20] (Simlin) vs [0,1,2] (Ref.vdf); sorted_target_year[cop_developing_b] = NaN vs 4000.0 (Ref.vdf). This is one of the two root causes of C-LEARN's core series being entirely NaN at AC7.3.
Root cause
Opcode::VectorSortOrder sorts and ranks over the flattened whole-array view and emits absolute flat indices. This is the multi-row / apply-to-all (A2A) case. Phase 4 (AC5) corrected the 1-based->0-based output for the single-row case but did not cover per-iterated-slice ranks for a multi-dimensional source: a sort order for [COP,Target] must produce, for each iterated COP row, a 0-based permutation within that row ([0..Target.len())), not flat indices into the whole [COP,Target] array.
Why it matters
- Correctness: any arrayed
VECTOR SORT ORDER whose result is consumed positionally (e.g. as the offset argument to VECTOR ELM MAP, the canonical Vensim idiom) produces out-of-range indices -> NaN. C-LEARN's sorted target year series is entirely NaN as a direct consequence.
Components affected
src/simlin-engine/src/vm.rs:~2334-2377 -- Opcode::VectorSortOrder (emits global flat indices over the whole view)
src/simlin-engine/src/vm_vector_elm_map.rs:~107 -- the consumer whose OOB->NaN guard (correctly) trips on the bad indices
Possible approach for resolution
VectorSortOrder must rank within the currently-iterated source slice (per-row), 0-based, producing a valid per-row index permutation rather than absolute flat indices into the whole array. The fix must keep the single-row AC5 case byte-identical (it is already genuine-Vensim 0-based for an effectively-1-D source).
Context / lineage
Freshly root-caused during Phase 6 of the element-level cycle resolution work (branch clearn-hero-model, HEAD b0910842). Being fixed now as Phase 6 Task 9; filing for traceability. Relate to the AC5 / Phase 4 VECTOR SORT ORDER work (commit a82dff29, which fixed the single-row 1-based->0-based output).
This is the Cluster B spurious-NaN bug. Relationship to existing issues:
Summary
For an arrayed (multi-dimensional) source,
Opcode::VectorSortOrderreturns global flat indices into the entire array instead of per-iterated-slice 0-based ranks. When the resulting "order" is fed toVECTOR ELM MAP, the out-of-range global indices index a single-row slice -> out-of-bounds -> NaN. This is a runtime-numeric correctness bug (the model compiles and runs to FINAL TIME).Concrete C-LEARN manifestation
C-LEARN (
test/xmutil_test_models/C-LEARN v77 for Vensim.mdl):Targethas 3 elements;COPhas 7 rows.Opcode::VectorSortOrder(src/simlin-engine/src/vm.rs:~2334-2377) iterates the whole input view (for i in 0..size,size = input_view.size()) and writesorig_idx-- the global flat index -- into the result. So for the 7thCOProw it emits[18,19,20]instead of the per-row 0-based ranks[0,1,2].VECTOR ELM MAP(src/simlin-engine/src/vm_vector_elm_map.rs:~107, theflat_i = base_i + offset_valpath with the[0, full_len)-> NaN guard) then uses[18,19,20]to index the 7-element single-column slice -> out of bounds -> NaN. (The ELM MAP OOB->NaN behavior is the correct genuine-Vensim semantics from the Phase 5 work; the bug is the bad indices coming fromVectorSortOrder, not the guard.)Observed:
target_order[cop_developing_b] = [18,19,20](Simlin) vs[0,1,2](Ref.vdf);sorted_target_year[cop_developing_b] = NaNvs4000.0(Ref.vdf). This is one of the two root causes of C-LEARN's core series being entirely NaN at AC7.3.Root cause
Opcode::VectorSortOrdersorts and ranks over the flattened whole-array view and emits absolute flat indices. This is the multi-row / apply-to-all (A2A) case. Phase 4 (AC5) corrected the 1-based->0-based output for the single-row case but did not cover per-iterated-slice ranks for a multi-dimensional source: a sort order for[COP,Target]must produce, for each iteratedCOProw, a 0-based permutation within that row ([0..Target.len())), not flat indices into the whole[COP,Target]array.Why it matters
VECTOR SORT ORDERwhose result is consumed positionally (e.g. as the offset argument toVECTOR ELM MAP, the canonical Vensim idiom) produces out-of-range indices -> NaN. C-LEARN'ssorted target yearseries is entirely NaN as a direct consequence.Components affected
src/simlin-engine/src/vm.rs:~2334-2377--Opcode::VectorSortOrder(emits global flat indices over the whole view)src/simlin-engine/src/vm_vector_elm_map.rs:~107-- the consumer whose OOB->NaN guard (correctly) trips on the bad indicesPossible approach for resolution
VectorSortOrdermust rank within the currently-iterated source slice (per-row), 0-based, producing a valid per-row index permutation rather than absolute flat indices into the whole array. The fix must keep the single-row AC5 case byte-identical (it is already genuine-Vensim 0-based for an effectively-1-D source).Context / lineage
Freshly root-caused during Phase 6 of the element-level cycle resolution work (branch
clearn-hero-model, HEADb0910842). Being fixed now as Phase 6 Task 9; filing for traceability. Relate to the AC5 / Phase 4 VECTOR SORT ORDER work (commita82dff29, which fixed the single-row 1-based->0-based output).This is the Cluster B spurious-NaN bug. Relationship to existing issues:
VECTOR SORT ORDERvector.datfixture inconsistent + genuine-Vensim multi-dim VSO semantics unverified by any live fixture): closely related and effectively predicted this -- it noted the engine's flattened whole-view VSO over a 2-D input yields[0,1,3,2,4,5](flat indices) and that no live fixture verifies genuine-Vensim multi-dim semantics. engine: dormant vector.dat 2-D VECTOR SORT ORDER p block is inconsistent + genuine-Vensim multi-dim VSO semantics unverified by any live fixture #576 is a test-data / coverage gap ("NOT a regression and NOT a current blocker"). This issue is the confirmed runtime correctness bug, root-caused against C-LEARN's live data with a real-Vensim cross-check (Ref.vdf) -- i.e. C-LEARN now provides exactly the genuine-Vensim multi-dim VSO ground truth engine: dormant vector.dat 2-D VECTOR SORT ORDER p block is inconsistent + genuine-Vensim multi-dim VSO semantics unverified by any live fixture #576 said was missing, and it confirms the flattened whole-view semantics are wrong. The engine: dormant vector.dat 2-D VECTOR SORT ORDER p block is inconsistent + genuine-Vensim multi-dim VSO semantics unverified by any live fixture #576 dormant-fixture / verification follow-up remains valid; this issue should be cross-referenced when fixing either.full_source_lenhas no numeric end-to-end test gate): a coverage gap on a different opcode field; distinct from this runtime correctness bug inVectorSortOrder.