# MSet Instruction Author 03 - Standard scale and substitutions

Status: **canonical-development**
Branch: **MSet 64-bit**
Current product: **MSet 2026 by GRAPHICO**
Format covered: **MSet 2026 native instruction format 2026.1 plus legacy-readable packages**

## Purpose

This is the third required module for an LLM that will read, explain, create, or modify an MSet 64-bit instruction.

After this module, the model should understand the current **standard numeric MSet scale** model, the runtime meaning of optional `<Scales>`, the `$S(...)` macro, and the exact scale-sensitive substitution rules currently implemented by MSet 2026.

This module deliberately covers **standard scale mode only**. Annotation Scale is not part of this learning module and must not be inferred from it. `$M(...)` also remains outside canonical MSet 2026 authoring guidance.

## Source authority

For this module, current MSet 2026 source and contract/runtime tests are authoritative. The MSet 10 manual and the runtime-verified `MSet_Polohopis` package are supporting evidence only where they agree with the current implementation.

Current source checkpoint used for reconciliation: MSet 2026 version `26.11.0.4`.

Primary implementation evidence:

- `MSet.Core/Scaling/ScaleDefinition.cs`
- `MSet.Core/Scaling/ScaleState.cs`
- `MSet.Core/Scaling/InstructionScaleDefinitionFactory.cs`
- `MSet.Core/Instructions/InstructionXmlParser.cs`
- `MSet.Core/Substitutions/SubstitutionEngine.cs`
- `MSet.Core/Substitutions/SubstitutionContext.cs`
- `MSet.Core/Substitutions/Rules/MacroRule.cs`
- `TextScaleRule.cs`
- `PatternScaleRule.cs`
- `CellScaleRule.cs`
- `TermScaleRule.cs`
- `LineStyleAutoScaleRule.cs`
- `MSet.MicroStation/Scaling/MicroStationScaleApplier.cs`

Do not import MSet 32-bit/V8i scale behavior into this module.

## 1. Standard MSet scale model

The current standard scale model has four important values:

```text
PrimaryScale
RecommendedScales
ScaleFactor       <- authoritative runtime value
CurrentScale      <- derived/displayed drawing scale
```

The defining relationship is:

```text
ScaleFactor = CurrentScale / PrimaryScale
```

and, when a stored factor is restored:

```text
CurrentScale = round(PrimaryScale * ScaleFactor)
```

Example with `PrimaryScale = 1000`:

```text
CurrentScale 1:500   -> ScaleFactor 0.5
CurrentScale 1:1000  -> ScaleFactor 1.0
CurrentScale 1:2000  -> ScaleFactor 2.0
CurrentScale 1:2500  -> ScaleFactor 2.5
```

`ScaleFactor` is the value used by standard scale substitutions.

### Important authoring consequence

`PrimaryScale` is not a decorative label. Changing it changes the relationship between a persisted/runtime `ScaleFactor` and the displayed `CurrentScale`.

For the same factor `2.0`:

```text
PrimaryScale 1000 -> CurrentScale 2000
PrimaryScale 200  -> CurrentScale 400
```

Do not change `PrimaryScale` during a routine XML cleanup unless that scale contract is intentionally being changed.

## 2. `<Scales>` is optional

The structural rules from module 01 now have a runtime meaning.

A valid custom declaration is for example:

```xml
<Scales PrimaryScale="200">50,100,200,400,500</Scales>
```

Current MSet 2026 rules:

- at most one `<Scales>` element is allowed,
- `PrimaryScale` is required when `<Scales>` is present,
- `PrimaryScale` must be a positive integer,
- listed scale values are comma-separated positive integers,
- `PrimaryScale` must occur in the declared values,
- declared value order is preserved,
- the list supplies **recommended/quick-pick scales**, not the complete allowed domain.

A user may enter any positive integer `CurrentScale`, even when it is not present in `RecommendedScales`.

For example, with a default definition, `750`, `1250` or `3000` are valid current scales even if they are not recommended quick picks.

### Missing `<Scales>`

If `<Scales>` is absent, MSet uses this current default definition:

```text
PrimaryScale = 1000
RecommendedScales =
50, 100, 200, 250, 500, 1000, 1440, 2000, 2500, 2880,
5000, 10000, 20000, 25000, 50000, 100000, 200000
```

The current runtime-verified `MSet_Polohopis` package intentionally contains no `<Scales>` and therefore uses this default definition.

Do not add a `<Scales>` element merely because it is absent.

## 3. Initial and persisted standard scale

A newly created `ScaleState` starts with:

```text
ScaleFactor = 1.0
CurrentScale = PrimaryScale
```

For DGN-associated work, MSet restores the persisted standard **ScaleFactor**, not a persisted `CurrentScale` value.

Missing, non-positive, non-finite or otherwise unusable persisted factors fall back to `1.0`.

The normal Scale dialog lets the user edit a positive integer `CurrentScale`; MSet derives the decimal factor from it. Recommended scales are only quick picks.

### Host-side scale application is not XML authoring

When standard scale is applied to MicroStation, the current host adapter sends, in this order:

```text
LINESTYLE SET SCALE <ScaleFactor>
AS=<ScaleFactor>
```

using invariant decimal formatting.

These host-side actions are part of MSet runtime scale application. Do not insert them into an instruction merely to imitate selection of a scale in the MSet UI.

## 4. Where substitutions happen

After command composition, the current substitution pipeline runs in this order:

```text
Normalize
-> MacroRule          ($S, $C, $X; $M remains unsupported guidance)
-> TextScaleRule
-> PatternScaleRule
-> CellScaleRule
-> TermScaleRule
-> LineStyleAutoScaleRule
-> split into key-ins
-> conservative final optimizer
-> dispatch
```

Module 02 remains authoritative for `$C`, `$X`, raw composition and separator normalization.

The same substitution engine is used when MSet executes lifecycle command text and Feature commands. However, `Instruction`/`Page` lifecycle scope remains separate from Feature inheritance.

## 5. `$S(number)` - explicit general scale substitution

In standard mode:

```text
$S(number) -> number * ScaleFactor
```

Example at `ScaleFactor = 2.0`:

```text
place cir radius $S(1.5)
```

becomes:

```text
place cir radius 3
```

The command spelling in this example follows the evidence-classified `place cir radius <value>` form from module 09. This section demonstrates `$S(...)` substitution only; it does not upgrade that parameterized circle form beyond its published historical/supporting evidence state or remove its current-host runtime-test requirement.

The current runtime uses invariant numeric parsing for `$S(...)` and strict processing for dispatched commands.

Canonical authoring rule:

- use a decimal point in `$S(...)`, for example `$S(1.5)`,
- do not write locale decimal comma such as `$S(1,5)`,
- if the numeric argument is invalid, strict processing fails rather than silently inventing a value.

### Current runtime evidence

The MSet 2026 standard-scale path has been runtime-verified end-to-end with a temporary `$S(1)` Feature on MicroStation 2026.

The current real `MSet_Polohopis` package also contains `$S(...)` uses such as:

```text
ig_svah $S(1000) ...
imbo import $S(1)
```

Those package entries prove current `$S(...)` usage. They do **not** make the external `ig_*` or `imbo` key-ins canonical Bentley commands.

## 6. Do not double-scale an automatically scaled parameter

`$S(...)` runs **before** the automatic scale-sensitive rules below.

Therefore a value can be multiplied twice if an author combines `$S(...)` with a parameter that MSet automatically scales.

At `ScaleFactor = 2`:

```text
th=$S(2)
```

first becomes:

```text
th=4
```

and then `TextScaleRule` produces:

```text
th=8
```

The same principle applies to other automatically scaled assignments such as `PS=`, `PD=`, explicit `AS=` and explicit `TS=`.

**Canonical editing rule:** use `$S(...)` for values that need explicit general scaling, but do not wrap a value in `$S(...)` when that value is already covered by a current automatic scale rule unless double scaling is deliberately intended and verified.

## 7. Text scale substitutions

Current `TextScaleRule` recognizes these assignments case-insensitively:

```text
tx=
th=
tw=
ls=
```

These are supported scale-sensitive assignment forms, not historical word-prefix abbreviations that should be mechanically expanded. Module 08 defines the canonical-command versus defined-shorthand authoring policy.

Their numeric value is multiplied by the current standard `ScaleFactor`.

At `ScaleFactor = 2`:

```text
th=2;tw=1.5;tx=2.5;ls=0.75;place text
```

becomes:

```text
th=4;tw=3;tx=5;ls=1.5;place text
```

In this rule, `ls=` means the text line-spacing parameter. Do not confuse it with line-style selection; line-style selection is handled through `lc=`.

The runtime accepts decimal comma in these numeric assignment values and writes scaled values with invariant decimal point. For new canonical authoring, prefer invariant decimal point.

### Real-package evidence

`MSet_Polohopis/texty.ent` contains reusable text fragments such as:

```text
textstyle active Arial_n;ls=0.75;tx=1.5;
```

These values are intended to pass through the current text scaling rule.

## 8. Pattern scale substitutions

Current `PatternScaleRule` recognizes semicolon-delimited tokens beginning with:

```text
PS=
PD=
```

case-insensitively.

Their values are multiplied by standard `ScaleFactor`, and processed output normalizes the key to uppercase `PS=` / `PD=`.

At `ScaleFactor = 2`:

```text
ps=2;Pd=1.25
```

becomes:

```text
PS=4;PD=2.5
```

Do not infer additional pattern keys that are not listed here.

## 9. Cell scale substitutions

Current cell scaling uses `AS=`.

Recognized cell-trigger prefixes are case-insensitive:

```text
pla cel
place cel
ac=
ar=
cm=
```

Because these are prefix matches, longer forms beginning with them are also recognized. In particular, the full current MicroStation forms `place cell` and `place cell relative` begin with the recognized `place cel` prefix and therefore enter the same MSet cell-scale rule as legacy `pla cel` / `pla cel rel`.

For new MSet 2026 authoring, module 08 therefore prefers the full forms `place cell` / `place cell relative`. Legacy spellings remain supported for backward compatibility.

### No explicit `AS=`

If the compound command has a cell trigger but no `AS=`, MSet inserts:

```text
AS=<ScaleFactor>
```

immediately after the first recognized cell-trigger token.

At `ScaleFactor = 2`:

```text
place cell relative;ac=TEST
```

becomes at the cell-scale stage:

```text
place cell relative;AS=2;ac=TEST
```

### Explicit `AS=`

If an explicit numeric `AS=` already exists, standard mode multiplies that authored value by `ScaleFactor` instead of inserting another scale token.

At `ScaleFactor = 2`:

```text
AS=1.5;place cell relative
```

becomes:

```text
AS=3;place cell relative
```

### Real-package evidence

`MSet_Polohopis` extensively uses inherited cell placement plus `ac=...`, for example a Group with the historically used `place cel rel` spelling and child Features with `ac=<cell name>`. The current substitution rule supplies standard cell scale for that pattern. Preserve such working historical source when merely analysing it; for newly authored MSet 2026 XML prefer `place cell relative`.

## 10. Terminator scale substitutions

Current terminator scaling uses `TS=`.

Recognized terminator-trigger prefixes are case-insensitive:

```text
pla ter
place ter
active terminator
lt=
```

### No explicit `TS=`

If a terminator trigger is present and no `TS=` exists, MSet inserts:

```text
TS=<ScaleFactor>
```

after the first recognized terminator trigger.

At `ScaleFactor = 2`:

```text
place terminator;lt=ARROW
```

becomes:

```text
place terminator;TS=2;lt=ARROW
```

### Explicit `TS=`

An explicit numeric `TS=` is multiplied by standard `ScaleFactor`.

Do not infer additional terminator syntax beyond the current recognized prefixes without separate key-in evidence.

## 11. Line-style auto scale

Current `LineStyleAutoScaleRule` reacts to a semicolon-delimited token beginning with:

```text
lc=
```

case-insensitively. `LC=` is therefore a supported MSet-sensitive assignment form. Do not replace it with a guessed full-word command merely for stylistic normalization; an alternative must be separately verified to trigger the same MSet behavior before it can be treated as equivalent.

It inserts:

```text
LINESTYLE SET SCALE <ScaleFactor>
```

immediately after that line-style selection unless a nearby line-style scale setter is already present.

At `ScaleFactor = 2`:

```text
lc=2;place smartline
```

becomes at this substitution stage:

```text
lc=2;LINESTYLE SET SCALE 2;place smartline
```

Important current-code details:

- `lc=` is the implemented selection trigger; do not broaden this rule to arbitrary `linestyle ...` commands without evidence,
- **the trigger applies to `lc=bylevel` as well as numeric/custom selections**,
- even when `ScaleFactor = 1`, the current rule may still insert `LINESTYLE SET SCALE 1`,
- this rule is distinct from `ls=` text line spacing.

### Runtime evidence - `lc=bylevel` baseline

GRAPHICO runtime testing on 2026-08-24 with MSet 2026 `26.11.0.4` confirmed the side effect in Command Inspector. With standard `ScaleFactor = 0.5`, a baseline containing:

```text
co=bylevel;lc=bylevel;wt=bylevel;lv=...
```

produced final key-ins including:

```text
lc=bylevel
LINESTYLE SET SCALE 0.5
```

Therefore, using `lc=bylevel` for deterministic symbology does **not** mean that standard scale processing is irrelevant. Omitting `<Scales>` also does not disable the scale model; the default scale definition still applies. Authoring/review must account for this interaction.

## 12. Final optimizer can remove earlier repeated settings

After substitution and splitting, current MSet 2026 applies a conservative **last-wins** optimizer only to this explicit whitelist:

```text
co=
lc=
wt=
lv=
LINESTYLE SET SCALE
```

For each recognized setting category, only its last occurrence survives. Unknown key-ins are not removed or reordered.

This matters when Group inheritance contains multiple `lc=` values and the line-style substitution inserts multiple scale setters.

Do not use the optimizer as an excuse to generate redundant commands. It is a runtime cleanup step, not a replacement for clear instruction authoring.

## 13. ScaleFactor 1.0 is not a textual no-op

Do not assume that `ScaleFactor = 1` means the final command text must be identical to the source.

At factor `1.0`:

- `$S(x)` resolves to the numeric value `x`,
- text numeric values remain numerically unchanged,
- pattern keys may be normalized to `PS=` / `PD=`,
- a cell trigger without explicit `AS=` may receive `AS=1`,
- a terminator trigger without explicit `TS=` may receive `TS=1`,
- `lc=` may receive `LINESTYLE SET SCALE 1`.

Compare semantics, not only raw string identity.

## 14. Safe authoring rules for standard scale

When modifying or generating a standard-scale MSet 2026 instruction:

- treat `ScaleFactor` as runtime state, not as a constant stored in the XML instruction,
- use `<Scales>` only to define `PrimaryScale` and recommended quick-pick scales,
- do not treat `RecommendedScales` as an allowed-values whitelist,
- preserve an existing `PrimaryScale` unless an intentional scale-contract change is requested,
- remember that removing `<Scales>` activates the default `PrimaryScale = 1000` definition,
- use `$S(number)` for explicit general numeric scaling,
- prefer invariant decimal point in new numeric authoring,
- do not double-scale values already covered by automatic rules,
- use only the exact automatic scale-sensitive tokens/triggers documented in this module,
- do not invent Bentley key-ins merely because MSet can scale their numeric parameters,
- distinguish source command, substituted command and optimized final key-ins,
- keep Annotation Scale outside this module.

## 15. What is deliberately not taught yet

Do not infer these from this module:

- Annotation Scale behavior,
- historical `$M(...)` control-macro semantics as current MSet 2026 guidance,
- validity/meaning of arbitrary Bentley, OpenRail, OpenRoads or external-application key-ins,
- complete safe-editing/error-recovery policy for modifying a user's whole package.

Those remain separate later knowledge.

## Completion check

Before proceeding to the next Instruction Author module, the model should be able to answer all of these correctly:

1. What is the standard scale formula? **`ScaleFactor = CurrentScale / PrimaryScale`.**
2. Are recommended scales the only allowed current scales? **No.**
3. What happens when `<Scales>` is absent? **Default PrimaryScale 1000 plus the current default recommended list are used.**
4. What is persisted/restored for standard DGN scale state? **ScaleFactor, not displayed CurrentScale.**
5. What does `$S(1.5)` do at factor 2? **It becomes `3`.**
6. Should `$S(...)` normally be nested inside `th=`, `PS=`, explicit `AS=` or explicit `TS=`? **No; those values are automatically scaled and would normally be multiplied again.**
7. Which text assignments are automatically scaled? **`tx=`, `th=`, `tw=`, `ls=`.**
8. Which pattern assignments are automatically scaled? **`PS=`, `PD=`.**
9. What happens to a cell trigger without explicit `AS=`? **MSet inserts `AS=<ScaleFactor>`.**
10. What happens to a terminator trigger without explicit `TS=`? **MSet inserts `TS=<ScaleFactor>`.**
11. What line-style selection token triggers automatic scale insertion? **`lc=`.**
12. Is `ls=` the line-style selector here? **No; it is handled as text line spacing.**
13. Is `ScaleFactor = 1` guaranteed to leave command text unchanged? **No.**
14. Does this module authorize invention of Bentley key-ins? **No.**
15. Does this module teach Annotation Scale? **No.**

Next planned module: `04-safe-editing-and-errors.md`.

### CONNECT-safe authoring note

The current substitution rules intentionally recognize historical abbreviated triggers such as `pla cel` and `pla ter` for compatibility. For new MSet 2026 authoring, module 08 prefers the established full forms `place cell`, `place cell relative` and `place terminator`. This normalization rule does **not** apply mechanically to compact assignments such as `TH=`, `TX=` or `LC=`: those are supported scale/substitution forms in their own right. Trigger compatibility, defined shorthand and authoring preference are separate concerns.
