Add DWG-native constraint compatibility design doc

Hakan (upstream maintainer) asked for the sketch-constraint system to
persist using the constraint object model already exposed in acadrust/
cadcodec (Assoc2dConstraintGroup, AssocConstraintNode), so a file saved
by OpenCADStudio carries constraints AutoCAD/BricsCAD recognize as
their own native objects, not just an opaque custom XRecord blob.

Covers:
- The full ConstraintKind -> AssocConstraintNodeData mapping table,
  grounded in build_constraint's actual refs shapes (the real contract,
  not the doc comments).
- The geometry-node mapping for SketchRef (line/circle/arc/point
  references) onto Line/BoundedLine/Circle/Arc/ImplicitPoint nodes.
- The AssocNetwork -> AssocAction -> Assoc2dConstraintGroup graph shape
  actually needed, not just a flat data swap.
- A proposed additive persistence strategy: write both our own
  OCS_SKETCH_CONSTRAINTS XRecord and the native graph, since our model
  carries things (DrivingValue::Named) the native graph can't yet
  losslessly express.
- Known gaps: Equal has no length/radius sub-kind split natively,
  several AutoCAD constraint types we don't model at all, Tangent
  involving an Arc (a pre-existing gap in the constraint system,
  unrelated to DWG compatibility).

Three of five open questions are resolved against Autodesk's own
primary sources rather than left as inference: the ObjectARX 2012 SDK's
own C++ headers (mirrored on GitHub, Autodesk's own copyright notice
grants free use/distribution -- it's the SDK Autodesk publishes for
third-party interop) and Autodesk's live public ObjectARX reference
site. Confirmed: the associative network's dictionary key
("ACAD_ASSOCNETWORK"), the AcDb::ImplicitPointType enum settling how
SketchRef's point markers map to constraint geometry nodes, and that
AssocVariable's evaluator field can be left empty (Autodesk's own doc:
"the default evaluator for current acad version will be used"). Two
questions remain and need a real write-then-open test rather than more
reading: whether AutoCAD tolerates a partially-populated graph, and
DWG version gating on fields not yet checked.

Suggested staged approach: prove out Horizontal/Vertical on a single
Line first (sidesteps the value-dependency wiring), verify it opens
cleanly in real AutoCAD/BricsCAD or a DWG inspector, before writing
more of the graph on an unverified foundation. Design only -- no
implementation yet.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Felix RiestraandClaude Sonnet 5 committed 2026-09-09 14:48:02 +02:00
1 parent 7cddf31bcb
commit 5d93476c01
1 file changed
+352
+352
View File
@@ -0,0 +1,352 @@
# DWG-Native Constraint Compatibility — Design
## 1. What this is, and why
Hakan (upstream maintainer) reviewed the sketch-constraint system
(`docs/parametric_system_design.md`) and asked for it to persist using the
constraint object model already exposed in `acadrust`/`cadcodec` (the pinned
CAD kernel), so a file saved by OpenCADStudio carries constraints AutoCAD or
BricsCAD would recognize as their own native objects — not just an opaque
custom blob only this app understands.
This is purely a **persistence-format** question, not a solver question.
`ocs_gcs` (the FreeCAD `planegcs` port: `crates/ocs_gcs/src/solvers/{bfgs,dogleg,lm}.rs`)
is the numerical engine that computes geometry from a constraint system —
that stays exactly as it is regardless of what this document decides. What
changes is only how `SketchConstraintSet` gets written to and read from a
`CadDocument`, in `src/scene/sketch_persist.rs`, currently a custom
`bincode`-in-XRecord blob keyed `OCS_SKETCH_CONSTRAINTS`.
## 2. What `acadrust` already provides (confirmed from source)
All paths below are relative to the pinned checkout
(`~/.cargo/git/checkouts/cadcodec-*/*/src/`, rev pinned in `Cargo.toml`).
- `objects/associative.rs` defines AutoCAD's real 2D geometric-constraint
object graph as plain Rust types, reachable generically through
`ObjectType::Associative(AssociativeObject { data: AssociativeData::ConstraintGroup(Assoc2dConstraintGroup), .. })`
— the same `document.objects: HashMap<Handle, ObjectType>` map
`sketch_persist.rs` already inserts `XRecord`/`Dictionary` entries into.
- Both DWG (`io/dwg/dwg_stream_{readers,writers}/object_{reader,writer}/associative.rs`,
~1240/~870 lines) and DXF (`io/dxf/{reader,writer}/section_{reader,writer}/associative.rs`)
have **complete read and write** implementations for every type below —
confirmed by reading the actual field-by-field (de)serialization code, not
inferred from the type definitions alone.
- `classes/mod.rs` registers the DXF class names every one of these needs
when written (`(DXF_NAME, "AcDbClassName", ...)` tuples):
`AcDbAssocDependency`, `AcDbAssocGeomDependency`, `AcDbAssocAction`,
`AcDbAssocNetwork`, `AcDbAssoc2dConstraintGroup`, `AcDbAssocVariable`,
`AcDbAssocPersSubentManager`.
### The graph shape
Not a flat list — a small object graph, matching AutoCAD's real associative
framework:
```
AssocNetwork (per block/space that owns constrained geometry)
└─ owned_actions: Vec<Handle> ─→ AssocAction
├─ action_body: Handle ─→ Assoc2dConstraintGroup
│ ├─ work_plane: [Vector3; 3]
│ └─ nodes: Vec<AssocConstraintNode>
│ (both the *constraint* nodes
│ and the *geometry* nodes live
│ in this one Vec — see §3/§4)
└─ dependencies: Vec<AssocActionDependency>
─→ AssocGeomDependency
├─ dependency: AssocDependency
│ └─ dependent_on: Handle
│ (the real Line/Circle/... entity)
└─ persistent_subent: AssocPersistentSubentId
```
`AssocConstraintNode { node_id: i32, status: u8, connections: Vec<i32>,
class_name: String, registry_flag: bool, data: AssocConstraintNodeData }` —
`node_id`/`connections` link nodes to each other *within* the group (a
constraint node's `owner_id` fields point at a geometry node's `node_id`);
`class_name` picks which `AssocConstraintNodeData` variant applies on
read (`read_constraint_node_data(reader, class_name)`,
`dwg_stream_readers/object_reader/associative.rs:663`).
### 2b. Primary sources beyond `cadcodec`'s own code
`cadcodec` ships no constraint-bearing test fixture and its own source only
shows *what bytes get read*, not *what they mean* semantically — §7 (now
mostly resolved) needed Autodesk's own interface documentation to answer.
Two real, first-party sources turned out to be publicly available with no
sample DWG file needed at all:
- **The ObjectARX 2012 SDK's own C++ headers**, mirrored on GitHub at
[Stalso/CadPlugin](https://github.com/Stalso/CadPlugin/tree/master/ObjectARX%202012/inc)
— Autodesk's own copyright header on each file explicitly grants "permission
to use, copy, modify, and distribute this software... for any purpose and
without fee", and this is the SDK Autodesk itself publishes specifically so
third parties can build interoperable tools — not leaked or decompiled
source. `AcDbAssoc2dConstraintGroup.h`, `AcDbAssocNetwork.h`,
`AcDbAssocManager.h`, `AcDbAssocVariable.h`, `AcConstrainedGeometry.h` were
all read directly; their class-level doc comments describe the exact
same object graph `cadcodec` implements, in Autodesk's own words.
- **Autodesk's live public ObjectARX reference**
(`help.autodesk.com/view/OARXMAC/2025/...`) — confirmed the
`AcDb::ImplicitPointType` enum's exact values and per-member descriptions
(used for §4's `point_type` mapping) and `AcDbAssocNetwork`'s
`getInstanceFromObject`/`getInstanceFromDatabase` dictionary-key default
(used for §7 question 1). No login or SDK download needed — the reference
guide is indexed and publicly browsable.
## 3. Constraint-kind mapping
Our `ConstraintKind` (`src/scene/sketch_constraints.rs`) against
`AssocConstraintNodeData` (`objects/associative.rs:885`). The "refs shape"
column is copied from `sketch_solve::build_constraint`'s actual `match`
(`src/scene/sketch_solve.rs:151-208`) — the ground truth for what each kind
expects, not the doc comment.
| `ConstraintKind` | refs shape (from `build_constraint`) | `AssocConstraintNodeData` | Notes |
|---|---|---|---|
| `Coincident` | `[point, point]` | `Geometrical` + `class_name = "ACPOINTCOINCIDENCECONSTRAINT"` | |
| `Horizontal` | `[whole_line]` | `Geometrical` + `"ACHORIZONTALCONSTRAINT"` | |
| `Vertical` | `[whole_line]` | `Geometrical` + `"ACVERTICALCONSTRAINT"` | |
| `Parallel` | `[whole_line, whole_line]` | `Parallel { datum_line_index: None, .. }` | dedicated variant, not `Geometrical` |
| `Perpendicular` | `[whole_line, whole_line]` | `Geometrical` + `"ACPERPENDICULARCONSTRAINT"` | |
| `Equal` (line case) | `[whole_line, whole_line]` | `Geometrical` + `"ACEQUALLENGTHCONSTRAINT"` | |
| `Equal` (circle/arc case) | `[whole_circle, whole_circle]` | `Geometrical` + `"ACEQUALRADIUSCONSTRAINT"` | **our `Equal` doesn't distinguish these — see §5** |
| `Distance` | `[point, point]` | `Distance { direction_type, distance: Option<Vector3>, value_dependency, dimension_dependency, .. }` | needs an `AssocVariable`/value dependency for the driving value — see §5 |
| `Angle` | `[whole_line, whole_line]` | `Angle { sector_type, value_dependency, dimension_dependency, .. }` | same value-dependency need |
| `Radius` | `[whole_circle]` | `RadiusDiameter { mode, value_dependency, dimension_dependency, .. }` | `mode` presumably distinguishes radius vs. diameter — we're always radius |
| `Tangent` | `[circle, circle]` or `[circle, line]`/`[line, circle]` — Line-Line and anything involving an `Arc` are explicitly unsupported today (`sketch_solve.rs:239-241`, `Arc` isn't a supported `EntityGeom` variant yet) | `Geometrical` + `"ACTANGENTCONSTRAINT"` | our own `Arc`-tangent gap predates this document, unrelated to DWG compatibility |
AutoCAD's `is_plain_geometrical_constraint` list also includes several kinds
we have no `ConstraintKind` for at all: `ACCENTERPOINTCONSTRAINT`,
`ACCOLINEARCONSTRAINT`, `ACCONCENTRICCONSTRAINT`, `ACEQUALCURVATURECONSTRAINT`,
`ACEQUALDISTANCECONSTRAINT`, `ACEQUALHELPPARAMETERCONSTRAINT`,
`ACFIXEDCONSTRAINT`, `ACMIDPOINTCONSTRAINT`, `ACNORMALCONSTRAINT`,
`ACPOINTCURVECONSTRAINT`, `ACSYMMETRICCONSTRAINT` — genuinely nothing to map
to yet, not a mapping gap (§5).
## 4. Geometry-node mapping
Every entity a constraint references also needs its own node in the same
`nodes: Vec<AssocConstraintNode>`, so constraint nodes have something to
point `owner_id` at, *and* a paired `AssocGeomDependency` (in the owning
`AssocAction`'s `dependencies`) so the graph — not just the constraint data
— actually references the real `Line`/`Circle`/... entity by `Handle`.
| Our reference | `AssocConstraintNodeData` geometry variant |
|---|---|
| `SketchRef::whole(line)` | `BoundedLine { point, direction, is_ray: false, start_point, end_point, .. }` — confirmed a *bounded* line is the right choice: `Line`/`BoundedLine`'s split exists because AutoCAD's own constrained-geometry set includes true infinite construction lines (`ACCONSTRAINEDCONSTRUCTIONLINE`/`ACCONSTRAINEDDATUMLINE`, `read_constraint_node_data`, `associative.rs:711-714`) as a *separate* case from an ordinary bounded segment; nothing we draw is an infinite line |
| `SketchRef::point(line, 0\|1)` | `ImplicitPoint { point_type: kStartImplicit(0)\|kEndImplicit(1), point_index: -1, curve_id: <the Line/BoundedLine node's node_id>, .. }` — **confirmed**, `AcDb::ImplicitPointType` enum, `kStartImplicit`/`kEndImplicit`/`kMidImplicit`/`kCenterImplicit`/`kDefineImplicit` (0-4), Autodesk's own live ObjectARX reference (§2b). `point_index` is `-1` for every case except `kDefineImplicit` (a spline control point, which we don't reference); `curve_id` is presumably the owning geometry node's own `node_id` (not yet cross-checked against a real file — the *values* are confirmed, this one wiring detail isn't) |
| `SketchRef::whole(circle)` | `Circle { center, normal, direction, radius, start_parameter: 0.0, end_parameter: TAU, .. }` |
| `SketchRef::whole(arc)` | `Arc { .. same fields .. , start_point, end_point }` |
| `SketchRef::center(circle\|arc)` | `ImplicitPoint { point_type: kCenterImplicit(3), point_index: -1, curve_id: <the Circle/Arc node's node_id>, .. }` — confirmed by the same enum (`kCenterImplicit`: "Center point of a circle, arc, ellipse or bounded ellipse") — **not** the separate `Point`/`ACCONSTRAINEDPOINT` variant, which the ObjectARX docs' own class hierarchy (§2b) show is for a *free-standing* constrained point, not one implicitly defined by another curve |
| `SketchRef::point(_, -2)` (reserved, unused today) | would be `kMidImplicit` if ever used — "Mid point of a bounded line ... or arc" is exactly what marker `-2`'s doc comment already reserved this for |
`SketchRef` and this graph already agree on one thing directly: our own doc
comment (`src/scene/sketch_constraints.rs:20-22`) says the `marker`
convention was deliberately borrowed from
`AssocDimensionReference::main_gs_marker` (`src/scene/dimension_assoc.rs`,
already shipping, already reading/writing real `Assoc*` objects for
associative dimensions). That's a different, lighter mechanism than the
constraint graph (an `AcDbOsnapPointRef` chain attached directly to a
`Dimension` entity, not the `AssocNetwork`/`AssocAction` framework — see
`AssocDimensionAssociation`, `objects/associative.rs:1200`), so it's a
precedent for *how this codebase already talks to `Assoc*` types*, not a
template for the constraint graph's own wiring.
## 5. Known gaps (not addressed by this document)
- **`Equal` is one `ConstraintKind`, AutoCAD has (at least) two**:
`ACEQUALLENGTHCONSTRAINT` (lines) vs. `ACEQUALRADIUSCONSTRAINT`
(circles/arcs), plus `ACEQUALCURVATURECONSTRAINT`/
`ACEQUALDISTANCECONSTRAINT` we don't model at all. `build_constraint`
already branches on `refs`' resolved shape at solve time
(`sketch_solve.rs:181-188`), so the same branch can pick the write-side
`class_name` — no data-model change needed for the length/radius split;
the other two AutoCAD sub-kinds are a real, separate gap.
- **`DrivingValue::Named` — confirmed, has a real native equivalent, and the
full wiring is now traced end to end.**
`AcDbAssocVariable` (`objects/associative.rs:1024`: `name`, `expression`,
`evaluator`, `description`, cached `value`) is fully read *and* written on
both DWG and DXF (confirmed in
`dwg_stream_readers/object_reader/associative.rs:1031-1063` and the DXF
reader/writer equivalents) — it's the same associative-action graph a
constraint group lives in (`AssocVariable` wraps an `AssocAction`, exactly
like `Assoc2dConstraintGroup` does), not a separate table.
The two handles on `Distance`/`Angle`/`RadiusDiameter`
(`read_explicit_constraint`, `associative.rs:652-661` — reads them in this
order: `value_dependency` first, `dimension_dependency` second) resolve
to two different, unrelated things:
- **`value_dependency`** → an `AssocValueDependency` object
(`{ dependency: AssocDependency, name, value: AssocEvalVariant }`,
`objects/associative.rs:554`). Its own `value: AssocEvalVariant`
(`{ code: i16 /* DXF resbuf type */, value: AssocEvalValue::Real(f64) }`,
confirmed `associative.rs:579-596`) is the **cached, already-resolved
number** — always present, read fast without evaluating anything. The
embedded `AssocDependency.dependent_on: Handle`
(`read_dependency`, `associative.rs:83-109`) is what actually links to a
named parameter: `Handle::NULL` when the driving value is a bare
literal, or an `AssocVariable`'s handle when it's driven by one. This
gives a direct, confirmed mapping:
- `DrivingValue::Literal(v)` → `AssocValueDependency` with
`value = Real(v)`, `dependency.dependent_on = Handle::NULL`.
- `DrivingValue::Named(name)` → the same, but `dependency.dependent_on`
points at an `AssocVariable{ name, expression: <our formula>, .. }`
object (create once per distinct name, reused by every constraint
referencing it — `value` still carries the current resolved number,
kept in sync with `ParameterTable`'s last evaluation, same duty our
own `dof`/`conflicts` caches already have).
- **`dimension_dependency`** → a *separate* dependency chain
(`AssocAnnotationDependency`/`AssocDimDependencyBody`,
`associative.rs:744-751` and `:1077-1082`) linking to the actual
`Dimension` entity AutoCAD draws next to a constraint to show its
current value on screen — nothing to do with named parameters at all.
Not required for our purposes unless we also want the constraint to
carry a visible on-canvas dimension the way AutoCAD's does (open
question, not yet decided — §7).
Remaining gap before mapping directly: AutoCAD's `expression` is
evaluated by a named, extensible `evaluator` (not fixed) — real-file
survey needed to know what evaluator name(s) actually appear in practice
(§7 open question 4), ours has one hardcoded evaluator.
- **Missing `ConstraintKind`s**: Concentric, Colinear, Symmetric, Fixed,
Midpoint, Normal, CenterPoint, PointOnCurve — real AutoCAD constraint
types with no `ConstraintKind` variant to map from at all. Out of scope
until/unless the sketch-constraint system itself grows them.
- **`Tangent` involving an `Arc`** has no native mapping to design, since
`build_constraint` doesn't support it at all yet (`sketch_solve.rs:239-241`
— `Arc` isn't a supported `EntityGeom` variant in that function). A
pre-existing gap in the constraint system itself, unrelated to and not
blocking this document.
## 6. Persistence architecture: additive, not a replacement
**Proposal: write both.** Keep `OCS_SKETCH_CONSTRAINTS` as the authoritative
round-trip format OpenCADStudio itself reads back
(`sketch_persist::load_sketch_constraints_from_document`), and *additionally*
materialize the `AssocNetwork`/.../`Assoc2dConstraintGroup` graph alongside
it on save, for other applications to read.
Reasoning:
- Our own model carries information the native graph can't represent
losslessly today — `DrivingValue::Named` chief among them (§5), also
`enabled` (a suppressed-but-kept constraint) which may or may not have a
clean native equivalent (`AssocConstraintNodeData`'s `is_active` field on
several variants looks like the right fit, but unconfirmed, §7).
Round-tripping through the native format only and reconstructing our
model from it risks silent data loss on every save/reopen cycle.
- The native graph's own geometry-node fields (`center`, `radius`, `point`,
...) are *snapshots* of the current solved geometry, not derived at load
time — so they need to be kept in sync with whatever `ocs_gcs` last
solved, same as we already do for our own tessellated wire geometry. Two
representations of "the same" constraint, kept consistent by one save
path, is a smaller invariant to maintain than trying to make the native
graph the *only* source of truth for a system it can't fully express.
- This is reversible: if the native graph turns out to work well enough
(once tested against real AutoCAD/BricsCAD opens, §7) to *become* the
primary format later, dropping the redundant `OCS_SKETCH_CONSTRAINTS`
write is a small follow-up, not a rewrite.
## 7. Open questions
Three of the original five are now resolved from Autodesk's own primary
sources (§2b), not just inferred from `cadcodec`'s struct shapes — kept
here with their answers so the reasoning stays visible, not deleted.
`cadcodec` itself still ships no constraint-bearing DWG/DXF fixture and no
constraint-specific test (checked: no hits for `Assoc2dConstraintGroup`/
`AssocConstraintNode` under any `tests/` directory or fixture path in the
dependency), so even the resolved ones haven't been checked against a real
written-and-reopened file yet — that step still matters (§8 stage 1) for
confirming implementation correctness, just not for the *design* questions
below anymore.
1. **Resolved.** Where the top-level `AssocNetwork` attaches:
`AcDbAssocNetwork::getInstanceFromDatabase`/`getInstanceFromObject`'s own
doc comments (§2b) state the default sub-dictionary key directly —
`"ACAD_ASSOCNETWORK"` — used both for the one top-level network (owned by
the database's Named Object Dictionary) and for each per-block-record
network (owned by that `AcDbBlockTableRecord`'s own extension
dictionary — confirming the parallel to `OCS_SKETCH_CONSTRAINTS`'s own
attachment was right). The two are linked: creating a per-block network
with `addToTopLevelNetwork = true` (the default) also registers it as an
action inside the top-level network, so evaluating the top-level network
cascades through every block's sub-network.
2. **Resolved.** Sub-entity addressing for `ImplicitPoint`: confirmed the
real addressing lives entirely in the constraint-node's own
`point_type`/`point_index`/`curve_id` fields, not in
`AssocPersistentSubentId` (that type turned out to be an abstract C++
base class for a *different*, more general subentity-tracking need
elsewhere in the associative framework — irrelevant to 2D constraint
geometry). `AcDb::ImplicitPointType`'s exact enum (§4) settles the
mapping.
3. **Still open.** Whether a partially-populated graph (e.g. omitting
`AssocPersSubentManager`, or leaving some `AssocAction` bookkeeping
fields at their `Default`) still opens cleanly in real AutoCAD, or
whether it requires every field correctly populated to avoid a repair
prompt or silent constraint loss. Genuinely needs a real write-then-open
test, not documentation — this is a robustness/tolerance question, not a
structural one.
4. **Narrowed to almost nothing.** `AssocVariable`'s `evaluator` field:
`AcDbAssocVariable.h`'s own doc comment (§2b) says plainly — "If empty,
the default evaluator for current acad version will be used." Leaving it
as an empty string is officially-supported behavior per the SDK itself,
not a guess. No further investigation needed unless a real file later
shows this doesn't round-trip as expected.
5. **Still open.** DWG version gating — `AssocConstraintNode`'s own reader
already branches on `version.r2013_plus(dxf_version)` for one field
(`dwg_stream_readers/object_reader/associative.rs:604`); unclear whether
other fields need per-version handling this document hasn't looked for.
Likely answerable the same way as questions 1/2/4 were (Autodesk's own
docs usually note version-specific members) — just not done yet.
6. **Still open, low priority.** Whether to bother wiring
`dimension_dependency` at all (§5) — it drives the small on-canvas
dimension label AutoCAD shows next to a constrained value, cosmetic
rather than structural. Leaving it `Handle::NULL` is presumably safe (the
constraint itself is still fully defined without it) but unconfirmed;
deferring this to a later stage either way (§8).
## 8. Suggested staged approach
1. **Proof of concept, narrowest possible slice**: `Horizontal`/`Vertical`
on a single `Line`, no dimensional constraints (sidesteps the
`AssocVariable`/value-dependency wiring entirely, though that's now fully
designed too — §5). Write the graph — `AssocNetwork` at
`"ACAD_ASSOCNETWORK"` on the scope owner's extension dictionary (§7.1),
one `Assoc2dConstraintGroup` action, one `BoundedLine` geometry node, one
`Geometrical`/`"ACHORIZONTALCONSTRAINT"` (or vertical) constraint node —
and open the file in whatever real-DWG-reading tool is available, to
confirm AutoCAD/BricsCAD actually shows a constraint icon on the line and
doesn't flag the file as corrupt. This is what answers open question 3
(partial-graph tolerance) — the one remaining question documentation
alone can't settle.
2. **Geometric constraints, still no driving value**: add `Coincident`
(needs `ImplicitPoint`/`kStartImplicit`/`kEndImplicit`, §4, now
confirmed), `Parallel`, `Perpendicular`, `Equal` (both sub-kinds) —
verify against the same real-file test from stage 1.
3. **Dimensional constraints**: `Distance`, `Angle`, `Radius`, using the now
fully-traced `value_dependency → AssocValueDependency → AssocVariable`
chain (§5) — including deciding whether `DrivingValue::Named` gets the
real native mapping designed there, or stays a resolved-literal degrade
for a first pass.
4. **Everything else**: `Tangent`'s non-circle-circle cases, the missing
`ConstraintKind`s (§5) if/when the sketch-constraint system grows them,
`dimension_dependency` (§7.6) if a visible on-canvas dimension label
turns out to matter.
Each stage's file should be hand-inspected (or opened in real software)
before moving to the next — the graph's correctness genuinely can't be
verified by this codebase's own round-trip tests alone, since a bug that's
symmetric (write it wrong, read the same wrongness back) would pass here and
still fail to open correctly elsewhere.
## 9. Progress
Design only — no implementation yet. Of the original five open questions,
three are now resolved from Autodesk's own primary documentation (§7.1, 7.2,
7.4) rather than inference — the network's dictionary key, `ImplicitPoint`'s
addressing enum, and the `AssocVariable` evaluator default are no longer
guesses. Two remain (§7.3 partial-graph tolerance, §7.5 version gating) and
both genuinely need a real write-then-open test rather than more reading.
Next action per §8 is stage 1's narrowest slice — the design for it no
longer rests on an unconfirmed foundation, only on an unverified one.