Sampfuncs 037 R5 May 2026

SAMPFUNCS 0.3.7 R5 is a mandatory update for the single-player-to-multiplayer modding pipeline.

It is safer, more stable, and handles memory better than any previous iteration. Just remember: with great power comes great responsibility—don't get banned. sampfuncs 037 r5

Stay modded, stay clean.


Do you still play on 0.3.7 servers, or have you moved to open.mp? Let me know in the comments below! SAMPFUNCS 0

Do not download SAMPFUNCS from YouTube video descriptions or random Discord servers. In the last two years, threat actors have embedded RATs (Remote Access Trojans) and cryptocurrency miners into fake "SAMPFUNCS 037 r5" installers. Do you still play on 0

Always:

# --------------------------------------------------------------
# File: sampfuncs/stratified_batch_sampler.py
# --------------------------------------------------------------
from __future__ import annotations
from collections import defaultdict
from itertools import cycle, islice
from typing import Iterator, Mapping, Sequence, Tuple
import numpy as np
def _validate_inputs(
    labels: np.ndarray,
    batch_size: int,
    extra_strata: Mapping[str, np.ndarray] | None,
) -> Tuple[np.ndarray, int, dict]:
    """Internal sanity checks and canonicalisation."""
    if labels.ndim != 1:
        raise ValueError("`labels` must be a 1‑D array of class identifiers.")
    if not isinstance(batch_size, int) or batch_size <= 0:
        raise ValueError("`batch_size` must be a positive integer.")
n_samples = labels.shape[0]
# Convert everything to int64 for consistent hashing later.
    labels = labels.astype(np.int64, copy=False)
# Build a dict of all categorical columns (primary + extra)
    cat_columns: dict[str, np.ndarray] = "__primary__": labels
    if extra_strata:
        for name, arr in extra_strata.items():
            if arr.shape != (n_samples,):
                raise ValueError(
                    f"Extra stratification column `name` must have the same length as `labels`."
                )
            cat_columns[name] = arr.astype(np.int64, copy=False)
return labels, batch_size, cat_columns
def _compute_joint_strata(
    cat_columns: Mapping[str, np.ndarray]
) -> Tuple[np.ndarray, np.ndarray]:
    """
    Return a tuple ``(joint_codes, inv_permutation)`` where:
* ``joint_codes`` is a 1‑D int64 array of length N giving a *single* integer
      code for the joint combination of all categorical columns.
    * ``inv_permutation`` is a permutation that can be used to restore the
      original order (useful when shuffling later).
    """
    # Stack all columns (shape: C x N) and view as a structured dtype.
    # This yields a unique code for each distinct combination.
    stacked = np.stack(list(cat_columns.values()), axis=0)
    # Use a view of the rows as a single byte string; NumPy will then treat
    # each column vector as a “record”.  The resulting dtype is guaranteed
    # to be hashable and comparable.
    dtype = np.dtype([("fi", stacked.dtype) for i in range(stacked.shape[0])])
    structured = stacked.T.view(dtype).ravel()
# ``np.unique`` returns the sorted unique codes and the inverse mapping.
    joint_codes, inv = np.unique(structured, return_inverse=True)
    return joint_codes, inv
def stratified_batch_sampler(
    *,
    labels: np.ndarray,
    batch_size: int,
    stratify_on: Sequence[str] | None = None,
    extra_strata: Mapping[str, np.ndarray] | None = None,
    shuffle: bool = True,
    seed: int | None = None,
    drop_last: bool = False,
) -> Iterator[np.ndarray]:
    # ----------------------------------------------------------
    # 1️⃣  Validate and normalise inputs
    # ----------------------------------------------------------
    labels, batch_size, cat_columns = _validate_inputs(
        labels, batch_size, extra_strata
    )
# If the caller supplied a list of columns, keep only those.
    if stratify_on is not None:
        missing = set(stratify_on) - set(cat_columns.keys())
        if missing:
            raise ValueError(f"Requested stratify columns not found: missing")
        cat_columns = k: cat_columns[k] for k in ("__primary__", *stratify_on)
# ----------------------------------------------------------
    # 2️⃣  Build joint strata codes
    # ----------------------------------------------------------
    joint_codes, inv = _compute_joint_strata(cat_columns)  # inv: N → K
# Group indices by joint stratum
    strata_to_indices: dict[int, np.ndarray] = defaultdict(list)
    for idx, stratum in enumerate(inv):
        strata_to_indices[stratum].append(idx)
# Convert each list to a NumPy array (for fast slicing)
    for k in strata_to_indices:
        strata_to_indices[k] = np.asarray(strata_to_indices[k], dtype=np.int64)
# ----------------------------------------------------------
    # 3️⃣  Shuffle inside each stratum (if requested)
    # ----------------------------------------------------------
    rng = np.random.default_rng(seed) if seed is not None else np.random
    if shuffle:
        for k, arr in strata_to_indices.items():
            rng.shuffle(arr)
# ----------------------------------------------------------
    # 4️⃣  Determine how many samples each stratum contributes per batch
    # ----------------------------------------------------------
    # Compute the proportion of each stratum in the whole dataset.
    total_n = labels.shape[0]
    stratum_counts = np.array([len(arr) for arr in strata_to_indices.values()], dtype=np.int64)
    stratum_proportions = stratum_counts / total_n
# Ideal per‑batch contribution (may be fractional)
    ideal_per_batch = stratum_proportions * batch_size
# Round to nearest integer while guaranteeing the sum ≤ batch_size.
    # We use the “largest remainder” method (a.k.a. Hamilton method).
    floor_counts = np.floor(ideal_per_batch).astype(np.int64)
    remainder = ideal_per_batch - floor_counts
    deficit = batch_size - floor_counts.sum()
# Distribute the remaining slots to strata with the largest remainder.
    if deficit > 0:
        largest = np.argsort(-remainder)[:deficit]
        floor_counts[largest] += 1
per_batch_counts = floor_counts  # final integer contributions per stratum
# ----------------------------------------------------------
    # 5️⃣  Create cyclic iterators for each stratum
    # ----------------------------------------------------------
    # If a stratum has fewer samples than required for one full batch,
    # we will “re‑use” its indices across epochs (the classic oversampling
    # trick).  The iterator is therefore infinite.
    stratum_iters: dict[int, Iterator[int]] = {}
    for stratum_id, arr in strata_to_indices.items():
        if len(arr) == 0:
            # Should never happen, but guard against pathological input.
            continue
        # Cycle the array, then take slices on demand.
        stratum_iters[stratum_id] = cycle(arr)
# ----------------------------------------------------------
    # 6️⃣  Yield batches
    # ----------------------------------------------------------
    while True:
        batch_indices = []
# Pull the required number of samples from each stratum iterator.
        for stratum_id, cnt in enumerate(per_batch_counts):
            if cnt == 0:
                continue
            itr = stratum_iters[stratum_id]
            # `islice` consumes `cnt` elements from the infinite cycle.
            batch_indices.extend(islice(itr, cnt))
# If the total number of collected indices is less than batch_size
        # (possible only when `drop_last=False` and we ran out of data),
        # we simply break after yielding the final smaller batch.
        if len(batch_indices) == 0:
            break  # no data left at all (should only happen when dataset empty)
if len(batch_indices) < batch_size:
            if drop_last:
                break
            # Return the smaller, final batch.
            yield np.asarray(batch_indices, dtype=np.int64)
            break
# Normal full batch – optionally shuffle the order of indices *inside* the batch.
        batch_arr = np.asarray(batch_indices, dtype=np.int64)
        if shuffle:
            rng.shuffle(batch_arr)
yield batch_arr

The previous limit of 256 custom memory records has been expanded. For heavy scripters, this means you can now run massive cleo packs (like those 50+ car mod packs with individual handling) without the dreaded "Unhandled Exception" error.