vyre-primitives 0.6.1

Compositional primitives for vyre - marker types (always on) + Tier 2.5 LEGO substrate (feature-gated per domain).
Documentation
use super::super::*;
use crate::graph::program_graph::ProgramGraphShape;



    #[test]
    fn cpu_ref_expands_in_place_frontier_pass() {
        let (frontier, changed) = cpu_ref(
            4,
            &[0, 2, 3, 4, 4],
            &[1, 2, 3, 3],
            &[1, 1, 1, 1],
            &[0b0001],
            1,
        );
        assert_eq!(frontier, vec![0b1111]);
        assert_eq!(changed, 1);
    }

    #[test]
    fn cpu_ref_closure_reaches_fixpoint() {
        let closure = cpu_ref_closure(
            4,
            &[0, 1, 2, 3, 3],
            &[1, 2, 3],
            &[1, 1, 1],
            &[0b0001],
            0xFFFF_FFFF,
            10,
        );
        assert_eq!(closure, vec![0b1111]);
    }

    #[test]
    fn cpu_ref_closure_into_reuses_buffers() {
        let mut current = Vec::with_capacity(8);
        let mut next = Vec::with_capacity(8);
        cpu_ref_closure_into(
            4,
            &[0, 1, 2, 3, 3],
            &[1, 2, 3],
            &[1, 1, 1],
            &[0b0001],
            0xFFFF_FFFF,
            10,
            &mut current,
            &mut next,
        );
        let current_capacity = current.capacity();
        let next_capacity = next.capacity();
        assert_eq!(current, vec![0b1111]);

        cpu_ref_closure_into(
            4,
            &[0, 1, 2, 3, 3],
            &[1, 2, 3],
            &[1, 1, 1],
            &[0],
            0xFFFF_FFFF,
            10,
            &mut current,
            &mut next,
        );
        assert_eq!(current.capacity(), current_capacity);
        assert_eq!(next.capacity(), next_capacity);
        assert_eq!(current, vec![0]);
    }

    #[test]
    fn validate_csr_inputs_rejects_mismatched_and_nonmonotonic_csr() {
        let err = validate_csr_inputs(2, &[0, 1, 1], &[1], &[]).unwrap_err();
        assert!(err.contains("edge_targets.len() == edge_kind_mask.len()"));

        let err = validate_csr_inputs(2, &[0, 2, 1], &[1, 0], &[1, 1]).unwrap_err();
        assert!(err.contains("offsets must be monotonic"));
    }

    #[test]
    fn cpu_ref_into_rejects_malformed_csr_before_touching_output_storage() {
        let mut out = vec![0xDEAD_BEEFu32, 0xABCD_EF01];
        let ptr = out.as_ptr();
        let previous_hook = std::panic::take_hook();
        std::panic::set_hook(Box::new(|_| {}));
        let err = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
            cpu_ref_into(2, &[0, 2, 1], &[1, 0], &[1, 1], &[0b01], 1, &mut out);
        }));
        std::panic::set_hook(previous_hook);

        assert!(err.is_err(), "malformed CSR must be rejected");
        assert_eq!(
            out,
            vec![0xDEAD_BEEFu32, 0xABCD_EF01],
            "Fix: malformed CSR must not clear or resize caller output before validation."
        );
        assert_eq!(out.as_ptr(), ptr);
    }

    #[test]
    fn empty_offsets_shorthand_is_empty_edge_set_only() {
        assert_eq!(
            validate_csr_inputs(64, &[], &[], &[]).expect("Fix: empty CSR shorthand is valid"),
            CsrForwardOrChangedLayout {
                node_count: 64,
                node_words: 64,
                edge_offset_words: 65,
                edge_storage_words: 1,
                shape_edge_count: 0,
                frontier_words: 2,
            }
        );

        let err = validate_csr_inputs(64, &[], &[1], &[]).unwrap_err();
        assert!(err.contains("empty edge_offsets may only encode an empty edge set"));

        let mut out = Vec::new();
        let changed = cpu_ref_into(64, &[], &[], &[], &[0b101], 0xFFFF_FFFF, &mut out);
        assert_eq!(changed, 0);
        assert_eq!(out.len(), 2);
        assert_eq!(out[0], 0b101);
        assert_eq!(out[1], 0);
    }

    #[test]
    fn dispatch_plan_selects_changed_history_and_pins_buffer_shape() {
        let edge_offsets = vec![0u32; 66];
        let plan =
            plan_csr_forward_or_changed_dispatch(65, &edge_offsets, &[], &[], 0xFFFF_FFFF, 8)
                .expect("Fix: bounded CSR forward-or-changed plan should validate");

        assert_eq!(plan.layout().node_count, 65);
        assert_eq!(plan.frontier_words(), 3);
        assert_eq!(plan.node_words(), 65);
        assert_eq!(plan.edge_storage_words(), 1);
        assert_eq!(plan.changed_words(), 8);
        assert!(plan.uses_changed_history());
        assert_eq!(plan.changed_slot_value(3), Some(3));
        assert_eq!(plan.changed_read_index(3).unwrap(), 3);
        assert!(
            plan.changed_read_index(8).is_err(),
            "Fix: changed-history readback index must reject iterations outside the buffer"
        );
        assert_eq!(plan.dispatch_grid(), [65, 1, 1]);
        assert_eq!(
            plan.program().workgroup_size,
            CSR_FORWARD_OR_CHANGED_WORKGROUP_SIZE
        );
        assert!(
            plan.program()
                .buffers()
                .iter()
                .any(|buffer| buffer.name() == "changed_slot"),
            "Fix: changed-history fast path must expose the primitive slot selector"
        );
    }

    #[test]
    fn dispatch_plan_uses_single_changed_word_for_unbounded_or_zero_iteration_cases() {
        let plan = plan_csr_forward_or_changed_dispatch(0, &[], &[], &[], 0xFFFF_FFFF, 0)
            .expect("Fix: zero-node zero-iteration plan should validate");
        assert_eq!(plan.frontier_words(), 1);
        assert_eq!(plan.changed_words(), 1);
        assert!(!plan.uses_changed_history());
        assert_eq!(plan.changed_slot_value(0), None);
        assert_eq!(plan.changed_read_index(99).unwrap(), 0);
        assert_eq!(plan.dispatch_grid(), [1, 1, 1]);

        let long_plan = plan_csr_forward_or_changed_dispatch(1, &[0, 0], &[], &[], 0xFFFF_FFFF, 65)
            .expect("Fix: long-running plan should validate without changed history");
        assert_eq!(long_plan.changed_words(), 1);
        assert!(!long_plan.uses_changed_history());
        assert!(
            !long_plan
                .program()
                .buffers()
                .iter()
                .any(|buffer| buffer.name() == "changed_slot"),
            "Fix: unbounded path must not carry the changed-history slot input"
        );
    }

    #[test]
    fn parallel_program_keeps_frontier_and_changed_resident() {
        let program = csr_forward_or_changed_parallel(
            ProgramGraphShape::new(65, 4),
            "frontier",
            "changed",
            0xFFFF_FFFF,
        );
        assert_eq!(program.workgroup_size, [1, 1, 1]);
        let names: Vec<&str> = program.buffers.iter().map(|buffer| buffer.name()).collect();
        assert!(names.contains(&"frontier"));
        assert!(names.contains(&"changed"));
        assert!(
            names.iter().any(|name| name.starts_with("pg_")),
            "parallel CSR expansion must keep ProgramGraph buffers resident"
        );
    }

    #[test]
    fn parallel_batch_program_packs_query_frontiers() {
        let program = csr_forward_or_changed_parallel_batch(
            ProgramGraphShape::new(65, 4),
            "frontiers",
            "changed",
            0xFFFF_FFFF,
            3,
        );
        assert_eq!(program.workgroup_size, [1, 1, 1]);
        let frontier = program
            .buffers
            .iter()
            .find(|buffer| buffer.name() == "frontiers")
            .expect("Fix: frontiers buffer must exist");
        let changed = program
            .buffers
            .iter()
            .find(|buffer| buffer.name() == "changed")
            .expect("Fix: changed buffer must exist");
        assert_eq!(frontier.count(), 9);
        assert_eq!(changed.count(), 3);
    }