Skip to content

Resolve a block parameter in the mask workspaces around it - #23

Merged
ww-mw merged 1 commit into
mainfrom
mask-workspace
Sep 10, 2026
Merged

Resolve a block parameter in the mask workspaces around it#23
ww-mw merged 1 commit into
mainfrom
mask-workspace

Conversation

@ww-mw

@ww-mw ww-mw commented Sep 10, 2026

Copy link
Copy Markdown
Member

A masked subsystem's parameters are a resolution scope, and this package knew three: model workspace, dictionary chain, MAT-file. So a Gain = g1 inside a mask resolved to nothing and rendered as bare unlinked text — a name appearing nowhere in the workspace the reader was looking at — while the model workspace variable g1_param the mask actually reads collected no usage at all, because <Mask> was never read.

Both halves

  • a mask parameter's value is a parameter expression of the masked block, evaluated where that block sits: MulAdd gains a row reading g1 = g1_param, and g1_param gains the user MulAdd;
  • a mask parameter's name is a definition for the blocks inside: the inner Gain's g1 resolves to the enclosing mask, reported as kind: 'mask' with the masked block in maskBlock and a linkTarget of that block's key.

The rules, measured rather than derived

Simulink.findVars on a purpose-built model (test/parity/matlab/gen_mask.m), one arm per rule:

  1. a value is credited even when nothing inside reads the parameter;
  2. only expression-valued types count — edit, slider, dial, spinbox, min, max, and not checkbox, popup, combobox, listbox, radiobutton, unit, promote. A popup's value is the option text the user picked, and the fixture plants a workspace variable spelled exactly like one;
  3. a name shadows outwards — the model workspace's shadowed loses its usage, and the inner mask's o1 beats the outer's;
  4. a value resolves in the mask block's enclosing scope, never its own: Inner's i1 = o1 credits Outer's o1.

Evaluate="off" is a fifth, separate gate (probe_evaluate.m): the value is the literal string the user typed. Everything above was measured on R2025a and R2027a, whose answers are byte-identical.

The classic .mdl

That format spells a mask in three flat properties read together — MaskVariables names each parameter and indexes into MaskValueString, with the type at the same index of MaskStyleString. They used to fall through the ordinary parameter loop, so the classic flavour credited the masked block one row named MaskValueString carrying every value at once where the .slx of the same diagram credited nothing. Both now read the same masks, pinned by a new cross-format pair built from one diagram by one MATLAB.

Tests

  • test/maskWorkspace.test.ts — the parsers, the scope rule, the index, and the truth-derived parity read both ways round, so a variable MATLAB does not credit must have no users on our side either;
  • test/usageEngines.test.ts — the same answers pinned on both usage engines, which reach the mask by unrelated routes (resolveName against a summary, collectUsages against a ModelNode);
  • drift.mjs now regenerates the mask truth too, since these expectations are pure MATLAB behaviour and a release could change its mind silently.

npm run verify green: 4317 tests, typecheck, build, smoke, pack and leak checks.

A masked subsystem's parameters are a resolution scope, and this package knew
three: model workspace, dictionary chain, MAT-file. So a `Gain = g1` inside a
mask resolved to nothing and rendered as bare unlinked text — a name appearing
nowhere in the workspace the reader was looking at — while the model workspace
variable `g1_param` the mask actually reads collected no usage at all, because
`<Mask>` was never read.

Both halves are one fact seen from either end, and both are now implemented:

- a mask parameter's VALUE is a parameter expression of the MASKED BLOCK,
  evaluated where that block sits, so `MulAdd` gains a row reading
  `g1 = g1_param` and `g1_param` gains the user `MulAdd`;
- a mask parameter's NAME is a definition for the blocks INSIDE, so the inner
  `Gain`'s `g1` resolves to the enclosing mask — reported as `kind: 'mask'`
  with the masked block in `maskBlock`, and a `linkTarget` of that block's key.

Four rules came out of `Simulink.findVars`, not out of the file format, and each
is an arm of the new fixture (test/parity/matlab/gen_mask.m):

- a value is credited even when nothing inside reads the parameter;
- only expression-valued TYPES count — a popup's value is the option text the
  user picked, and the fixture plants a workspace variable spelled like one;
- a name shadows outwards, so the model workspace's `shadowed` loses its usage
  and the inner mask's `o1` beats the outer's;
- a value resolves in the mask block's ENCLOSING scope, never its own, so
  `Inner`'s `i1 = o1` credits `Outer`'s `o1`.

`Evaluate="off"` is a fifth and separate gate: the value is the literal string
the user typed. Both were measured on R2025a and R2027a, which agree exactly.

The classic `.mdl` spells all of this in three flat properties read together —
`MaskVariables` names each parameter and indexes into `MaskValueString`, with
the type at the same index of `MaskStyleString`. Those properties used to fall
through the ordinary parameter loop, so that flavour credited the masked block
one row named `MaskValueString` carrying every value at once, where the `.slx`
of the same diagram credited nothing. Both now read the same masks, pinned by a
new cross-format pair built from one diagram by one MATLAB.

Also: `isInsideBlockPath`, which the scope rule needs, because `/` is escaped
by doubling and `A//B/C` is a block inside a block named `a/b` — a plain
`startsWith` would let a mask on a sibling `A` swallow it.
@ww-mw
ww-mw merged commit 3c3f064 into main Sep 10, 2026
1 check passed
@ww-mw
ww-mw deleted the mask-workspace branch September 10, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant