Skip to content

Reachability and agreement

Every number the example corpus produces is a measurement of the corpus more than of the toolchain: fifteen models, each written by the people who wrote the compiler, each chosen to force exactly one capability. moon run :coverage produces the other kind of number — over every example model in the Modelica Standard Library at a pinned version, how far does each one get through this toolchain, and why does it stop where it stops.

Two claims are measured separately, because they fail differently and a single "it works" hides both:

  • Reachability — the model parses, flattens, causalizes and simulates. This is a language-coverage question.
  • Agreement — it simulates and produces the same trajectory as a tool people trust. This is a semantics question, and it is the one that can be quietly wrong.

There is a published precedent for saying this plainly. Rumoca reports that 28 % of MSL examples reach end-to-end simulation under it, and that about a third of the models that simulate disagree with OpenModelica. Those numbers are unflattering and checkable, which is what makes them worth having — and worth copying as a habit. The landscape survey has the longer argument.

The rules

The denominator is the half of a fraction that is easiest to fudge, so its rule is stated here: an example model is a non-package.mo file under the Modelica package whose text carries an experiment annotation, named by its path — Modelica/Blocks/Examples/PID_Controller.mo is Modelica.Blocks.Examples.PID_Controller.

  • The MSL is fetched at the pinned version into .cache/msl/, never vendored.
  • Every refusal is attributed: a model that does not reach the end records which stage refused it and why, and the report orders the refusals by how many models each one blocks. That work list is the real product — "arrays block N models" is a decision, a percentage alone is a headline.
  • The reference for agreement is OpenModelica (omc on PATH), never a second run of this toolchain. When omc is absent the agreement number is reported as unmeasured, with the command that would measure it.
  • Not a CI gate, deliberately. The number moves as language coverage moves, and gating on it would make a green pipeline a matter of taste.
  • Measuring and improving are different jobs. The run changes nothing; coverage.json holds the per-model detail for whoever picks up the work list.

The measurements, newest first

2026-09-11, after a relation differentiates to zero for a variable it does not contain

A Jacobian entry is taken for each variable a torn algebraic block solves for. When a residual carries a relation or a logical operator (x > 0, off or fire), differentiating it used to be refused outright, even for a variable the relation does not mention, whose derivative there is simply zero. The derivative rules now return zero in that case and keep the refusal only for the variable the relation actually turns on.

stage models of 430
parsed 415 96.5 %
flattened 90 20.9 %
causalized 41 9.5 %
simulated 27 6.3 %

The totals hold at 90 / 41 / 27: no model was blocked only by this. The nine that named it are the ideal thyristor rectifiers, and there the slack variable s sits inside the relation itself (off = s < 0 or (pre(off) and not fire)), so the derivative is genuinely undefined. An ideal switch is a complementarity, a variable-structure element whose active equations change on an event; supporting it needs event-driven mode switching, not a derivative rule, so that wall stays. This fix is the correctness half: a relation the solved-for variable does not touch no longer stops a block it happens to share.

2026-09-11, after a redeclaration on an extends clause replaces the type

The machines declare a replaceable thermal port of a base type and, in each concrete machine, redeclare it to the type that carries that machine's own heat ports: extends PartialBasicDCMachine( redeclare final ThermalPortDCEE internalThermalPort). The redeclaration rides on the modification the extends clause writes, and that modification was only ever carried as a value on the member, never applied. So the base type was instantiated, its added heat ports were missing, and a connect to one, connect(re.heatPort, internalThermalPort.heatPortExcitation), failed as "not a connector".

A redeclaration written on an extends clause now replaces the member's type, the same way one written at instantiation does. The two spellings meet in the same _redeclared, so there is one rule; a class redeclaration on an extends clause is still left for _rebind.

stage models of 430
parsed 415 96.5 %
flattened 90 20.9 %
causalized 41 9.5 %
simulated 27 6.3 %

The reach jumps: flattened 86 to 90, causalized 23 to 41, simulated 21 to 27. The DC machines flatten, causalize and simulate now that their thermal ports and power-balance records redeclare to the right types; the induction and synchronous machines flatten past this wall and stop at the next (a space-phasor array shape, a star-point condition). No model regressed, verified by a per-stage set difference.

2026-09-11, after a multi-output function in an expression yields its first output

The machine sensors write v_ = ToSpacePhasor(i), and ToSpacePhasor returns two values, the space phasor and the zero-sequence component. Called where one value is expected, Modelica takes the first output and drops the rest, but the flattener refused any call to a function with more than one output, "declares 2 outputs; only one can be inlined". The tuple form (a, b) = f(x) already had its own path.

The single-value inline now reads the first output rather than refusing. The body is run once as before, so every output is computed and the unused ones are simply not read; only a function that declares no output at all is refused now, since there is then nothing to inline. So v_ = ToSpacePhasor(i) becomes the space phasor 2/m * {cos(phi), sin(phi)} * i, the zero sequence dropped.

stage models of 430
parsed 415 96.5 %
flattened 86 20.0 %
causalized 23 5.3 %
simulated 21 4.9 %

Flattened rose from 84 to 86 -- the polyphase rectifier and the sensor testbench, whose space-phasor readouts this had blocked. The other three that named the refusal step to their next wall (an array shape, an if-equation condition). Every one of the 86 still flattens, and causalized and simulated hold at 23 and 21, verified by a per-stage set difference. Agreement is still unmeasured (no omc).

2026-09-10, after size is resolved to its dimension length at flatten

The polyphase sensors write u .^ 2 / size(u, 1), and size(u, 1) is the length of the first dimension of u -- a structural number, three for a three-phase current. The shape reader did not know size: it took a function's shape to be the shape of its arguments, so size(u, 1) was read as a vector of length three, and an array vector / a vector was refused, "an array / is not supported". Even had the shape been right, the element split would then have subscripted u inside the call, size(u[i], 1), which is not what it means.

size is now resolved to a literal during flatten, the same pass that expands zeros(n) and identity(n): size(a, k) becomes the k-th entry of a's flat shape, size(a) the vector of its dimension lengths. Its argument is already flat, so the shape registry has the answer. The shape reader learned size too, so size(a, k) reads as a scalar wherever it is met. It is a structural read, so it never reaches the element split or the emitter as a call.

stage models of 430
parsed 415 96.5 %
flattened 84 19.5 %
causalized 23 5.3 %
simulated 21 4.9 %

Flattened rose from 68 to 84 -- size is written across the polyphase and machine library, so the one fix cleared far more than the six models the array division named. Every one of the 84 flattens with no size left in its equations, and causalized and simulated hold at 23 and 21, verified by a per-stage set difference. The leaders are now a subscript on an inner component (parse, 70), the Clock builtin (66), an array shape unknown at flatten (26 and 24), and an if-equation condition unknown (11). Agreement is still unmeasured (no omc).

2026-09-10, after a range subscript is read as an array slice

The polyphase transformers ground a delta connection with transformerL.i[1:m] = zeros(m) -- a range subscript on the left, a whole-array constructor on the right. A range in a subscript is a slice, a sub-array, but the subscript handler only knew the scalar case: every index had to resolve to one integer, and a range there was refused as "a range is read only in the header of a 'for' equation".

A range subscript now expands to an array literal, one array level per range dimension and a scalar dimension held fixed: x[1:m] becomes {x[1], ..., x[m]}, A[1:2, 3] becomes {A[1, 3], A[2, 3]}. The slice then has the shape of its ranges, so _element reads it out exactly as it reads any array, and an equation between a slice and its right-hand side splits into one scalar equation per element like the rest. The range enumeration a for header and a comprehension already share is factored into one helper the slice reuses. Three examples carried the refusal (TransformerYD, TransformerYY, Rectifier6pulse) and all three now flatten.

stage models of 430
parsed 415 96.5 %
flattened 68 15.8 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are a subscript on an inner component (parse, 70), the Clock builtin (66), an if-equation condition unknown at flatten (27), and an array shape unknown at flatten (24 and 24). Agreement is still unmeasured (no omc).

2026-09-10, after an if-equation may appear in an initial equation section

The SPICE transistors write initial equation if UIC then vcs = IC; end if; -- a parameter-gated initial condition, the same structural if the ordinary equation section already handles: the condition is a parameter, evaluated at flatten, and only the chosen branch's a = b survives, into the initial equations. The initial section refused it anyway. Its guard admitted only a = b, a call equation and a for equation, so an if there was rejected as "only a = b equations may appear in initial equation" before it could reach the handler.

The handler needed nothing new: _if_equation already takes the initial flag, threads it through a deferred condition and into each branch body, so a branch's equations land among the initial equations on their own. Only the guard had to admit ir.IfEquation. Five examples carried the refusal; three now flatten (the difference amplifier and the two heating NOR gates), and the other two step to their next wall (an array shape, a record redeclaration). No model regressed, verified by a per-stage set difference.

stage models of 430
parsed 415 96.5 %
flattened 65 15.1 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are a subscript on an inner component (parse, 70), the Clock builtin (66), an if-equation condition unknown at flatten (27), and an array shape unknown at flatten (24 and 24). Agreement is still unmeasured (no omc).

2026-09-10, after a comprehension in an array declaration binding is expanded

The machines set a per-phase parameter as a comprehension: parameter Real x[m] = {f(i) for i in 1:m}. An array declaration is split into one variable per element, and an array constructor in its binding (zeros(m), identity(m)) was already expanded to the literal array first, so each element could take its own value. A comprehension was not, so it reached the element split whole -- every element carrying the entire comprehension, which the emitter then could not read, or, where the binding was prefixed without the parameter values in hand, refused as "a comprehension is not expanded here".

The comprehension is now expanded the same way a constructor is: to the array it stands for, one element per iterator value, evaluated against the parameters that are the shape. So x[1] binds f(1), x[2] binds f(2), each its own value -- and the refusal, twenty-five induction-machine examples, leaves the list. They do not reach a new stage; each meets the next wall at once, a thermal-port connect the resolver does not see, so the stage totals hold. No model regressed, and the wrong binding it would have produced is corrected.

stage models of 430
parsed 415 96.5 %
flattened 62 14.4 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are a connect endpoint not seen as a connector (parse), the Clock builtin (62), an if-equation condition unknown at flatten (27), and an array shape unknown at flatten (24 and 23). Agreement is still unmeasured (no omc).

2026-09-10, after a whole array of connectors is reached through an array of components

The element-wise connect of the previous change found the array only on the last name of a reference -- plug_p.pin, where pin is the array. The polyphase machines write the other shape: connect(resistor.p, plug_p.pin), one resistor per phase, so resistor.p is a whole array of connectors reached through an array of components, the array sitting on resistor rather than on p. That earlier change then read one side as an array and the other as a single connector and refused them as unequal -- a refusal it had newly introduced, standing in front of sixty-three induction-machine examples.

The array is now found on whichever segment of the reference carries it, last or earlier, and each side is expanded through that segment: resistor.p becomes resistor[i].p, plug_p.pin becomes plug_p.pin[i], and the two are joined phase by phase. The refusal leaves sixty-three models. They do not reach a new stage -- each meets the next wall at once, a comprehension not yet expanded, an if-equation condition or an array shape unknown at flatten -- so the stage totals hold. No model regressed.

stage models of 430
parsed 415 96.5 %
flattened 62 14.4 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are a connect endpoint not seen as a connector (parse), the Clock builtin (62), an if-equation condition unknown at flatten (27), and an array shape unknown at flatten (24 and 23). Agreement is still unmeasured (no omc).

2026-09-10, after a conditional component's condition reads an enumeration literal

The power converters gate a component on an enumeration: SignalPWM referenceGenerator if refType == SingleReferenceType.Sawtooth. The condition on a conditional component was resolved by prefixing its names, but an enumeration literal is not a name -- SingleReferenceType.Sawtooth has to become its ordinal first -- so prefixing read it as a variable that does not exist. An if-equation condition already took that rewrite; a component's condition did not.

It does now, the same literal-then-names order an equation uses, so the literal becomes its number and the comparison against the parameter is a comparison of two numbers. Three DC/DC converter examples flatten end to end -- ChopperBuckBoost_DutyCycle, HBridge_R, HBridge_RL -- so flattened moves 59 to 62. No model regressed.

stage models of 430
parsed 415 96.5 %
flattened 62 14.4 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are a connect endpoint not seen as a connector (parse), the Clock builtin (62), an array shape unknown at flatten (24 and 17), an if-equation condition unknown at flatten (16), and a subscript on an inner component in parse. Agreement is still unmeasured (no omc).

2026-09-10, after a whole array of connectors is connected element by element

The power converters connect two arrays of connectors at once -- connect(internalEnable, booleanReplicator.y), a replicator's outputs to an enable line, both arrays of the same length -- rather than writing the loop. A connect endpoint was resolved to one connector, so a whole array was refused, with the advice to connect the elements. It now is: when both sides name a one-dimensional array of connectors of the same length, the connect is read as the element-wise connect it stands for, x[i] to y[i] for every i. A plain connect is untouched, and two arrays of different lengths, or an array joined to a single connector, are refused as the size mismatch they are.

The refusal leaves twenty models. They do not reach a new stage -- each meets the next wall at once, an enumeration literal used as a name, a connect endpoint the resolver still does not see -- so the stage totals hold. No model regressed.

stage models of 430
parsed 415 96.5 %
flattened 59 13.7 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are a connect endpoint not seen as a connector (parse), the Clock builtin (62), an array shape unknown at flatten (24 and 17), a subscript on an inner component in parse, and an if-equation condition unknown at flatten (14). Agreement is still unmeasured (no omc).

2026-09-10, after an assignment to an array element parses

Modelica.Blocks.Math and Modelica.Blocks.Routing did not parse: the FFT block writes buf[iTick] := u in an algorithm, the routing block k[i] = 0 in a when, and the statement parser read the target as a bare name and then expected an assignment where the subscript's [ was. A file that does not parse takes its dependents with it -- a model that names any class in Blocks.Math, however small, failed at the lookup -- so this one refusal stood in front of forty-seven example models.

The assignment target now carries a subscript, the way the last name of any reference does, and the two files parse. Writing a single element of a scalarised array (often at a run-time index, as the FFT does) is not carried through the rest of the pipeline yet, so a model that actually flattens such a block is refused there, by the element it names, rather than at the file that defines it.

The dependency refusal leaves forty-seven models. They do not reach a new stage -- each meets the next wall at once, an array shape unknown at flatten or an if-equation condition it cannot evaluate, the counts of which rise as this one empties -- so the stage totals are unchanged. No model regressed.

stage models of 430
parsed 415 96.5 %
flattened 59 13.7 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are a connect endpoint not seen as a connector (68 in parse), the Clock builtin (62), an array shape unknown at flatten (24 and 17), a subscript on an inner component in parse, and an if-equation condition unknown at flatten (14). Agreement is still unmeasured (no omc).

2026-09-10, after index reduction is capped at a real model's depth

Not a reachability change, a measurement-speed one, and a better diagnosis. The three DC machine examples that began to flatten last change turned out to be over-determined once flattened -- five more equations than unknowns, a redundancy in the connected machine our flattener does not yet resolve. Index reduction differentiates such a system looking for the equation that would balance it and never finds one, so it differentiated the same chain nineteen times, each derivative doubling an already-large expression until it reached tens of megabytes. The run took minutes and ended in a timeout.

Index reduction now stops when a chain has been differentiated past twelve, far beyond any real model's index (a constrained multibody model, the deepest, is three), and reports the same over-determined diagnosis the equation-count budget already gave -- but at once, naming the equation, before the expressions explode. The three machines now fail in under a second with a message that points at the redundant equation (dcpm.inertiaRotor.phi = loadInertia.phi), the whole moon run :coverage is back to fifteen seconds from three minutes, and no well-posed model is touched. The stage counts are unchanged.

stage models of 430
parsed 415 96.5 %
flattened 59 13.7 %
causalized 23 5.3 %
simulated 21 4.9 %

2026-09-10, after a structural if-equation waits for a parameter a later sibling supplies

The largest flatten wall was a structural if-equation whose condition names a parameter not yet known. The machine friction is written if frictionParameters.PRef <= 0 then tau = 0 else tau = ..., and frictionParameters is set from a data record declared after the machine in the example, so the condition could not be evaluated where the if sits. Such an if is now held and resolved in a final pass, once the whole model's parameters have settled and the condition is a number; a condition that still does not resolve on that pass is the genuine run-time one (a continuous variable, if pipe.volumeFlow ...) and is refused as before.

The if-equation-condition wall falls from forty to eight (the eight that remain are the continuous conditions, which want a hybrid when, not this). Three DC machine examples flatten end to end (DCPM_Start, DCPM_QuasiStatic, DCPM_withLosses), so flattened moves 56 to 59. Their causalize does not finish inside the sixty-second budget, though: a permanent-magnet machine is a few hundred equations and the current causalizer is too slow on one that size, a new frontier this exposes rather than a regression (they failed earlier, at flatten, before). No model regressed, verified by a per-stage set difference.

stage models of 430
parsed 415 96.5 %
flattened 59 13.7 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are the Clock builtin (62), a subscript on an inner component in parse (51), a connect endpoint not seen as a connector (48), an array-shaped declaration met in parse (44 and 37), and an array shape unknown at flatten (21). Agreement is still unmeasured (no omc).

2026-09-10, after a fixed attribute may name a boolean parameter

The wall the medium constructor exposed. The fluid models write their outlet temperature as T(start = T0, fixed = T0fixed), with parameter Boolean T0fixed = false a switch the modeller can flip, rather than spelling fixed = false at the declaration. The fixed attribute was required to be a boolean literal, so a parameter there was refused. It is now evaluated against the parameter values gathered so far, the way a condition or a dimension is, and the parameter was declared before the component, so its value is already in hand; a fixed that resolves to a number rather than a boolean is still the error it was.

The ten fluid models leave this refusal and meet the next one, an if-equation condition the flattener cannot yet evaluate (the count there rises from 29 to 38). So the stage counts hold at 56 flattened; this is a wall cleared inside flatten rather than a model carried to the end of it. No model regressed.

stage models of 430
parsed 415 96.5 %
flattened 56 13.0 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are the Clock builtin (62), a subscript on an inner component in parse (51), a connect endpoint not seen as a connector (48), an array-shaped declaration met in parse (44 and 37), and an if-equation condition unknown at flatten (38, now the largest flatten wall). Agreement is still unmeasured (no omc).

2026-09-10, after a record constructor binding is read as a modification

The follow-up to the whole-record binding, the case it left refused. A record component may be given a constructor rather than a reference: material = BaseData() for the record's own defaults, R(a = 5) to set a field. This is read the same way now -- each named or positional argument becomes that member's modification, an empty constructor leaves every member at the record's default -- so a field the constructor does not name (a final, most often) keeps its own formula rather than being copied, which is the cleaner reading. A constructor of a different record is a redeclaration, a change of type rather than a plain binding, and stays refused as one; the flux-tube examples that pick a specific magnetic material this way wait for that.

This one moves the tally: three flux-tube examples flatten end to end (QuadraticCoreAirgap, ToroidalCoreAirgap, ToroidalCoreQuadraticCrossSection), whose cores default their material with an empty constructor. The fluid models whose medium is Medium() flatten past that binding too, then meet a fixed attribute that has to be a boolean literal, a new frontier of ten. No model regressed.

stage models of 430
parsed 415 96.5 %
flattened 56 13.0 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are the Clock builtin (62), a subscript on an inner component in parse (51), a connect endpoint not seen as a connector (48), an array-shaped declaration met in parse (44 and 37), and an if-equation condition unknown at flatten (29). The record-constructor redeclaration (10) and the fixed literal (10) are the next two flatten walls. Agreement is still unmeasured (no omc).

2026-09-10, after a whole-record binding is read member by member

The machine models bind one parameter record to another rather than repeating every field: frictionParameters = dcpmData.frictionParameters. A composite component given a value was refused outright, and this was the single largest flatten wall, 70 models. A record standing in for another record means each of its members equals the matching member of the other, so the binding is now read that way: the value arrived already prefixed into the flat namespace, and each member's copy is written by appending the member's name to it. A field derived from the others (a final in the record) comes out consistent because the fields it reads were copied too. Only a reference to another record is expanded; a value of any other shape (a record constructor, a medium) has no name to read the members from and stays refused, now with a message that says so.

The 70-model refusal leaves the top of the list. About fifty-six of those models -- the DC and induction machines, which write the binding as a reference -- flatten past it and meet the next wall at once: a connect endpoint the resolver does not see as a connector, an array shape unknown at flatten, an if-equation condition it cannot yet evaluate. So the stage counts hold at 53 flattened and 21 simulated, the frontier moving without the tally, the same shape the connect-in-for change had. The twenty-two that remain bind a record constructor or a replaceable medium, the natural next case.

stage models of 430
parsed 415 96.5 %
flattened 53 12.3 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are now the Clock builtin (62), a subscript on an inner component in parse (51), a connect endpoint not seen as a connector (48), an array-shaped declaration met in parse as expected '=' (44) or expected ';' (37), an if-equation condition unknown at flatten (29), and a composite bound to something other than a name (22, the follow-up to this change). Agreement is still unmeasured (no omc).

2026-09-10, after a connect inside a for-loop is unrolled

The polyphase library wires a plug's pins in a loop: for j in 1:m loop connect(plug_p.pin[j], pin_n); end for;. Unrolling the loop substitutes the index everywhere it appears, but the connect was refused outright rather than substituted -- the reasoning that a connect names components and a loop index cannot be one of them missed that the index rides on the endpoints, which are indexed. The index is now substituted into each endpoint, so the loop comes out one connect per element, which the endpoint resolver reads like any other.

The refusal leaves the work list -- 23 models carried it, the polyphase machines and rectifiers -- but the stage counts hold at 53 flattened and 21 simulated. The models that clear this wall meet the next one at once: a connect endpoint the resolver does not yet see as a connector, the array-of-connectors subtlety these same models lean on. So the frontier moves without the tally.

stage models of 430
parsed 415 96.5 %
flattened 53 12.3 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are a composite component with a value (70), the Clock builtin (62), a subscript on an inner component in parse (42, now the largest), an array shape not known at flatten (29), and a connect endpoint not seen as a connector (17). Agreement is still unmeasured (no omc).

2026-09-10, after a conditional component gated by an extends-set parameter resolves

A transistor puts its heat port behind a condition it inherits: extends ConditionalHeatPort(useHeatPort = useTemperatureDependency). The gating parameter is set, through the extends, to another parameter of the class, and that one is declared after it, so its value was not in hand when the parameter was instantiated -- the condition on the heat port then failed with "nothing in the environment answers '...useHeatPort'". A parameter whose binding named another was only recorded when the other happened to come first. The unconditional members are now instantiated first and their values resolved among themselves to a fixed point, before the conditions are evaluated, so the heat port is kept or dropped by what the sibling settles on.

Flattening climbs from 50 to 53 of 430 example models, and the "condition must be known" refusal loses its ten useHeatPort cases -- the analog examples with a transistor or a diode that carries a heat port. Three flatten through; the rest meet a later wall (an initial-equation form among them). Causalize and simulate hold at 23 and 21.

stage models of 430
parsed 415 96.5 %
flattened 53 12.3 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (down to 21). Agreement is still unmeasured.

2026-09-10, after a boolean latch that reads its own pre value is isolated

An on-off controller reads its previous state: on = pre(on) and u < hi or u < lo. pre(on) is the value at the last event, a known in the current phase, not a reference to the current on -- so on stands alone on the left and its boolean right-hand side is the answer, read off without a derivative (a boolean or has none). The check for whether the unknown stood alone counted the pre(on) as on, so it took the linear-solve path instead and causalization fell over. It ignores a pre now, so the isolated shape catches the equation, as it already does for an ordinary hot = x > 0.5.

Causalize climbs from 22 to 23 of 430 example models: MSL's HeatTransfer...ControlledTemperature, whose OnOffController is this latch, gets past the fall-over (it meets an initial-equation wall after). Reachable simulation holds at 21.

stage models of 430
parsed 415 96.5 %
flattened 50 11.6 %
causalized 23 5.3 %
simulated 21 4.9 %

The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).

2026-09-10, after a package constant in a function body resolves

Modelica.Units.Conversions.to_degC reads Modelica.Constants.T_zero in its body and is inlined wherever a model converts a temperature. The inlined body was prefixed but never took the rewrite that turns a package constant into its value -- the rewrite an equation gets -- so the name survived as a reference that flattening then could not resolve, and every model using the conversion failed with "unknown name 'Modelica.Constants.T_zero'". The body is now resolved in the function's own scope, so a package constant or an enumeration literal it reads becomes its value before prefixing sees it.

Flattening climbs from 47 to 50 of 430 example models: the electrical and thermal examples that convert a temperature clear the wall. Reachable simulation holds at 21 -- the newly flattening models meet a later wall, a derivative of a boolean or among them -- so causalize edges to 22 while simulate stays.

stage models of 430
parsed 415 96.5 %
flattened 50 11.6 %
causalized 22 5.1 %
simulated 21 4.9 %

The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).

2026-09-10, after a homotopy written with named arguments resolves

homotopy(actual = ..., simplified = ...) is how the standard library's ideal op-amp writes the operator, and every consumer of the call read the actual off the first positional argument -- so with the arguments named and none positional, the differentiator indexed an empty tuple and the flatten stage fell over with a bare "tuple index out of range", an internal error rather than a refusal. The names are resolved to the positional homotopy(actual, simplified) at flatten now, once, so the derivative, the emitter and the evaluator each read it the way they always did.

Reachable simulation climbs from 19 to 21 of 430 example models: OpAmps.Multivibrator and SignalGenerator, which build on the ideal op-amp, run end to end. Causalize and simulate stay level with each other at 21, every model that causalizes also simulates.

stage models of 430
parsed 415 96.5 %
flattened 47 10.9 %
causalized 21 4.9 %
simulated 21 4.9 %

The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).

2026-09-10, after the multiple-import list parses

import Modelica.Blocks.Types.{ExternalCombiTable1D, Extrapolation, Smoothness} brings several names from one package in a single clause, each the same as writing the qualified import three times. The standard library's rotational components -- Clutch, LossyGear and their neighbours -- open with two such clauses, and the dotted-name reader consumed the dot before the brace and then choked on it, so those files did not parse and every model that depends on them stopped there. The clause is read a segment at a time now, and a {...} list becomes one import per name, so nothing below the parser meets a fourth form.

The refusal leaves the work list -- eleven models carried it -- but the stage counts hold at 47 flattened and 19 simulated: the rotational examples that now get past their dependencies meet the next wall, an array whose shape is not known at flatten (mu_pos, lossTable), so the frontier moves without the tally.

stage models of 430
parsed 415 96.5 %
flattened 47 10.9 %
causalized 19 4.4 %
simulated 19 4.4 %

The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).

2026-09-10, after a qualified call to a builtin reads as the builtin

Modelica.Math.sin(x) is the builtin sin under its library path, and the standard library reaches the maths functions this way where a call is not inlined. Both the ends of the toolchain that consume a call knew sin but not the qualified name: the emitter refused it, "no translation for the function 'Modelica.Math.sin'", and the differentiator had "no derivative rule" for it. Each now reads a qualified call as the builtin -- the last segment, and only when it is a name that stage already has, so an unknown call still fails by its full name.

Reachable simulation climbs from 14 to 19 of 430 example models: the rotational and other examples that call a maths function qualified were causalizing and then failing at emit, so this closes the gap between causalize and simulate -- both now stand at 19, every model that causalizes also simulates.

stage models of 430
parsed 415 96.5 %
flattened 47 10.9 %
causalized 19 4.4 %
simulated 19 4.4 %

The leaders are unchanged, all before causalize: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).

2026-09-10, after the same element inherited through two paths is merged

The other half of the "twice" refusal: a name that reaches a class through two extends paths. MSL's battery CellData extends both a base record and an icon record, and each declares an identical constant String CellType, so the name arrives twice -- the same element, which the tools merge, but flattening refused it, "CellData inherits 'CellType' twice". The inherited-member loop in effective now compares the two the same way the re-statement fix compares a re-declaration: identical (but for a description or a placement) is the one element, kept once; a difference is still a clash. A modification the second extends writes on the name still applies, on top of the member already placed.

That empties the "twice" refusal from the work list -- 12 two measurements ago, 3 after the re-statement fix, 0 now. The stage counts hold at 47 flattened and 14 simulated: the battery examples that clear this wall meet the next one, an array whose shape is not known at flatten (OCV_SOC[:,2]), so the frontier moves without the tally.

stage models of 430
parsed 415 96.5 %
flattened 47 10.9 %
causalized 19 4.4 %
simulated 14 3.3 %

The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).

2026-09-10, after an identical re-statement of an inherited component is accepted

A derived class may declare a component its base already declares, identically: MSL's Electrical.QuasiStatic...Interfaces.TwoPin declares SI.AngularVelocity omega, which its base TwoPinElementary already has, and the power-converter HBridge does the same with fire_p. That is the same variable written twice, not a name clash, and the tools accept it. Flattening refused it, "declares 'omega' twice", because a component whose name matched an inherited member was rejected outright. It now compares the two declarations: identical but for a description or a diagram placement is the same element, kept once; a declaration that actually differs (a different type) is still a clash.

The "declares twice" refusals fall from 12 to 3 -- the 3 that remain are the diamond case, a name inherited through two extends paths (inherits 'CellType' twice), which is a separate merge. The stage counts hold at 47 flattened and 14 simulated: the models that clear this wall (the quasi-static resonance examples, the H-bridge drives) meet the next one right after -- a connect to a signal with two sources, a subscript on an inner component in Complex, a condition not known at flatten -- so the frontier moves without the tally moving.

stage models of 430
parsed 415 96.5 %
flattened 47 10.9 %
causalized 19 4.4 %
simulated 14 3.3 %

The leaders are unchanged: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).

2026-09-09, after an enumeration literal in an if-equation condition resolves

The condition of a structural if-equation was prefixed but not put through the rewrite that turns an enumeration literal into its number. The op-amp and limiter blocks the analog library builds on write if homotopyType == Modelica.Blocks.Types.LimiterHomotopy.NoHomotopy then, a kept structural if (its guard strict defaults to true), and the literal on the right stayed a name that flattening then could not resolve, so the model failed with "unknown name". A top-level equation and a when condition already took the full rewrite; the if-equation condition was the one place that took only a bare prefix. The rewrite is now one method, shared by all three, so the literal becomes its ordinal before the condition is evaluated.

Flattening climbs from 31 to 47 of 430 example models, and reachable simulation from eleven to fourteen: the op-amp examples (OpAmps.Adder, Comparator, and their neighbours) and the models that instantiate a limiter clear the wall together. The "unknown name" line, which led the flatten work list two measurements ago, is off the top of it entirely.

stage models of 430
parsed 415 96.5 %
flattened 47 10.9 %
causalized 19 4.4 %
simulated 14 3.3 %

The leaders are now a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), a subscript on an inner component and a type alias to an array type in parse (32 each), and a condition not known at flatten (31). Agreement is still unmeasured (no omc).

2026-09-09, after a package constant resolves unqualified and across packages

The Modelica.Constants values the library leans on stopped a run at flatten. Two forms were missing. A bare constant, brought into a model by import Modelica.Constants.pi; or import Modelica.Constants.*; or declared in the enclosing package, was read as a component that did not exist and refused with "unknown name" (commit for 0.0.176). A cross-package constant, one whose own value names a constant in another package -- Modelica.Constants.eps = ModelicaServices.Machine.eps is the case that mattered, and it accounted for 36 of the models on the "unknown name" line -- resolved its qualified reference no further than the package it named, so eps never became its number (this change, 0.0.177). The evaluation now follows a qualified constant reference into the package it names, bounded by a hop count so a library whose packages name each other in a ring fails cleanly instead of looping; a same-package chain is still bounded by name.

Flattening climbs from 12 to 31 across the two fixes (17 after the bare form, 31 after the cross-package one); reachable simulation moves from eight to eleven. Most of the fourteen models the cross-package fix newly flattens stop a stage later -- an electrical example that reads eps also carries a connect to a component with an unknown array shape -- so causalize and simulate move by one each while flatten moves by fourteen. The "unknown name" line falls from 55 to 24; what remains there is led by an enumeration literal of a library type used as a default (LimiterHomotopy, sixteen models), which is a separate lookup.

stage models of 430
parsed 415 96.5 %
flattened 31 7.2 %
causalized 16 3.7 %
simulated 11 2.6 %

The work list's leaders are now all flatten walls that are not about names: a composite component with a value (70), the Clock builtin (62), an array shape not known at flatten (52), and a condition not known at flatten (31). Agreement is still unmeasured (no omc on this machine).

2026-09-09, after an if inside a when body parses

A when body is a run of statements and may hold an if or a for; Blocks.Math's extremum blocks latch if u < pre(y_min) then t_min = time; else ... inside a when, and the standard library's samplers do the same. The when-body parser read only simple assignments, so the compound form was refused at parse. It reads the algorithm statement forms now, and a plain assignment body is unchanged.

The stage counts do not move (parsed holds 415 / 430): the files that carry this construct meet another one right after (Blocks.Math an array-valued modification, the Clocked samplers the Clock builtin), and flattening refuses an if in a when by name -- a conditional in the discrete-event pipeline is separate work. What moved is the parse frontier: the if-in-when refusal is gone from the work list, and IfStatement inside a 'when' is not supported yet is now the honest message, at flatten, for the models that reach it.

stage models of 430
parsed 415 96.5 %
flattened 12 2.8 %
causalized 10 2.3 %
simulated 8 1.9 %

The largest blockers are unchanged: a composite component with a value (66), an array shape not known at flatten (52), unqualified constants (50), the Clock builtin (49), and, in parse, a type alias to an array type and a subscript on an inner component. Agreement is still unmeasured.

2026-09-09, after quoted identifiers and the bare operator class

Two constructs the operator-overloading half of the library needs, both on Complex.mo and the files that import it. A quoted identifier, '+' or 'a b', is a name spelled with an operator or a space; the lexer reads it now, keeping the quotes so it is its own name and round-trips. A bare operator class, operator '+' ... end '+', is an overload group named by the operator it overloads; it reads as a package, a namespace it is never instantiated from, so the file loads. The overloading itself is still refused where an operator is applied.

The unexpected character ' refusal is gone from the work list, and one more example model parses end to end (parsed 414 -> 415). The Complex.mo chain does not clear yet: it meets its third construct, a subscript on an inner component (c1[i].re), which the 18 ComplexBlocks models now share as their blocker.

stage models of 430
parsed 415 96.5 %
flattened 12 2.8 %
causalized 10 2.3 %
simulated 8 1.9 %

a subscript on an inner component (30) has become the largest parse blocker, ahead of a type alias to an array type (type QuaternionBase = Real[4], part of the 32 [ refusals). The flatten walls are unchanged: a composite component with a value (66), an array shape not known at flatten (52), unqualified constants (50). Agreement is still unmeasured (no omc).

2026-09-09, after an array dimension on the type

FixedHeatFlow[n] heat puts the array dimension on the type rather than the name, which is the same declaration as FixedHeatFlow heat[n] and the Fluid examples write it that way. The clause reads a dimension after the type name now, shared by every name in it; a name's own dimensions come first, so Real[3] x[2] is x[2, 3], per the specification. The flattener already expands an array of components, so nothing below the parser changed.

Modelica.Fluid.Examples.BranchingDynamicPipes and its neighbours parse now (parsed 412 -> 414). Reachability holds at eight. Two of the three [ refusals remain and are separate constructs: a type alias to an array type (type QuaternionBase = Real[4]) and a subscripted assignment target in an algorithm (a[1] := 1).

stage models of 430
parsed 414 96.3 %
flattened 12 2.8 %
causalized 10 2.3 %
simulated 8 1.9 %

The flatten walls that lead the work list are unchanged: a composite component that carries a value (66), an array shape not known at flatten time (52), and unqualified constants in an equation (pi, eps, 50). Agreement is still unmeasured (no omc).

2026-09-09, after pure/impure and external functions parse

Two function-declaration constructs, both on the utility libraries, both cleared from the work list. pure function and impure function carry a purity hint the tool does not act on, so the prefix is accepted and dropped. An external "C" [ref =] call [annotation]; body is the whole of a function implemented in another language; it cannot run here, so it is read and dropped, and a function that reaches the simulator this way is refused where a call would inline it (it has no algorithm), not at parse.

Modelica.Utilities (Streams, System, Internal), Modelica.Blocks.Tables and Modelica.Math.Random are written this way and imported widely, so refusing them blocked a slice of the library. Six more files parse (2526 files, 105 → 99 refused), and one more example model parses end to end (parsed 411 → 412). The models behind the two constructs advance to the same flatten walls the electrical and machine libraries already lead with, so reachability holds at eight:

stage models of 430
parsed 412 95.8 %
flattened 12 2.8 %
causalized 10 2.3 %
simulated 8 1.9 %

The work list is now flatten work almost top to bottom: a composite component that carries a value (66), an array shape not known at flatten time (52), an unqualified name in an equation (pi, eps, from the Constants the utility files pull in, 50 + rarer), and the Clock builtin (49). Strings.mo, Random.mo and Files.mo still trip on a [ construct (an array-valued modification) and so did not clear. Agreement is still unmeasured (no omc).

2026-09-09, after a when tuple equation reads =

Eight models simulate now, up from four. A when in an equation section takes equations, and a multi-output call is an equation written (a, b) = f(x). The parser reads a when body as statements and its tuple case required :=, so the equation spelling was refused. The one file that trips on it is Modelica.Blocks.Interfaces (its PartialNoise draws a random pair that way), and that file is imported across the library, so refusing it blocked well over a hundred example models at the point they looked it up. Either operator is accepted now.

stage models of 430
parsed 411 95.6 %
flattened 12 2.8 %
causalized 10 2.3 %
simulated 8 1.9 %

The four new reachable models are Mechanics again -- Rotational.Examples.ElasticBearing and Translational.Examples.{Accelerate, SignConvention, WhyArrows} -- which use Blocks sources and sensors and so needed that file to load. The parse frontier has moved off the Blocks :=/=; what leads the work list now is genuine flatten work: a composite component that carries a value (66) and array shapes not known at flatten time (52). Agreement is still unmeasured (no omc).

2026-09-09, after StateSelect -- the first models reach simulation

Reachability moved off zero. Four example models parse, flatten, causalize and simulate end to end, the first that have:

  • Modelica.Mechanics.Rotational.Examples.RollingWheel
  • Modelica.Mechanics.Translational.Examples.Damper
  • Modelica.Mechanics.Translational.Examples.ElastoGap
  • Modelica.Mechanics.Translational.Examples.InitialConditions

StateSelect is a predefined enumeration the language provides but no file declares, a hint on which variables a tool should prefer as states. The standard library carries it two ways, and both blocked models: as an attribute, Real x(stateSelect = StateSelect.prefer), and as a parameter type, parameter StateSelect ss = StateSelect.default. The attribute joins the accepted set (the hint has no effect here, state selection is not implemented), and the enumeration is injected into the flattener as a prelude, so its name resolves and its members map to 1..5 like any other enumeration's. That was the last thing between these four Mechanics models and a trajectory: they set stateSelect on their flange states.

models stage refusal
139 parse := versus = inside a when statement (the Clocked library)
59 flatten a composite component cannot carry a value
50 flatten an array shape not known at flatten time
32 parse expected ';', found '[' (an array-valued modification)
19 parse impure on a function
17 parse a single-quoted identifier ('+', operator function names)
~110 parse/flatten the rest; coverage.json has every one

Agreement is still unmeasured (no omc on this machine), so what these four produce has not yet been checked against a reference. Reaching a trajectory is the first claim; agreeing with a tool people trust is the second, and it is the one worth measuring next. The parse frontier is now the Clocked library's :=/=, and the flatten frontier is genuine work: composite-component bindings and array equations.

2026-09-09, after the quantity attribute is accepted

quantity is a descriptive type attribute, like unit: type Voltage = Real(quantity= "ElectricPotential", unit="V"). The whole Modelica.Units.SI type set carries it, so the flattener refusing it as unknown blocked the electrical and machine libraries wholesale. It is accepted and ignored now, the way unit and displayUnit already were: one name added to the attribute set.

This was the dominant flatten wall the operator-record change had just exposed (~230 models on one attribute). It is gone from the work list, and the models behind it advanced deep into flattening, so the frontier is no longer one wall but many smaller ones, spread across the libraries that now load:

models stage refusal
119 parse := versus = inside a when statement (the Clocked library)
52 flatten a composite component cannot carry a value
48 flatten an array shape that is not known at flatten time
31 parse expected ';', found '[' (an array-valued modification)
29 flatten the StateSelect enumeration (nothing named 'StateSelect')
19 parse impure on a function
14 flatten the stateSelect attribute (the sibling of quantity, next)
~100 parse/flatten the rest; coverage.json has every one

Reachability is still 0 %, because the models now reach real semantic gaps rather than a single descriptive attribute. The next cheap win is the pair the electrical library keeps hitting: stateSelect, another accept-and-ignore attribute, and the StateSelect enumeration behind its value. After that the walls are genuine work: array equations and composite-component bindings.

2026-09-09, after the operator class prefix parses

operator record and operator function parse now, each as its plain kind (a record, a function), carried through and refused where the overloading would be applied. This was the top of the previous work list: Modelica.Units defines dozens of operator record aliases of Complex, its file would not parse, and every model that imports Modelica.Units.SI (nearly all of them) needs that file. The file parses now, and one fewer file refuses across the library (107 to 106).

The stage line did not move (parsed 411 / 430, reachability 0 %), because those 311 models were already counted as parsed: they reached parse on their own files and then died at flatten looking up a name in the unparseable Units.mo, attributed back to it. What moved is the work list, which is the half that matters. Operator overloading is gone from it. The 311 now flatten Modelica.Units.SI and get much further, to a new and now dominant wall: the quantity attribute.

models stage refusal
~230 flatten unknown attribute(s) ['quantity'] on a Real subtype (summed over its many names: Vrms, VNominal, f, ...)
48 parse := versus = inside a when statement
19 parse impure on a function
10 flatten the Clock builtin (nothing named 'Clock')
7 flatten a medium's composite component cannot carry a value
6 parse a subscript on an inner component
~40 parse/flatten the rest; coverage.json has every one

The quantity attribute is descriptive, like unit: type Voltage = Real(quantity= "ElectricPotential", unit="V"). The flattener rejects the unknown attribute rather than carrying it. Accepting (and ignoring) quantity the way unit is already ignored is the next construct worth building, and it is now what stands between the bulk of the electrical and machine libraries and their first flatten.

2026-09-09, first run on macOS, after the array and builtin work

The harness ran on this platform for the first time. RLIMIT_AS is a Linux guard that macOS refuses (current limit exceeds maximum limit), and the bare setrlimit was aborting the whole run before it started. The request is clamped to the current hard limit now, and a platform that refuses it outright runs without the guard rather than not running at all, with a note saying so.

The measurement, against MSL v4.1.0 with modelica 0.0.163: parsed 411 / 430 (95.6 %), one past the 2026-09-01 row. Reachability is still 0 %, and the work list keeps its shape: one construct stands between the library and its first flatten, and it is the same one.

models stage refusal
311 parse operator overloading (operator record, reached through import Modelica.Units.SI)
48 parse := versus = inside a when statement
19 parse impure on a function
10 flatten the Clock builtin (nothing named 'Clock')
7 flatten a medium's composite component cannot carry a value
6 parse a subscript on an inner component
29 parse/flatten twelve rarer refusals; coverage.json has every one

Operator overloading is three-quarters of the library on one construct, and it is a prerequisite, not a leaf: Modelica.Units.SI defines its unit types with operator record, and almost every model imports it. Until that parses, the 311 cannot advance to show what they would fail on next, so reachability cannot move off 0 % no matter what else lands. That is the next construct worth building for this number: the array, reduction and builtin work since 2026-09-01, which the fifteen-model example corpus exercises, does not touch it.

2026-09-01, after the base prefix on short classes

connector RealInput = input Real — the MSL's most-used connector. The causality is carried on the class (it can only come from a short definition, so the class round-trips through the short form), and flattening applies it to every instance whose declaration says nothing itself. The 58-model row cleared; ten Clocked models now fail in flatten, resolving the Clock builtin — the first models to get that far. Parsed holds at 410 / 430 (95.3 %); a :=-versus-= parse point (48) surfaced behind it.

2026-09-01, after bare call statements

isValidTable(table); — the statement twin of the bare call equation, with the same empty output list. Parsed 409 → 410 (95.3 %). The work list is three rows now: operator overloading (311), input on a short class definition (58 — the connector RealInput = input Real family), and impure (19).

2026-09-01, after comprehensions, in both spellings

{x[i] for i in 1:n} and the reduction call sum(x[i] for i in 1:n) are one Comprehension node; the printer always writes the braces, which parses back to the same value. Carried like Array, refused beside it at the point of use. Parsed 406 → 409 (95.1 %). The tail rows keep trading places; operator overloading (311) is now three-quarters of everything left.

2026-09-01, after ranges become expressions

parameter Integer columns[:] = 2:size(table, 2) — a range as a binding value. The range tail now reads in expression() itself, where the specification's simple_expression puts it, and the for header stopped being special. Parsed 405 → 406 (94.4 %); the Tables/Sources chain advanced to array comprehensions (37, next).

2026-09-01, after when in a statement position

The when-statement reuses the when-equation's node — branches of statements either way — and the section decides how an assignment inside it prints, extending the existing :=/= note. Bodies are full algorithm statements. The fold refuses it by name. The Sources/Tables chain advanced again, to expected ';', found ':' (32). Parsed holds at 405 / 430 (94.2 %).

2026-09-01, after the statement twins

assert(...) as a statement and (a, b) := f(x) — the statement twins of the equation forms that already existed — parse, round-trip, and are refused by name where a model's section folds into equations. The Blocks.Sources chain advanced one more construct, to when inside an algorithm section (31 models, next). Parsed holds at 405 / 430 (94.2 %).

2026-09-01, after initial algorithm folds

initial algorithm is to initial equation what algorithm is to equation, and it now parses into Class.initial_algorithms and folds into the initial equations during flattening. The 32 models blocked on the section header advanced one construct deeper (their chain now meets expected '=', found '(', 34 models — next). Parsed holds at 405 / 430 (94.2 %): these were dependency cascades, and the frontier inside them moved.

2026-09-01, after slices read where the grammar expects them

x[1:3] and A[:, 1] parse: a range was already readable — but only in the header of a for, and a subscript is the other place the grammar expects one. The bare : gets its own node (Colon), because it can only ever be the whole subscript. Both are carried the way Array is and refused beside it at the point of use. Parsed rose 359 → 405 (94.2 %). The remaining list is operator overloading (305 — now 71 % of the library on one construct) plus the function-declaration rows (algorithm 32, input 25, impure 19) that are its own prerequisites, and tails of a few models each.

2026-09-01, after final on a component clause

final parameter Real THD1 = V3/V1 — the element prefix, accepted and discarded the way the parser's four other final sites already do: finality forbids later modification, and nothing downstream modifies further yet. One branch in the prefix loop; parsed 345 → 359 (83.5 %). The remaining work list now reads as one story: the function declaration chain (algorithm sections 32, input 25, impure 19) feeding into operator overloading (258), plus array slices (51 + 9).

2026-09-01, after connector-array elements wire

connect(plug.pin[2], x) is implemented, not merely parsed: the subscript is structural the way a dimension is, evaluated during flattening, and the wire lands on the element the array expansion already declared. Connecting a whole array at once, a subscript anywhere but the last name, and a connect inside a for loop stay refused by name. Parsed rose 314 → 345 (80.2 %). The frontier is now two constructs: operator overloading (240) and whatever expected name, found keyword 'final' turns out to be (90, suddenly visible now that Modelica.Blocks.Tables loads this far).

models stage refusal
240 parse operator overloading
90 parse expected name, found keyword 'final'
47 parse array slices ([:])
10 parse expected name, found keyword 'input'
7 flatten a composite component cannot have a value (media bindings)
7 parse expected an expression, found operator ':'
29 parse/flatten rarer refusals; coverage.json has every model

2026-09-01, after bare call equations parse

Connections.root(a.b); is a function call as an equation — specification 8.3.1 with an empty output list. It now parses into CallEquation with no targets, and flattening refuses the form by name; the connection-graph semantics behind Connections.root and Connections.branch are a separate job. The QuasiStatic interface files load, and parsed jumped 277 → 314 (73.0 %). Reachability still 0 %; operator overloading now blocks over half the library on its own.

models stage refusal
226 parse operator overloading
83 parse arrays of connectors
44 parse array slices ([:])
35 parse expected name, found keyword 'final'
7 flatten a composite component cannot have a value (media bindings)
35 parse four rarer refusals; coverage.json has every model

2026-09-01, after assert equations carry

assert(condition, message); in an equation section parses and flattens into a new FlatModel.asserts — prefixed like every equation, never counted by causalization, because an assert binds no unknown. The simulator does not evaluate asserts yet; the node records that instead of the model losing its diagnostics. Modelica.Electrical.Analog. Basic.Resistor and its neighbours load now; the expected '=', found ';' row shrank 48 → 44, and what remains under that message is a different construct (Modelica.Electrical.QuasiStatic interfaces — likely the overconstrained-connector operators Connections.branch/root). Parsed holds at 277 / 430; the cleared models redistribute (operator overloading 190 → 193).

2026-09-01, after matrix expressions parse

[1, 2; 3, 4] becomes an ir.Matrix, following ir.Array's design exactly: it survives parsing and flattening so the file that holds it loads, and evaluate, derive and emit refuse it by name at the point of use. One matrix in one file had been keeping whole packages from loading; with those files in, parsed rose 262 → 277 (64.4 %). Reachability still 0 %.

models stage refusal
190 parse operator overloading
80 parse arrays of connectors
48 parse expected '=', found operator ';'
44 parse array slices ([:])
32 parse expected name, found keyword 'final'
8 flatten a composite component cannot have a value (media bindings)
28 parse six rarer refusals; coverage.json has every model

2026-09-01, after conditional components work

Sensor s if useSensor; is implemented, not merely accepted: the condition is evaluated during flattening — structural the way a dimension is — and a false one removes the component together with every connect() touching it. The 53 models this construct blocked moved to their next refusals; one now parses end-to-end (262 / 430, 60.9 %). Reachability still 0 %.

models stage refusal
183 parse operator overloading (operator record, mostly via Modelica.Units)
72 parse arrays of connectors
66 parse matrix expressions
41 parse array slices ([:])
32 parse expected name, found keyword 'final'
8 flatten a composite component cannot have a value (media bindings)
28 parse ten rarer refusals; coverage.json has every model

2026-08-31, after expandable connector parses

Same day, one construct later: the parser now reads expandable connector (instantiating one is still refused in flattening, where it would mean something). Reachability did not move — that is the expected shape of this curve for a while — but the work list did, which is the half that matters. The 258 models that all stopped at Modelica/Icons.mo:629 spread out to their next blockers:

models stage refusal
173 parse operator overloading — operator record, mostly reached through import Modelica.Units.SI (Units.mo:1090)
72 parse arrays of connectors
53 parse expected ';', found keyword 'if' — conditional components
40 parse expected ']', found operator ':' — array slices
32 parse expected name, found keyword 'final'
24 parse matrix expressions
8 flatten a composite component cannot have a value (media bindings)
28 parse eleven rarer refusals; coverage.json has every model

The stage table is unchanged from the first measurement below (parsed counts a model's own file and ancestor packages, and those did not move). The harness also learned to trace one more failure shape back to its cause: an import that does not resolve because the imported file refused.

2026-08-31, first measurement

Against MSL v4.1.0, with modelica 0.0.96. The library is 2157 .mo files; 484 of them refuse to parse. Of the 430 example models:

stage models of 430
parsed 261 60.7 %
flattened 0 0.0 %
causalized 0 0.0 %
simulated 0 0.0 %

Reachability is 0 % (Rumoca reports 28 % on the same library), and agreement is therefore vacuous: nothing reached the reference. "Parsed" counts a model whose own file and ancestor packages parse; every one of the 261 then dies looking up a dependency whose file refused, which the harness traces back to the construct that refused it.

The work list, first refusal per model — kept as taken; a measurement keeps its date:

models stage refusal
258 parse expected name, found keyword 'expandable' — one expandable connector at Modelica/Icons.mo:629, imported by nearly everything
44 parse expected ';', found keyword 'if' — conditional components (... if condition)
40 parse expected ']', found operator ':' — array slices
34 parse arrays of connectors
24 parse matrix expressions
17 parse expected name, found keyword 'final'
13 parse five rarer refusals; coverage.json has every model

One caution against optimism: the list shows each model's first refusal only. Parsing expandable connector would not make 258 models simulate — it would re-run the measurement and let each of them advance to its next blocker. That is what the number is for: rerun after every language-coverage change, and publish the new table with its date.