Skip to main content

Overview

Every time you change a model, ProDex runs a validation pass that checks the model against a comprehensive set of correctness rules. The result is the play button at the bottom of the canvas: green means the model is valid and ready to run; red means there’s something blocking simulation. Validation happens continuously while you build, which means you catch problems as you introduce them rather than discovering them hours later when a simulation fails.

The Red Play Button

When the play button at the bottom of the canvas is green, hovering it shows the tooltip “Run simulation.” Click it and the simulation runs. When it’s red, hovering opens a popover with:
  • A header counting the problems (e.g., “1 validation error” or “6 validation errors”).
  • The verbatim error text from the validator, naming the offending component and what’s wrong with it.
  • A Fix with Assistant button (with a sparkles icon) that hands the error to Dexter.
A real example of what you’ll see in the popover:
“Component ‘Halo Repress Halo Queue’ (type: BufferDefinition) must have exactly one outgoing standard connection, but has 2.”
Clicking Fix with Assistant drops that exact error into a Dexter conversation and asks it to propose a corrective change. Dexter reads the error, locates the component on the canvas, and suggests the edit — usually deleting the extra connection or splitting the buffer’s downstream into a Router. You review the proposed change before it’s applied. If you’d rather fix it yourself, the error message is specific enough to point you at the field, component, or expression to edit. The popover dismisses on un-hover, so reopen it if you need to re-read.

What Gets Validated

The validator runs about seventeen distinct rule categories. The list below covers the most consequential ones — the ones that block real models day-to-day.

Configuration

Required top-level fields, file-format correctness, schedule presence (when required). A model without a metadata.json or with malformed JSON fails before any rule below it runs.

Naming and Identifiers

  • Component ids must be unique across all resources, stations, and components in the model
  • :: is reserved as the path separator — no single component id may contain it
  • State variable names must not collide with components, entities, constants, lookup tables, or reserved DSL identifiers
  • Topic names must be unique
  • Entity attribute names must be unique per entity type
A collision fails fast with a specific error naming both ends of the conflict.

Connection Topology

Validation enforces strict cardinality on connections:
  • Source — 0 incoming, exactly 1 outgoing
  • Sink — 0 outgoing
  • Router, ModelNode — at least 1 outgoing
  • Process, Buffer, Combiner, Separator, Transformer — exactly 1 outgoing standard connection
  • You cannot draw a connection to or from a Station. Stations hold components; connections wire components, not containers.
Disconnected components — orphans, dead ends, components with the wrong number of outgoing edges — are caught here.

Entity Flow Consistency

Each component declares which entity types it accepts and emits. Validation checks that:
  • Every component’s expected input types are produced somewhere upstream
  • No component receives a type it can’t handle (the flow doesn’t carry a raw_material into a process expecting painted_part)
  • Combiner and Separator type expectations match the surrounding flow
This is how silent type mismatches get caught before they corrupt a run.

Entities

Attribute names unique per entity type; choices arrays valid for list types; lower_boundupper_bound on Number attributes; length non-negative on Text.

Components and Stations

Required fields per component type (e.g., station_id on Process, Combiner, Separator, and Transformer — even though the schema defaults it to null, the validator rejects models that leave it unset). Station capacity keys must match entity types that enter the station; including an internally-created output type fails.

Resources

Every defined resource must be referenced by at least one component; every component reference must point at a real resource.

Routers

Probabilistic routes have non-negative weights; conditional routes have non-empty rule lists; type-based routes cover the expected input types.

Transformers

Output entity types declared on the Transformer match the actual emitted types.

Entity-Generator Attribute Assignment

The strictest set of attribute-type restrictions:
  • dsl_expr is not valid for text_list or number_list attributes
  • round_robin and random_choice are valid only for boolean, text_list, and number_list
  • random (probability distribution sampling) is valid only for number
  • fixed and weighted are valid for any type
Mismatches fail at validation with a specific error pointing at the offending assignment. See Entities for the full compatibility matrix.

DSL Expressions

The DSL is statically typed. Every expression is checked at validation time, not at runtime:
  • Boolean fields (conditions, gates) must produce booleans
  • Numeric fields must produce numbers
  • An aggregation function (SUM, MAX, ANY, etc.) outside a multi-entity context fails
  • An identifier not available in the expression’s context (e.g., an entity attribute in a no-entity context) fails
  • A referenced constant, lookup table, or state variable that doesn’t exist in the model’s opt-in list fails
See The Expression Language — the Expression Contexts section is the right starting point when the popover says an identifier isn’t available.

State Variables

Each state variable has a type (one of number, text, boolean) and a required initial_value matching that type. Mismatched types fail.

Topics

Topic names unique; the topic-trigger graph (edges from topic A to topic B exist when a listener on A emits B) must be acyclic. Topic cycles are caught here before they can cause runaway behavior at simulation time.

Event Hooks and Listeners

Hooks reference valid lifecycle events for their host component type — process started on a Process is fine, process started on a Resource fails. Listener topic fields reference declared topics. Per-component action restrictions are enforced (see Event System). Two specific direct self-loops are forbidden: a Source’s on_entity_created hook running a release action, and a Buffer’s on_entity_exited hook running a release action — both fail validation.
Flow-graph cycles are explicitly allowed. Routing entities back through an upstream Process (rework loops) is a valid pattern. Only event-driven loops (topic acyclicity, direct self-loops) are forbidden.

Constants and Lookup Tables

Multi-key lookup tables require all LOOKUP() calls to pass the right number of keys with matching types. Key possible_keys allowlists (when declared) are enforced. Each model’s constants[] / lookup_tables[] opt-in arrays must reference real factory-level slugs.

Schedule

  • All datetime fields (Start Time, End Time, release_time) must be timezone-aware ISO datetimes. Naive datetimes fail validation immediately — no implicit timezone fallback.
  • Every event timestamp must fall between Start Time and End Time (or Start Time + model Duration if End Time isn’t set)
  • element_id references must resolve to real components (including hierarchical parent::child paths)
  • Material releases target Sources or Buffers; releases to other component types fail
  • Scheduled actions are restricted to assign and emit — component-bound actions (pause, resume, set capacity, release) are not valid in schedules. Use a scheduled emit to a topic and let an Event Listener handle the side effect.

Nested Models (Model Nodes)

Validation recurses into every nested model referenced by a ModelNode. Errors inside a sub-model bubble up with a path prefix — "Nested model 'cell_a' (used by model node 'fab_cell'): {error}". ModelNode nested_model_id must appear in the parent’s imported_models[] list. Mapping fields (input_mappings, output_mappings, resource_mappings, topic_mappings, state_variable_mappings) all resolve against real targets.

Simulation Duration

Maximum simulation duration is 365 days. The schema doesn’t enforce this — it’s a validation-layer rule. Models with longer durations fail capture even though the JSON itself is well-formed.

Sources Without Arrival Logic

A Source isn’t required to have an arrival_logic field. Three independent arrival modes can coexist on the same Source: stochastic arrivals from arrival_logic, scheduled releases from a schedule’s material_releases[], and event-driven releases from release actions on Event Hooks or Listeners. A Source with no arrival_logic, no schedule material releases, and no release actions has no way to produce entities — that’s a no-op model, not a validation error. The validator allows it because it’s a legitimate intermediate state during authoring.

What Validation Does NOT Catch

Validation catches structural and type-level correctness. A few classes of issue slip through:
  • Semantic correctness. A model that compiles and runs may still encode wrong assumptions about your operation. Validation can’t tell you a 30-second process should really be 30 minutes.
  • Resource starvation patterns. A model where a critical resource is held forever by a stuck entity validates fine but produces meaningless results. Read the run’s results to catch these.
  • Sensible Monte Carlo / experiment design. Validation runs on a single model; it doesn’t reason about whether your sweep of snapshots is statistically informative.
  • Performance. A valid model can still take a long time to run. Use Snapshots and short Durations to iterate fast.
The validator is a structural correctness gate, not a correctness proof.

Validation and Dexter

Validation applies to changes Dexter makes, too. When Dexter modifies your model, the same rules run. If Dexter tries to configure a component that would fail validation, the platform rejects the change and Dexter has to retry or ask you to clarify. This is one of the mechanisms that keeps Dexter from silently corrupting a working model. If Dexter is repeatedly failing to apply a change, check the validation errors yourself — often the fix is a small correction Dexter couldn’t infer (a missing constant, an attribute name that doesn’t exist on the entity type) and telling Dexter the specific fact unblocks it. The Fix with Assistant button is the same channel in reverse: it gives Dexter the exact error text so it doesn’t have to guess.

Tips

  • Fix structural errors first. Disconnected-component errors often hide expression errors that can’t run until the structure is sound.
  • The error messages are specific. If an error is confusing, search the message text in the docs — most validation errors map directly to concepts covered in the reference.
  • Validation is fast. You can make sweeping edits and let validation check them — you don’t need to be surgical.
  • Red play button is your friend. It’s a forcing function that prevents you from wasting time on a simulation that was going to fail. Treat each red state as information, not an obstacle.