add_reactevt() now detects when a character string is accidentally passed as .data (the pipe-first argument) and errors with guidance to use name_evt = explicitly.draw_categorical() and qcategorical() are new utility functions that return a 1-based integer index from a vector of probabilities, replacing the error-prone findInterval(luck, cumsum(probs)) + 1L pattern.print.warden_results() is a new print method for results objects returned by run_sim() and run_sim_parallel(), showing the number of analyses, simulations, arms, and access-pattern hints.random_stream() gains a strict argument (default FALSE). When TRUE, draw_n() throws an error on stream exhaustion instead of auto-regenerating with a warning.run_sim() and run_sim_parallel() now check for integer overflow in seed computation at call time, before the simulation starts.run_sim() and run_sim_parallel() now validate that outcome accumulator variables (e.g., q_default, c_default) are scalar (length 1, unnamed) at the start of the first patient-arm evaluation.scale_remaining_time() is a new utility that wraps the common curtime + (get_event(x) - curtime) / hr pattern for rescaling event times by a hazard ratio.seize() gains a priority argument (default 1L) forwarded to attempt_block(). Passing seize_all()-specific arguments (e.g., accum_queue, force_unblock) now produces a clear error pointing to seize_all().summary_results_det(), summary_results_sim(), and summary_results_sens() now auto-detect the nesting level of the results object and either auto-unwrap with a message or error with guidance.validate_model() is a new function that performs static checks on model inputs (arm coverage, event/reaction matching, seed overflow, dimensions) before running the simulation.waning_hr() is a new utility function that computes an effective hazard ratio under linear, exponential, or instant treatment waning, implementing a common NICE submission pattern.release_all() and release_all_if_using() now deduplicate resume events: when the same patient is first in queue on multiple released resources, only one resume event is scheduled (prevents double-seize on retry).seize_all() gains an accum_queue argument (default TRUE). When FALSE, retries do not accumulate phantom queue entries on bottleneck resources — existing entries serve as placeholders.seize_all() no longer rejects (returns NA) when retrying on resources with allow_multiple_queue = FALSE. Previously, a retry was incorrectly rejected because the patient was already queued; now the existing queue entry is recognized and preserved.new_event() and modify_event() now reject NA and NaN event times with an informative error; Inf event times remain valid.release() and attempt_free() now enforce indivisible units: release(amount = NULL) (the default) releases one unit (equivalent to amount = 1), and release(amount = X) requires an exact match with the seized amount — mismatches throw an error. remove_all = TRUE still removes all usage entries regardless of amount.release_all() and release_all_if_using() now follow an all-or-none policy: both functions only act if the patient is currently using all listed resources. If the patient holds some but not all, they do nothing and emit an informative message. release_all(amounts = NULL) purges queue entries as before; release_all(amounts = c(...)) performs indivisible per-resource release without touching queue entries. release_all() now also purges queue entries when the patient is not using any of the listed resources (e.g. a patient who queued via seize_all() and then died before acquiring); previously, calling release_all() at a patient's death event left dead patients stranded in resource queues, causing them to be rescheduled when another patient later released.release_if_using() is a new exported function that releases a resource for the current patient only if they are currently using it, doing nothing otherwise. Unlike release(), it never errors on a non-using patient and never removes queue entries.resource_discrete() gains an allow_multiple_queue argument (default TRUE). When FALSE, a patient that already has a queued entry will be rejected (attempt_block() returns NA) on a second seize attempt rather than being allowed to queue again.seize_all() with policy = "all_or_none" now queues the patient on all bottleneck resources simultaneously (previously only the first unavailable resource was queued). If any bottleneck would reject queuing, no resource is queued and NA is returned. seize_all() also gains a force_unblock argument: when TRUE and all resources have capacity but the patient is blocked by queue position on some resources, the patient is forcibly moved to the front of those queues and acquires all resources.release() is a new helper that frees a resource_discrete for the current patient and, when resume_event is supplied, automatically schedules that event for the next patient in the resource queue — eliminating the manual if(success & queue_size > 0) new_event(...) pattern.release_all() is a new helper that fully purges the current patient from a list of resource_discrete objects (removes from using and from all queue entries) and optionally schedules per-resource resume events. Resume events are only triggered for resources where the patient was actually using (capacity freed).release_all_if_using() is a new helper that frees the current patient from a list of resource_discrete objects only if they are currently using them. Does not touch queue entries, preserving queue position for multi-resource workflows.resource_discrete() gains discipline ("FIFO" or "LIFO") and max_queue arguments. discipline controls ordering within the same priority level. max_queue caps the waiting list so that attempt_block() returns NA (patient rejected) when the queue is full, enabling M/M/c/k systems.resource_discrete() gains new statistics methods: queue_wait_time(), queue_wait_time_current(), had_to_queue(), time_in_use(), utilization(), n_using(), total_patients_blocked(), and total_patients_queued(). queue_wait_time_current() returns 0 at queue entry, grows with elapsed time at each subsequent event while waiting, and returns the total wait on acquisition — replacing the need to manually track time_start_queue. queue_wait_time() returns the final stored wait only after dequeue.resource_discrete() gains batch_seize() for seizing a resource for multiple patients in a single C++ call, and attempt_block() / attempt_free() now accept an amount argument for multi-unit acquisitions by a single patient.seize() is a new thin wrapper around resource$attempt_block() that reads i and curtime from the calling environment, returning TRUE (acquired), FALSE (queued), or NA (rejected).seize_all() is a new helper that atomically seizes a list of resources in C++ under "all_or_none" or "sequential" policy, enabling deadlock-free surgery-scheduling and philosophers-problem patterns. Fixed a bug where "all_or_none" could partially acquire resources when one resource had free capacity but patients already in its queue.shared_decr() and shared_incr() are new one-liner helpers for shared_input counters that increment or decrement by delta and return the new value.add_item() now works correctly with the native pipe (|>). The .data argument has been moved to the first position (.data = NULL, ..., input), so the LHS of |> is naturally routed to .data without relying on magrittr's . symbol. Existing code using %>%, input=, or named ... arguments is unaffected (#TODO).input_block() and run_sim() now correctly handle multiple blocks spanning different simulation levels (e.g., common_all_inputs and common_pt_inputs): n_sensitivity is summed across all blocks, and binary-mode parameter offsets are injected automatically so each block activates at the right DSA iteration.input_block() is a new helper that builds a complete pick_val_v() expression from explicit base, psa, sens, and names_out arguments. The binary and dsa_indicators parameters have been renamed to indicator_sens_binary and sens_indicators respectively to align with pick_val_v(). The dsa_names argument now defaults to NULL, meaning all sensitivity_names are treated as scenarios (one iteration per name); supply dsa_names explicitly to designate which names are DSA directions. Setting an entry in sens_indicators to 0 now permanently excludes that parameter from variation in both DSA and scenario analyses, and the engine automatically deduces n_sensitivity from the number of active parameters/groups.pick_val_v() now correctly respects indicator_psa in grouped mode (indicator_sens_binary = FALSE). Previously, when sens_bool = TRUE and psa_bool = TRUE, all parameters drew from PSA regardless of indicator_psa; parameters with indicator_psa = 0 now correctly draw from base.*adj_val now accepts a vectorized_f argument to speed computations in the case of vectorized functions