rvt 0.3.0

Apache-2.0 clean-room reader for Autodesk Revit files (.rvt, .rfa, .rte, .rft) without Revit: metadata, previews and the embedded schema of 2016-2026 files, and typed elements with measured geometry from Revit 2024 and 2025 projects, exported as IFC4, glTF and plan SVG.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
{
  "schema_version": 1,
  "matrix_id": "rvt-rs-support-matrix",
  "reviewed": "2026-09-24",
  "description": "Executable support matrix for rvt-rs. Seeded from honest current capabilities in docs/status.md, docs/supported-profile.md, and ROADMAP.md — not aspirational milestone goals. Do not claim converter-grade typed recovery.",
  "sources": [
    "docs/status.md",
    "docs/supported-profile.md",
    "ROADMAP.md"
  ],
  "status_vocabulary": [
    "verified",
    "partial",
    "experimental",
    "unsupported",
    "unknown"
  ],
  "audit_controls": [
    "COR-001",
    "TEST-001",
    "DOC-001",
    "A3"
  ],
  "honesty_invariants": {
    "never_verified_capability_ids": [
      "typed-project-elements",
      "typed-decoder-registry",
      "ifc4-export-real-projects",
      "converter-grade-rvt-ifc",
      "typed-door-window",
      "schema-field-wall",
      "level-elementid-storey-bind",
      "aparameter-host-joins",
      "semantic-revit-editing",
      "stream-level-writer"
    ],
    "max_status_by_capability_id": {
      "typed-project-elements": "partial",
      "typed-decoder-registry": "partial",
      "ifc4-export-real-projects": "partial",
      "adocument-walker": "partial",
      "browser-viewer": "partial",
      "python-package": "partial",
      "inspect-cli": "partial",
      "stream-level-writer": "partial",
      "community-corpus-scaffold": "partial",
      "converter-grade-rvt-ifc": "unsupported",
      "typed-door-window": "unsupported",
      "schema-field-wall": "unsupported",
      "level-elementid-storey-bind": "unsupported",
      "aparameter-host-joins": "unsupported",
      "semantic-revit-editing": "unsupported"
    }
  },
  "capabilities": [
    {
      "id": "cfb-container-open",
      "area": "container",
      "name": "Open .rvt / .rfa / .rte / .rft CFB containers",
      "status": "verified",
      "evidence": "reader module; CI open matrix; Q-01 corpus open path",
      "notes": "Standard OLE/CFB Revit layout. Corrupt, encrypted, or non-CFB inputs fail closed.",
      "user_impact": "Files can be inspected without Revit."
    },
    {
      "id": "truncated-gzip-streams",
      "area": "compression",
      "name": "Decode Revit truncated-gzip streams",
      "status": "verified",
      "evidence": "compression; checksum_page_framing; fuzz targets; checksum-page strip on Partitions, Global and (#410) Formats/Latest; examples/probe_schema_page_strip.rs",
      "notes": "Every paged stream is stripped before inflate. Formats/Latest joined in #410: stripped, every file of one release inflates to the byte-identical schema (2023: 4 files, 2024: 5, 2025: 8); unstripped it drifted after about 140 KB. Writer path has separate audit coverage.",
      "user_impact": "Internal streams can be read safely."
    },
    {
      "id": "metadata-partatom-preview",
      "area": "metadata",
      "name": "Extract BasicFileInfo (incl. worksharing text block), PartAtom / ProjectInformation Atom, preview PNG",
      "status": "verified",
      "evidence": "basic_file_info; part_atom; project_information; metadata; tests/samples.rs basic_file_info_properties_read_on_every_release (2016-2026); rvt_info_cli; preview tests",
      "notes": "Identity, build, GUID, title, OmniClass, stream preview, and the BasicFileInfo Key: value block (worksharing, central model path, last-saved-by user, save counter, cloud flag) on every release 2016-2026. Folder inventory via rvt-info. Workshared values other than 'Not enabled' are read verbatim; no workshared sample is in the redistributable corpus.",
      "user_impact": "Users can identify and audit files, one at a time or a folder at once."
    },
    {
      "id": "formats-latest-schema",
      "area": "schema",
      "name": "Parse Formats/Latest schema with field classification",
      "status": "verified",
      "evidence": "formats::parse_schema over the whole page-stripped stream (#410): 3,490 to 4,285 classes and 9,932 to 12,144 fields per release on the 2016-2026 family corpus, 122,107 fields in all; tests/field_type_coverage.rs gates classification with 9 to 12 fields per release listed as unclassified; examples/probe_schema_page_strip.rs",
      "notes": "Class and field structure for developers. Not per-element instance recovery. Before #410 only the first 64 KB were parsed (the 395 classes and 13,570 fields previously quoted), because the rest was inflated without its page strip and read as noise. The class tag here is the u16 after each name; the element records name their class by definition ordinal (#154, #223).",
      "user_impact": "Schema introspection works across the verified family band."
    },
    {
      "id": "adocument-walker",
      "area": "document",
      "name": "Read document-level ADocument data",
      "status": "partial",
      "evidence": "Layer 5a walker; reliable on newer samples",
      "notes": "Good for diagnostics. Older/project bands need more corpus proof. Not complete model extraction.",
      "user_impact": "Document metadata aids triage; not a full model read."
    },
    {
      "id": "typed-project-elements",
      "area": "elements",
      "name": "Decode typed elements from real project files",
      "status": "partial",
      "evidence": "Production iter_elements: ArcWall (2023) + partition MVP Level/Material + 2024 ArcWallRectOpening (ElemTable-confirmed ids) + 2024 partition element-record Wall/Door/Window/Column instances (RE-21/#211) Slab/BuildingPad instances with per-element IFC export overrides (RE-22/#212) and Room instances with their parameter-block name, number and Level (RE-29/#90); RE-19/RE-20 negatives",
      "notes": "Narrow fail-closed MVP only. Seven BuiltInCategories on one Revit 2024 edge match Revit's exporter exactly; generic real-project typed extraction is still not solved. Never mark verified without corpus-backed converter-grade evidence (COR-001).",
      "user_impact": "Useful recovery on supported subsets; full model conversion is not ready."
    },
    {
      "id": "typed-decoder-registry",
      "area": "elements",
      "name": "Per-class typed decoder structs",
      "status": "partial",
      "evidence": "elements::all_decoders() (~81); MVP_TYPED_CLASSES consulted by iter_elements",
      "notes": "Library building blocks plus production MVP/ArcWall path. Broader registry is not production conversion.",
      "user_impact": "Developers can compose decoders; users should not read this as full coverage."
    },
    {
      "id": "element-record-instance-recovery",
      "area": "elements",
      "name": "Wall / Door / Window / Column / Slab instance recovery from Revit 2024 partition element records",
      "status": "verified",
      "evidence": "partition_element_records + partition_schema_mvp::instances_from_partition_category_records / slabs_from_partition_category_records + partition_ifc_export_overrides; tests/project_count_fixtures.rs (IFCWALL 360/360, IFCDOOR 132/132, IFCWINDOW 6/6, IFCCOLUMN 256/256, IFCSLAB 80/80, IFCSHADINGDEVICE 20/20, tolerance 0) and the magnetar-2024-core-interior-slim OctetProof verdict, where entity_counts.IFCWALL / IFCDOOR / IFCWINDOW / IFCCOLUMN / IFCSLAB / IFCSHADINGDEVICE are all inside the claimed surface and rvt-rs, IfcOpenShell and IFClite agree exactly",
      "notes": "Scope is six BuiltInCategories (OST_Walls, OST_Doors, OST_Windows, OST_Columns, OST_Floors, OST_BuildingPad), one release band (Revit 2024) and one recorded edge. Selection is a direct byte test, not a heuristic: the record must carry no container reference at +0x32 and placement kind 0xffffef7f at +0x42 (RE-21). It supersedes the #204 column rule, whose family-local bbox proxy and highest-ElementId-per-footprint collapse are removed. RE-22 (#212) extended the same rule to slabs and found it already exact there: its 99 OST_Floors selections are all exported (79 IfcSlab + 20 IfcShadingDevice), and the 80th exported slab is an OST_BuildingPad (Pad:Site Pad, ElementId 21975). The IfcSlab / IfcShadingDevice split is a per-instance Revit 'IFC Export As' override read out of the partition parameter block: the UTF-16LE value string with the owning ElementId as a u64 at -0x0dc and again at -0x11e, both required to agree and to be declared in Global/ElemTable. Thirty-one entries pass on this file, naming 21 ids; the 20 that are also placed instances are exactly the export's IFCSHADINGDEVICE Tag set. Only IfcShadingDevice is honoured as an override target — an unrecognised value leaves the element on its class mapping. Where one ElementId is framed more than once, the record with the greatest bounding-box z extent is kept; that agrees with the export's IfcExtrudedAreaSolid.Depth on 79 of 80 slabs (the 80th is exported as two stacked solids summing to it). Ties go to the newest frame - the greatest (stream, offset) - because Revit rewrites an edited element into a higher-numbered Partitions stream and leaves the earlier copy in place (RE-26): 171 of 360 walls and 96 of 132 doors carry two frames that disagree about the plan box, and on the newest one the wall's thin plan extent equals the nominal thickness of the IfcWallType Revit's export assigns on 360 of 360 (201 of 360 on the oldest) and its type slot equals that IfcWallType.Tag on 356 of 360 (183 of 360 on the oldest; the positional read is superseded by RE-28's set test, which is 360 of 360 - see element-record-wall-type-join). Global/ElemTable's u32 at +0x1c is a monotone version counter that ranks the same way across all 976 exported instances. No frame of any instance on this file disagrees about the box's z, so the tie-break cannot move an element between storeys - storey_bound_elements was 801 either way when this was measured, and is 853 since #219 / RE-27. Column, window, slab and building-pad boxes are unchanged; door boxes move without a measurement that adjudicates them, since a door's record box is not the panel Revit exports and 0 of 132 are world-exact either way. Bodies are the record's own bounding box, not a recovered family profile or wall location curve; for plates the z extent is the real thickness, and since RE-25 (#31) the plan profile of a slab whose sketch closes is the recovered sketch boundary rather than the box rectangle - see element-record-slab-plan-profile. Base/top Level ElementIds stay unbound (#86); doors and windows are attached to their host wall as of RE-23/#222 (see element-record-door-window-host-binding). Storey containment came from the #213 column-derived elevation set when this row landed: walls, doors and columns bound by their record base, plates by their record top face (Revit hangs a floor below its level) — 51 of 100 plates bound and all 51 agreed with the export, none wrongly, while windows never bound because their record base is a sill height, measured 0 of 6. Since #219 / RE-27 the first join tried is the Level ElementId the record itself names, which reaches all 100 plates and all 6 windows; that elevation path survives as the fallback - see record-level-reference-storey-bind. This is not evidence for any other category or release.",
      "user_impact": "On the recorded Revit 2024 project, walls, doors, windows, architectural columns, floor slabs and building pads come out as placed IFC entities with position, a solid body and (for slabs) a measured thickness and plan profile, matching Revit's own export element for element. On Autodesk's Snowdon Towers 2024 samples most element frames use a second prologue; their ElementIds come from the enclosing partition record (RE-35), and walls, doors, windows, columns and floors export under Revit's own ids (see element-records-second-prologue-ids). Structural framing, structural columns, foundations and generic models export from the same records (see element-records-structural-categories)."
    },
    {
      "id": "element-records-revit-2025",
      "area": "elements",
      "name": "Revit 2025 partition element records (walls, slabs, rooms, and the rest of the RE-21 categories)",
      "status": "partial",
      "evidence": "partition_element_records::bbox_marker; tests/element_records_2025.rs against Drshelden/IFC-ECS RE1-Architecture (MIT, fetched by tools/fetch-corpus.sh); reports/element-framing/RE-32-revit-2025-element-records.md",
      "notes": "The 2024 record shape carries over with a per-release bbox marker whose tail is the Global/ElemTable header constant + 40 (0x05d3 on 2025). The RE-21 instance rule reproduces Revit's export exactly on RE1-Architecture (walls 7/7, slabs 2/2, rooms 11/11) with zero false positives across six 2025 files; the doors and the curtain wall, whose frames use the second prologue (RE-30), take their enclosing record's ElementId (RE-35), so every wall, slab and door Revit exported is exported, plus one door Revit's export leaves out. Room names, storeys, export-type overrides and wall types stay 2024-only. 2023 and 2026 are unsupported.",
      "user_impact": "Revit 2025 projects export their walls, slabs, rooms, doors and windows under Revit's own ElementIds, matching Revit's export on the measured files; the diagnostics say what was skipped."
    },
    {
      "id": "element-records-product-categories",
      "area": "elements",
      "name": "Furniture, casework, fixtures, equipment, ceilings, curtain-wall parts, railings, wall sweeps, ducts and pipes from partition element records",
      "status": "partial",
      "evidence": "partition_element_records::PRODUCT_RECORD_CATEGORIES; tests/element_records_2025.rs against Drshelden/IFC-ECS RE1 Architecture, Mechanical and Plumbing (MIT, fetched by CI tier 2); examples/probe_re33_category_census.rs; reports/element-framing/RE-33-product-categories.md",
      "notes": "Thirteen more BuiltInCategory values decode under the unchanged RE-21 instance rule, and every ElementId they select from a record carrying it at +0x00 is a Tag in Revit's own export of the same file: zero false positives on RE1 Architecture, Mechanical and Plumbing (Revit 2025) and on Snowdon Towers (Revit 2024, measured locally). With RE-35's second-prologue ids, RE1 Architecture exports all 58 elements of these categories, RE1 Mechanical all 66 ducts, pipes and fittings and RE1 Plumbing all 117 pipes and fittings and all 6 fixtures, plus one fixture Revit's export leaves out; on Snowdon, curtain panels, railings, ceilings, furniture and fixtures are complete, and 47 curtain panels are elements Revit's export omits (#309). The 60 placed instances with no 3D volume are left out as Revit leaves them out. Second-prologue frames keep the container and placement fields, so the unattributed count covers placed instances only and matches the missing elements exactly for pipes, fittings, mullions, columns, railings, ceilings and furniture. Bodies are the record bounding box; entities are the IFC4 types (IfcFurniture, IfcSanitaryTerminal, IfcCovering, IfcMember, IfcPlate, IfcRailing, IfcBuildingElementProxy, IfcDuctSegment, IfcDuctFitting, IfcPipeSegment, IfcPipeFitting). Revit 2024 and 2025 only.",
      "user_impact": "Revit 2024 and 2025 projects export furniture, casework, plumbing fixtures, specialty equipment, ceilings, curtain-wall mullions and panels, railings, wall sweeps, ducts and pipes whose records carry their ElementId, each one an element Revit's own export also holds; the diagnostics count the rest."
    },
    {
      "id": "element-records-second-prologue-ids",
      "area": "elements",
      "name": "ElementIds for second-prologue partition element records, read from the enclosing partition record",
      "status": "partial",
      "evidence": "partition_element_records::partition_record_chain / assign_second_prologue_ids / RevitFile::second_prologue_ids; tests/partition_record_chain.rs (every first-prologue frame starts a record of its own id on Core Interior and the RE1 models); tests/element_records_2025.rs; examples/probe_re35_record_wrapper.rs; reports/element-framing/RE-35-partition-record-wrapper.md; reports/element-framing/RE-43-invalid-element-id.md",
      "notes": "Every Partitions/* stream opens with a chain of records, one per element: u64 ElementId, u32 size, u16 prologue constant (release constant + 28), u16 count, the body, and a 16-byte trailer that repeats the size (RE-35). A first-prologue frame starts its record; a second-prologue frame (RE-30) sits inside its element's record, so its ElementId is the record's, read rather than inferred. All 30,432 first-prologue frames across nine 2024 and 2025 files start a record of their own id, and the chains hold every declared id on Core Interior and the RE1 models. Records after the chain belong to loaded families' own documents and never export. Against Revit's own exports: RE1 Architecture, Mechanical and Plumbing reach 100% of every recovered entity; Snowdon Towers Architectural exports 6,045 elements, 5,945 in Revit's export (a frame holding u32::MAX, the invalid ElementId, also takes its record's id, RE-43), with every wall, column, door, window, curtain wall and panel, railing, ceiling, furniture item, fixture and generic model of Revit's export attributed once RE-41 declares the ElemTable's second id. The rest are real elements Revit's export leaves out (#309); the 18 in non-primary design options are left out as Revit leaves them out (RE-40). Floors, building pads and ceilings get their ids too. It replaces RE-34's reference-order inference, which gave a door its type's id on a Revit 2025 file outside its hold-outs. Placed instances with no 3D volume are left out, as Revit leaves them out. Revit 2024 and 2025 only.",
      "user_impact": "Files whose element records mostly lack an ElementId at their start (Autodesk's Snowdon Towers samples, the RE1 models, projects saved incrementally) export their walls, doors, windows, floors, ceilings, columns, curtain-wall parts, railings, furniture, fixtures, ducts and pipes under the ElementIds Revit itself uses; the diagnostics count what is still left out."
    },
    {
      "id": "elem-table-secondary-element-ids",
      "area": "elements",
      "name": "ElemTable records whose two ids differ declare the second",
      "status": "experimental",
      "evidence": "elem_table::declared_ids; examples/probe_re41_elem_table_secondary_ids.rs; reports/element-framing/RE-41-elem-table-secondary-ids.md",
      "notes": "A 40-byte Global/ElemTable record carries two u32 ids, at +16 and +36. They agree on every record of Core Interior, the RE1 models, teste_export_2025, Projeto1 and the 2016-2023 projects measured. On Autodesk's Snowdon Towers samples (local only) they differ on 84 records (Architectural) and 27 (Structural); there the second is the id of the element's partition record (RE-35) and the Tag Revit's own IFC export writes, and the first is neither. The declared id set now holds both (a 0 in the second is not added), which attributes all 37 element records Snowdon Architectural left unattributed (16 walls, 4 columns, 17 generic models, all in Revit's export) and one floor on Snowdon Structural (in the VIM export). Licensed files export byte-identically. Revit 2024 and 2025 only.",
      "user_impact": "Elements whose ElemTable record carries a second id, such as those in a primary design option on Autodesk's sample, export under the ElementId Revit itself uses instead of being counted as unattributed."
    },
    {
      "id": "element-records-structural-categories",
      "area": "elements",
      "name": "Structural framing, structural columns, structural foundations and generic models from partition element records",
      "status": "experimental",
      "evidence": "partition_element_records::PRODUCT_RECORD_CATEGORIES (OST_StructuralFraming, OST_StructuralColumns, OST_StructuralFoundation, OST_GenericModel); examples/probe_re36_vim_oracle.rs; reports/element-framing/RE-36-structural-categories.md",
      "notes": "Four more BuiltInCategory values decode under the RE-21 instance rule with RE-35's enclosing-record ElementIds and export as IfcBeam (.BEAM.), IfcColumn (.COLUMN.), IfcFooting and IfcBuildingElementProxy. Measured locally on Autodesk's Snowdon Towers 2024 samples, which have no licence and are not in CI. Against the VIM export of the Structural sample (a later edition), every exported id the VIM carries has the record's category there: 914 framing members, 74 columns, 90 foundations and 91 generic models, none under another category. The 28 framing ids it lacks are beam-system members the later edition regenerated. On the architectural sample 260 of 261 generic models are in Revit's own IFC4 export; the other is in a non-primary design option. Framing on that file has no braces (Structural Usage: joists, girders, purlins), so IfcBeam is the entity; a brace would also export as IfcBeam, since the record's structural type is not decoded, and Revit's own PredefinedType is not measured. Rebar, structural connections and beam systems have correct ids but are not exported. Bodies are the record bounding box. Revit 2024 and 2025 only.",
      "user_impact": "Revit 2024 and 2025 structural models export their beams, joists, girders, columns, footings and generic models as IFC elements under Revit's own ElementIds, with bounding-box bodies; rebar and connections are not exported."
    },
    {
      "id": "element-records-fixture-site-categories",
      "area": "elements",
      "name": "Lighting fixtures, air terminals, food-service equipment, site elements, elevators and ramps from partition element records",
      "status": "partial",
      "evidence": "partition_element_records::PRODUCT_RECORD_CATEGORIES; tests/element_records_2025.rs against Drshelden/IFC-ECS RE1 Mechanical and Electrical (MIT, fetched by CI tier 2); reports/element-framing/RE-37-fixture-site-circulation-categories.md",
      "notes": "Nine more BuiltInCategory values (OST_LightingFixtures, OST_DuctTerminal, OST_FoodServiceEquipment, OST_Planting, OST_Parking, OST_Entourage, OST_Hardscape, OST_VerticalCirculation, OST_Ramps) decode under the RE-21 instance rule with RE-35's enclosing-record ElementIds. Every exported element is a Tag of Revit's own export with none outside it, and each entity is complete: 447 lighting fixtures, 26 food-service items, 157 planting, parking, entourage and hardscape proxies, 2 ramps and 2 elevators on Snowdon Towers (local only); 7 air terminals on RE1 Mechanical; 12 lighting fixtures on RE1 Electrical. Entities follow Revit's IFC4 export on Snowdon (IfcLightFixture, IfcElectricAppliance, IfcBuildingElementProxy, IfcTransportElement, IfcRamp); air terminals use IfcAirTerminal, the IFC4 subtype of the IfcFlowTerminal Revit writes in IFC2x3. Slab edges are added by RE-40 once non-primary design options are left out; electrical fixtures, lighting, fire alarm and data devices and electrical and mechanical equipment by RE-79 (element-records-mep-devices); stairs and roofs are RE-39. Bodies are the record bounding box. Revit 2024 and 2025 only.",
      "user_impact": "Revit 2024 and 2025 projects export their lighting fixtures, air terminals, kitchen equipment, planting, parking, entourage, hardscape, elevators and ramps as the IFC entities Revit itself uses, each one an element Revit's own export also holds."
    },
    {
      "id": "element-records-mep-devices",
      "area": "elements",
      "name": "Electrical and mechanical devices and equipment from partition element records, without their nested symbols (RE-79)",
      "status": "partial",
      "evidence": "partition_element_records::PRODUCT_RECORD_CATEGORIES and NESTED_COMPONENT_CATEGORIES; partition_schema_mvp::without_nested_components; examples/probe_re79_nested_mep_components.rs; tests/element_records_2025.rs (revit_2025_electrical_devices_are_revits_own); reports/element-framing/RE-79-mep-devices-nested-components.md",
      "notes": "Six BuiltInCategory values (OST_LightingDevices, OST_FireAlarmDevices, OST_DataDevices, OST_ElectricalFixtures, OST_ElectricalEquipment, OST_MechanicalEquipment) decode under the RE-21 instance rule. A shared nested device (the symbol a switch or receptacle nests) is a record of its own in its parent's category; the two list each other among their references and the nested one has the larger ElementId, and Revit's export leaves it out, so rvt-rs does too (21 of 21 on RE1 Electrical). RE1 Electrical then exports exactly Revit's elements: 12 lighting fixtures, 2 data devices, 5 fire alarm devices and 30 devices and panels, none outside its export; RE1 Mechanical adds its AHU. Entities are the IFC4 subtype of what Revit's IFC2x3 export writes: IfcAlarm (IfcAlarmType), IfcElectricAppliance (IfcElectricApplianceType), proxies. Measured on the RE1 models only; Core Interior and Snowdon Towers hold none of these categories and are unchanged. Bodies are the record bounding box. Revit 2024 and 2025 only.",
      "user_impact": "MEP models export their switches, receptacles, sensors, alarms, panels and equipment, each an element Revit's own export also holds."
    },
    {
      "id": "element-records-stair-aggregates",
      "area": "elements",
      "name": "Stairs as IFC aggregates of their runs, landings and stringers, and roofs",
      "status": "experimental",
      "evidence": "partition_schema_mvp::attach_aggregate_wholes; IfcEntity::Aggregate and the STEP writer's IfcRelAggregates; examples/probe_re39_stair_parts.rs; reports/element-framing/RE-39-stair-aggregates.md",
      "notes": "A stair's runs, landings and stringers name the stair's ElementId in their reference lists. Each stair exports as a bodiless IfcStair and its parts (IfcStairFlight, IfcSlab .LANDING., IfcMember .STRINGER.) are related to it by IfcRelAggregates, not contained in a storey. Measured locally on Snowdon Towers Architectural, which has no licence and is not in CI: the same 26 stairs aggregate as in Revit's IFC4 export, and every one of the 228 parts rvt-rs attaches is in the matching Revit aggregate; all 256 new elements (27 stairs, 42 flights, 17 landings, 170 stringers) are in Revit's export. Railings stay standalone: Revit aggregates 65 of the 70 that name a stair, and nothing read tells the other five apart. PredefinedTypes are left unset. Roofs (OST_Roofs) export as one IfcRoof each with the record box: all 20 Snowdon roofs are IfcRoof Tags in Revit's export, which decomposes 7 of them into a same-Tag IfcSlab .ROOF. part that rvt-rs does not reproduce. Revit 2024 and 2025 only.",
      "user_impact": "Stairs come out as Revit exports them: one IfcStair per stair holding its flights, landings and stringers, so IFC viewers show and select a stair as one assembly."
    },
    {
      "id": "element-records-design-options",
      "area": "elements",
      "name": "Design options: leave out non-primary options, and slab edges",
      "status": "experimental",
      "evidence": "partition_design_options::compute_design_options; PartitionElementRecord::design_option; examples/probe_re40_design_options.rs; tests/design_options.rs (the licensed files read no design option); reports/element-framing/RE-40-design-option-primary.md",
      "notes": "An element record holds its design option's ElementId at +0x2a. A design option set's partition record lists its options at +0x56, and the set's name entry repeats that list and closes with the primary option's ElementId. The exporter drops records in a set's other options, as Revit's IFC export does by default, and reports them as the skipped item element_record_in_non_primary_design_option. A set whose name entry is missing or ambiguous stays unresolved and its options are kept. Measured locally on Snowdon Towers Architectural, which has no licence and is not in CI: of the placed instances of recovered categories, the 29 in the primary option 2059970 are all in Revit's IFC4 export and the 27 in non-primary options are all missing from it; removing them takes the elements outside Revit's export from 118 to 100 and removes none it holds. Slab edges (OST_EdgeSlab) are added as IfcBuildingElementProxy: all 59 exported are in Revit's export. No licensed file has a design option. Revit 2024 and 2025 only.",
      "user_impact": "Projects with design options export the model Revit itself exports, the main model with each option set's primary option, instead of every option stacked on top of each other."
    },
    {
      "id": "element-record-family-type-names",
      "area": "elements",
      "name": "Family and type names of element-record elements, from partition name entries",
      "status": "partial",
      "evidence": "partition_names::find_name_entries / resolve_type / resolve_family; RevitFile::element_names; tests/element_names.rs against 2024_Core_Interior_slim.ifc and the RE1 exports (CI tier 2); examples/probe_re38_names.rs; reports/element-framing/RE-38-family-and-type-names.md; examples/probe_re42_nested_families.rs; reports/element-framing/RE-42-nested-families.md",
      "notes": "Revit 2024 and 2025 partitions carry a name entry for every loaded family and family type: u64 ElementId, u32 length, UTF-16 name, i64 BuiltInCategory. An element's type is the one reference-list id of its own category with an entry, and its family is the one such id in the type's partition record (RE-35); where that record also names families nested in the type's family, the family is the one candidate whose own record names all the others (RE-42). The IFC Name becomes Family:Type:ElementId and the ObjectType Family:Type, as in Revit's export, and the property set gains FamilyName and TypeName. Every name written equals Revit's for the same Tag: 394 on Core Interior, 3,831 on Snowdon Towers (plus 71 elements Revit's export omits), 56, 42, 60 and 12 on RE1 Architecture, Mechanical, Plumbing and Electrical (plus one each omitted on Architecture and Plumbing), 1 on teste_export_2025. On the Snowdon structural sample 1,132 of the 1,156 named elements in the VIM export equal its family and type; the other 24 are a type the VIM's later edition renamed. On Snowdon the name table agrees with the VIM export on all 348 shared ids, name and category. System families (walls, floors, roofs, ceilings, stairs, railings, curtain walls) have no name entry and keep Class-ElementId names and no ObjectType; their type names come from the type's own element data (RE-44, element-record-system-type-names). Revit 2024 and 2025 only.",
      "user_impact": "Doors, windows, columns, furniture, fixtures, mullions, panels and other family instances show the same Family:Type names in the IFC and the viewer that Revit gives them."
    },
    {
      "id": "element-record-system-type-names",
      "area": "elements",
      "name": "Type names of system-family elements (walls, floors, ceilings, roofs, railings, slab edges, ramps, curtain panels that are walls, stairs and their parts), from the type's element data",
      "status": "partial",
      "evidence": "partition_names::element_data_header / find_element_data_names; partition_type_records::scan_type_records (2024 and 2025, second-prologue records); partition_schema_mvp::recover_partition_schema_mvp; tests/element_names.rs core_interior_system_type_names_are_revits and re1_system_type_names_are_revits (CI tier 2); examples/probe_re44_system_type_names.rs; reports/element-framing/RE-44-system-type-names.md; RE-63: partition_schema_mvp::system_family / attach_system_family_names (FamilyNameSource system_family_by_category); tools/re/names_vs_ifc.py; reports/element-framing/RE-63-system-family-element-names.md; RE-64: partition_schema_mvp::attach_panel_wall_types; examples/probe_re64_panel_wall_types.rs; reports/element-framing/RE-64-panel-walls-slab-edges-ramps.md; RE-65: partition_stairs::component_type_at / scan_component_types; partition_schema_mvp::attach_stair_names (ELEMENT_NAME_FIELD, FamilyNameSource system_family_by_type_kind); examples/probe_re65_stair_names.rs; reports/element-framing/RE-65-stair-names.md; RE-66: partition_names::find_railing_type_names; examples/probe_re66_railing_type_names.rs; reports/element-framing/RE-66-railing-type-names.md; RE-67: partition_names::find_text_type_names; partition_schema_mvp::attach_model_text_types; examples/probe_re67_model_text_types.rs; reports/element-framing/RE-67-model-text-names.md; RE-69: partition_names::find_type_object_ids; partition_schema_mvp::attach_wall_sweep_types; examples/probe_re69_wall_sweep_types.rs; reports/element-framing/RE-69-wall-sweep-names.md",
      "notes": "A system-family type has no RE-38 name entry. Its serialised element data opens with ff ff ff ff, a u16 header word (0x02d3 on Revit 2024, 0x02ef on Revit 2025), 01 00 00 00 and the type's u64 ElementId, and its name is the first framed string after that (ff ff ff ff, a u16 tag other than 0xffff, a u32 length, UTF-16). An element's type is the one type-definition record of its own category its reference list names (RE-28); on Snowdon and RE1 most such records are second-prologue frames that take their partition record's id (RE-35), and on 2025 they carry the 2025 marker and prologue constant. The element's property set gains TypeName. Every name written equals the type half of Revit's ObjectType for the same Tag: 460 on Core Interior (360 walls, 99 floors, the pad), 1,545 on Snowdon Towers Architectural, 16 on RE1 Architecture, 1 on Projeto1, and 6 of 7 on teste_export_2025, where the seventh wall's frame, data and bounding box all describe the other type, so that export was made from a later state of the model. The system family's own name (Basic Wall, Floor) is not stored in the file - the Portuguese export writes Parede basica for the same family - so no FamilyName is written, the IFC Name stays Class-ElementId and ObjectType stays unset. Types with no element-data block stay unnamed: RE1's one railing type and 7 of 22 Snowdon railing types. No IfcWallType or IfcSlabType entity and no IfcRelDefinesByType is written, and no other field of the type's data is read. Revit 2024 and 2025 only. RE-63 (2026-09-24): the system family itself is derived, since Revit stores none - a curtain wall is a Curtain Wall, a railing a Railing, a floor a Floor, a building pad a Pad, and a wall, ceiling or roof whose type has compound layers (an element of the type drawn in layers, or the type's own data) a Basic Wall, Compound Ceiling or Basic Roof - so these elements are named Family:Type:ElementId with ObjectType Family:Type as Revit names them: Snowdon 1,077 of 1,077 walls, 42 of 42 curtain walls, 68 of 68 ceilings, 20 of 20 roofs, 101 of 131 railings (the others have no type name), 167 of 182 floors (the 15 are floors Revit exports in two parts); Core Interior 460 of 460; RE1 16 of 17. RE-64 (2026-09-24): a curtain panel whose cell holds a basic wall keeps the panel category, and its record names one wall type rather than a panel type; the panel takes it when that type has compound layers, and its family is Basic Wall - Snowdon 18 of 18 named as Revit names them, while 23 panels naming a wall type without layers, which Revit's export leaves out, stay unnamed. Slab edges and ramps take their only system family (Slab Edge, Ramp): Snowdon 59 of 59 slab edges named as Revit names them, and both ramps with Revit's ObjectType (Revit adds a :1 part suffix to the ramp's Name). RE-65 (2026-09-24, Revit 2024): stair, landing and support types are serialised like run types (tag db 0f) and keep their name and a family flag at their own offsets - stair type construction u32 +0x129 (0 Assembled Stair, 1 Cast-In-Place Stair; 2, Precast, unmeasured and unnamed), its parameter entries then name from +0x13f and its component types in seven u64 slots from +0xc9; landing flag +0x95 (0 Non-Monolithic Landing) and name +0x98; support flag +0x89 (0 Stringer, 1 Carriage) and name +0x92; a run's family is Monolithic or Non-Monolithic Run by RE-52's flag. A stair is named Family:Stair:ElementId and each part '<stair> Run|Landing|Stringer N', N its ElementId rank among every record of its category naming the stair. Where a part names two types or two stairs, the pair its stair's type lists decides. Snowdon: 26 of 26 stairs, 43 of 43 runs, 17 of 17 landings, 170 of 170 supports with Revit's Name (any of the copies Revit writes of a multistory stair) and ObjectType; Snowdon only, since no other oracle has stairs. RE-66 (2026-09-24, Revit 2024): a railing type with no element data keeps its name in its type object (tag db 0f), as the u32-length UTF-16 string ending at the first ff ff ff ff 0b 02 past +0x100 (+0x1ae on 18 of Snowdon's 20 railing types, +0x1cb on the 2 that carry a Uniformat code); read only where the element-data name is missing. Snowdon railings named as Revit names them: 131 of 131 (was 101); on the 14 types with both, the two names agree. RE-67 (2026-09-24, Revit 2024): model text is a Generic Model that is not a family instance; its list names one text type, a type object (tag db 0f) that frames its name with tag 0x0149 and then a font with tag 0x07eb. It is named Model Text:<type>:<ElementId>: Snowdon 7 of 7 as Revit names them; no other local file has model text. RE-69 (2026-09-24, Revit 2024): a wall sweep's type has no type-definition record of OST_Cornices; it is the one id the sweep's record names that has a type object (tag db 0f) and no name entry, named by its element data, and the sweep is Wall Sweep:<type>:<ElementId>; a sweep naming none is a sweep of a wall type's structure and is named by its ElementId alone, as Revit names it. Snowdon: 258 of 258 wall sweeps named as Revit names them (249 typed, 9 by ElementId).",
      "user_impact": "Walls, floors, ceilings, roofs and railings show the type name Revit gives them (for example 8\" Interior Partition 3 Hour or Basement Slab) in the IFC property set and the viewer."
    },
    {
      "id": "type-text-parameters",
      "area": "elements",
      "name": "Type Mark, Description and Fire Rating of element types, on their instances (RE-77)",
      "status": "partial",
      "evidence": "partition_type_parameters::type_text_parameters; partition_schema_mvp::attach_type_text_parameters; tools/re/type_parameters_vs_vim.py; tools/re/type_parameters_vs_ifc.py; reports/element-framing/RE-77-type-text-parameters.md",
      "notes": "Value blocks open [owner u64][56 x 0xff][3 x 0x00]; a type's block holds some text parameters as i64 BuiltInParameter, u32 n, UTF-16 x n. -1001405 Type Mark, -1010103 Description and -1001206 Fire Rating were named by matching values against the VIM export of Snowdon Towers' 2027 edition. Snowdon Towers: Type Mark 1,257 of 1,276 equal to the VIM (the 19 others are empty there), Description 70 of 70, Fire Rating 696 of 696 equal to Revit's IFC4 export; RE1 Architecture Fire Rating 7 of 7. A type with conflicting values gets none. Instance parameters and numbers are not read; recall is partial (696 of 862 rated Snowdon elements).",
      "user_impact": "Doors, walls and other elements carry their type's Type Mark, Description and Fire Rating in the IFC, the viewer and schedules."
    },
    {
      "id": "ifc-export-overrides",
      "area": "ifc",
      "name": "Revit's IFC export overrides (Export to IFC As, IFC Predefined Type, and their type-level twins), from element data",
      "status": "partial",
      "evidence": "partition_ifc_export_overrides::find_export_parameters / scan_export_parameters / ExportParameters; ifc::category_map::lookup_export_override / predefined_type_for; tests/ifc_export_overrides.rs (CI tier 2); examples/probe_re45_ifc_export_parameters.rs; reports/element-framing/RE-45-ifc-export-parameters.md",
      "notes": "Revit's four IFC export parameters are entries i64 BuiltInParameter, u32 n, UTF-16 value in an element's serialised data: IFC_EXPORT_ELEMENT_AS (-1019014), IFC_EXPORT_ELEMENT_TYPE_AS (-1019015), IFC_EXPORT_PREDEFINEDTYPE (-1019016) and IFC_EXPORT_PREDEFINEDTYPE_TYPE (-1019017), public BuiltInParameter values. An entry belongs to the declared element whose element-data header (RE-44) is the last one before it, at most 0x800 bytes back (0x1ac measured); a value must have the shape of an IFC entity name or an enumerator. An element exports as its own override, else its type's (IfcCoveringType naming IfcCovering), with its own predefined type, else its type's, written only when it is an IFC4 enumerator of that entity. Honoured entities are the ones a reference export demonstrates: IfcShadingDevice (RE-22), IfcSlab and IfcCovering. Measured: on Core Interior every element rvt-rs writes has Revit's entity, and floors 20912 and 70325, which carry ifcSlab with ROOF, are IFCSLAB .ROOF. as in Revit's export (previously .FLOOR.); on teste_export_2025 (local only) the three walls of type 402, which carries IfcCoveringType with CLADDING, are IFCCOVERING .CLADDING. as in Revit's (previously IFCWALL). RE1, Snowdon Towers, Einhoven and Projeto1 carry no export parameter. The owners of IfcShadingDevice equal RE-22's owner-offset read, which no longer feeds the export. IfcSpace and IfcFooting overrides exist on Core only on elements Revit does not export and stay unhonoured. Revit's own category-to-entity mapping table is not read, and no IfcSlabType or IfcCoveringType is written. Revit 2024 and 2025.",
      "user_impact": "Elements a modeller set to export as another IFC entity or predefined type - a roof-like floor, a wall used as cladding - come out of rvt-rs as they do out of Revit."
    },
    {
      "id": "element-record-curtain-walls",
      "area": "elements",
      "name": "Curtain walls from Revit 2024 and 2025 element records: the walls their mullions name, aggregating their panels and mullions",
      "status": "partial",
      "evidence": "partition_schema_mvp::scan_curtain_grid_walls / CURTAIN_GRID_WALL_OFFSETS / CURTAIN_PANEL_WALL_CLASS, ifc::category_map (CurtainPanelWall -> IFCCURTAINWALL), examples/probe_re72_curtain_panels.rs, tools/re/aggregates_vs_ifc.py, reports/element-framing/RE-72-curtain-grids.md; partition_schema_mvp::attach_curtain_walls / CURTAIN_WALL_CLASS / CURTAIN_WALL_PART_CLASSES; ifc::category_map (CurtainWall -> IFCCURTAINWALL .NOTDEFINED.); tests/curtain_walls.rs against RE1 Architecture (CI tier 2); examples/probe_re46_curtain_walls.rs; reports/element-framing/RE-46-curtain-walls.md; RE-62: partition_schema_mvp::attach_curtain_walls (box containment, CURTAIN_PART_BOX_TOLERANCE_FEET) and reports/element-framing/RE-62-curtain-part-containment.md",
      "notes": "RE-72: a part names the curtain grid it lies on, and the grid's element data names its curtain wall (u64 at +85 and +478); that wall comes first, before RE-46's named wall and RE-62's box. On Snowdon Towers every one of the 2,075 curtain parts Revit aggregates has Revit's whole (16 panels had none and one, nested curtain wall 1593696, another), and the 18 panels that hold a basic wall export as IfcCurtainWall with their own body, as Revit's do, so all 60 of Revit's curtain walls are matched. A placed wall named in the reference list of a placed OST_CurtainWallMullions record is a curtain wall: on Snowdon Towers Architectural those walls are exactly the 42 Revit's IFC4 export writes as IfcCurtainWall, and on RE1 Architecture the one; no other wall in the corpus is named by a mullion. Six Snowdon walls named only by panels are basic walls used as panel infill, which Revit exports as IfcWall, so a panel alone does not make a curtain wall. The wall exports as a bodiless IfcCurtainWall .NOTDEFINED. (Revit's value on all 60 of its rows), and each panel and mullion whose list names exactly one curtain wall is its IfcRelAggregates part. Against Revit's aggregates: 1,798 of 1,906 parts on Snowdon and 12 of 13 on RE1 sit under the same whole; 89 late Snowdon mullions name no curtain wall (72 reach it through a record that names it, likely the curtain grid, not shipped), 18 panels Revit writes as nested IfcCurtainWall stay IfcPlate, and doors are not aggregated because 3 of the 9 Snowdon doors naming a curtain wall are standalone in Revit's export. Core Interior's export is byte-identical. No curtain grid, panel type or IfcCurtainWallType is read or written. Revit 2024 and 2025. RE-62 (2026-09-24): a part whose reference list names no curtain wall (Snowdon's 'Solar Panels' mullions name only one another) takes the one curtain wall whose record box contains its own within 0.05 ft; that is Revit's parent for 89 of 89 such mullions, and Snowdon parts with Revit's parent rise from 1,967 to 2,057, none with a wrong one.",
      "user_impact": "Curtain walls and storefronts come out as IfcCurtainWall with their glazing panels and mullions grouped under them, as in Revit's export, instead of a solid wall box drawn over the glass."
    },
    {
      "id": "element-record-stair-dimensions",
      "area": "elements",
      "name": "Stair and flight riser count, riser height and tread length from Revit 2024 element data, as Pset_StairCommon / Pset_StairFlightCommon",
      "status": "partial",
      "evidence": "partition_stairs::stair_dimensions_at / run_riser_count_at / scan_stair_dimensions / flight_riser_counts; partition_schema_mvp (STAIR_* fields); ifc::entities::IfcEntity::ElementPropertySet and PropertyValue::PositiveLengthFeet; unit tests in partition_stairs, export_content and step_writer; examples/probe_re47_stair_dimensions.rs; reports/element-framing/RE-47-stair-dimensions.md",
      "notes": "A stair's element data carries, past ff ff ff ff fb 03 ff ff ff ff fb 03 00 00 00 00 (found within its first 0x100 bytes, at 0x63 or 0x6b on Snowdon), its riser height (f64 ft, +16), tread depth (f64 ft, +24) and number of risers (u32, +88); a run's data carries its number of risers after fc ff ff ff ff ff ff ff 00 00 00 02 00 00 00. A flight's count is the stair's for a single run, else its own when the runs add up to the stair's. They are written as Pset_StairCommon / Pset_StairFlightCommon NumberOfRiser (IfcCountMeasure), RiserHeight and TreadLength (IfcPositiveLengthMeasure), the sets and types Revit's export uses, through a new IfcEntity::ElementPropertySet. Measured on Snowdon Towers (local only) against Revit's own values for the same Tags: stairs 26 of 26 on all three; flights RiserHeight and TreadLength 43 of 43, NumberOfRiser 37 of 43, the other 6 being both flights of the three stairs Revit's export splits into two IfcRelAggregates, where it writes the stair total (24) on each flight and rvt-rs the runs' 13 and 11. NumberOfTreads is not written (risers minus runs on 23 of 26 stairs; the other 3 depend on an unread begin/end-with-riser setting), no stair geometry changes, and no licensed CI file has a stair. Revit 2024 only.",
      "user_impact": "Stairs and flights carry the riser count, riser height and tread length Revit schedules them by, in the standard IFC property sets, and the viewer shows them."
    },
    {
      "id": "element-record-beam-axes",
      "area": "elements",
      "name": "Beams along their location lines from Revit 2024 structural-framing element data",
      "status": "partial",
      "evidence": "partition_beam_axes::bounded_line_at / first_bounded_line / scan_bounded_lines / beam_body; partition_schema_mvp::attach_beam_axes (BEAM_AXIS_* fields); ifc::export_content RecordGeometry and BEAM_AXIS_BODY_SOURCE; unit tests in partition_beam_axes and export_content; examples/probe_re49_beam_axes.rs; reports/element-framing/RE-49-beam-axes.md",
      "notes": "A structural-framing element's data carries its location line as a bounded line record: 04 00 08 01, start and end parameters (f64 ft), an origin (3 f64, model ft) and a unit direction (3 f64), found in the data up to the next element-data header. With the element's record box, beam_body solves a box length x width x depth along the line: the length is the line's, the depth comes from the vertical extent, the width from one plan extent, and the other plan extent must come out to 0.01 ft. A level beam exports as Extrusion::rectangle(length, width, depth) rotated to the line's plan angle; a sloped one keeps its record box as the extrusion and sweeps its section along its centreline (SolidShape::SweptPath, IfcFixedReferenceSweptAreaSolid with +Z as the fixed reference). On Snowdon Towers Structural (local only) all 942 exported framing elements carry a line, 940 inside their box, and 923 solve (802 level on a model axis, 111 level rotated in plan, 10 sloped); IfcOpenShell meshes each exported solid to its record box (938 to 0.001 ft, 4 to 0.0016 ft). Against the meshes of the 2027 edition's VIM export, scored only where that edition kept the beam's Location, type and mesh consistent (839), the section across the line equals Revit's on 613 and Revit's less the top a floor join cuts on 179; the ends equal Revit's on 485 and Revit trims 352 at their supports (median 0.625 ft at the larger end), which is not read. Rotated beams' solids are 1.108 times Revit's own beam envelope at the median, against 8.406 times for the record box they replace. Status is partial: ends are not trimmed, floor-join cuts are not applied, the section is a rectangle envelope, 17 beams (15 oblique with skewed ends) keep their box, and no licensed CI file has structural framing. Revit 2024 only.",
      "user_impact": "Beams, joists and girders export along their own axes instead of as boxes around them, so a diagonal beam is a beam, not a slab of the bay it crosses."
    },
    {
      "id": "element-record-roof-outlines",
      "area": "elements",
      "name": "Roof outlines from their sketch lines, sketch lines' exact recorded ends, and shed roofs along their slope (Revit 2024)",
      "status": "partial",
      "evidence": "partition_schema_mvp::sketch_plan_profiles / attach_roof_profiles; element_record_plan_profiles::plan_profile_from_lines; partition_beam_axes::scan_bounded_lines; unit tests in element_record_plan_profiles; examples/probe_re50_roof_profiles.rs; reports/element-framing/RE-50-roof-outlines.md; partition_roof_slopes::edge_slope / scan_edge_slopes; partition_schema_mvp::attach_roof_slopes / roof_slope_from_fields; ifc::export_content::roof_slope_solid (BodySource partition_roof_slope); ifc::body_geometry::sloped_slab_mesh; reports/element-framing/RE-56-roof-slopes.md",
      "notes": "A roof's OST_SketchLines records carry the roof's ElementId as their owner reference, as a floor's do (RE-25), and close into its plan outline under the same box solve. A sketch line's element data also carries the bounded line record RE-49 reads for beams (04 00 08 01, start and end parameters, origin, unit direction), so its exact ends are known. Where the box solve does not close an element, its lines' recorded ends are chained into loops, provided every line is level with both ends in its own record's box and every end is shared by exactly two lines; otherwise the element keeps no profile. Profiles that closed before are unchanged. Measured on Snowdon Towers Architectural (local only) per Tag against the upward-face plan area of Revit's own IFC4 geometry (IfcOpenShell 0.8.5): 13 of 20 roofs carry an outline (none did), 6 equal to Revit's area, 5 equal per layer (Revit writes layered roofs as stacked layer solids) and 2 larger (5.4% and 34.7%, cut in Revit's geometry); every roof outline's plan box is its record box. Slabs rise from 117 to 132 of 199 with an outline; the 15 added are 11 equal (4 outright, 7 per layer) and 4 larger by 0.2% to 3.0%, the shaft-opening residual earlier outlines also show. RE-56: each roof sketch line's data carries its edge's slope angle (9:12 by default) and flags (00 00 01 00, or 01 01 01 00 on an edge that defines the slope, followed by -rise/run); a roof type's layers follow its name and u32 0. A 2024 roof with exactly one defining edge is drawn rising from it at its slope with its type's thickness square to the slope, as an IfcFacetedBrep, only when that reproduces its record box height within 1e-4 ft: on the Autodesk tutorial house (local only) the one sloped roof, 1:12 over 20.458 ft with a 1.25 ft type, gives 2.9591938797 ft against the recorded 2.959193880 ft. Status is partial: hip and gable roofs (two or more defining edges) and roofs sloped some other way (Snowdon's 2112673 has no defining edge but is sloped in Revit's export) stay level plates of their record box's height, cuts are not subtracted, 7 roofs keep their box, no licensed CI file has a roof, and the recorded ends are read on Revit 2024 only.",
      "user_impact": "Roofs and more floors export with the shape of their sketch instead of the rectangle around it."
    },
    {
      "id": "element-record-stair-treads",
      "area": "elements",
      "name": "Stair runs drawn as their treads and risers, from the run's sketch and its run type (Revit 2024)",
      "status": "partial",
      "evidence": "partition_stairs::run_sketch / run_type_at / scan_run_types / scan_run_lines / run_side_profile; partition_schema_mvp::attach_stair_run_bodies (STAIR_RUN_* fields) and stair_run_body_from_fields; ifc::export_content STAIR_RUN_BODY_SOURCE; ifc::entities::SolidShape::PlacedExtrusion in step_writer and body_geometry; unit tests in partition_stairs, export_content, body_geometry and step_writer; examples/probe_re52_stair_treads.rs; reports/element-framing/RE-52-stair-treads.md",
      "notes": "A straight run's element data holds its plan sketch as bounded line records (04 00 08 01) at its base elevation: two boundary lines from the first riser to the last, then a line across the run at each riser. The run type its reference list names (OST_StairsRuns type definition) has a serialised object 01 00 00 00 + u64 id with tag db 0f at +0x47, structural depth, tread thickness, riser thickness and nosing length as f64 ft at +0x59, +0x69, +0x71, +0x79, flags monolithic / treads / risers / slanted risers at +0xbd..+0xc0, a flag at +0xb5 set on the one type whose treads run under the riser above, then its name. With the stair's riser height (RE-47) the run's side view is extruded across it as an IfcExtrudedAreaSolid with a stated Position. Measured on Snowdon Towers (local only) against Revit's IFC4 and a VIM: 34 of 43 runs drawn; the 30 steel-pan runs equal Revit's geometry to 1e-5 ft; the 4 with upright risers square the nosing Revit shapes with a nosing profile, and one runs its first riser short of Revit's below a landing. Monolithic, riserless, spiral runs and runs whose riser lines outnumber their risers keep their record box. Run type names equal the type part of Revit's ObjectType on all 49 exported flights. 2024 only.",
      "user_impact": "Stairs export and display as steps instead of a box the height of the whole stair."
    },
    {
      "id": "element-record-wall-layers",
      "area": "elements",
      "name": "Walls drawn as their compound layers in their materials' shading colours (glTF and viewer, Revit 2024 and 2025)",
      "status": "partial",
      "evidence": "partition_materials::scan_material_appearances / appearance_at; partition_compound_structure::scan_type_layers / find_layers / scan_wall_flips; partition_schema_mvp::attach_wall_layers and element_layers_from_fields; ifc::IfcModel::element_layers; ifc::body_geometry::layered_extrusion_meshes; ifc::gltf per-layer nodes; examples/probe_re53_wall_layers.rs; tools/re/wall_layers_vs_ifc.py (rvt-gltf GLB against Revit's IFC4 per-layer solids); reports/element-framing/RE-53-wall-layers.md",
      "notes": "A material's object (01 00 00 00 + u64 id, tag 28 0a on 2024 and 6b 0a on 2025 at +0x47) holds, in its first ff x 8 frame, transparency, four pattern COLORREFs, the shading COLORREF and shininess; colour and transparency equal Revit's IFC surface style on 123 of 123 materials matched by name across Snowdon Towers, Core Interior, RE1 Architecture, Projeto1 and teste_export_2025. A wall, floor, roof or ceiling type's own element data holds a u32 layer count (framed by ff ff ff ff and tag a6 10 on 2024 / 0e 11 on 2025, or u32 k, k x 2d 00, u32 0 on 2025 floors and ceilings) and 37-byte layers: f64 width, u64 material or by category, u64 deck, u32 function; Snowdon 42 of 42 types give Revit's constituent sequence, Core Interior 4 of 4, RE1 its wall, floors and ceiling. A wall's data holds, after ff ff ff ff 01 00 00 00, its location line, a word and a flip flag; set puts the exterior right of the location line (RE-49's bounded line), on all 416 Snowdon walls whose outer layers Revit's IFC places measurably apart. The glTF export cuts the wall's body into one band per layer where the layers add up to its thickness within 0.02 ft. Measured end to end on Snowdon Towers (local only): rvt-gltf draws 883 of the 1,054 walls whose layers are read; against Revit's IFC4 per-layer solids (IfcOpenShell 0.8.5), every layer centre is within 0.001 ft on 851 of 862 comparable walls and within 0.126 ft on all (the 11 are one soffit type whose stud layer Revit cuts back), and 1,368 of 1,368 compared layer colours equal Revit's surface style colours. Status is partial: 165 Snowdon walls whose body is not as thick as their layers are drawn whole (#358); floors, roofs and ceilings are not drawn in layers; Revit 2025 walls are drawn in layers since RE-55, but no 2025 oracle wall has a layer whose material is unique, so their layers are measured only through the same house saved in 2024 (reports/element-framing/RE-55-wall-lines-2025.md); the IFC export writes these layers as IfcMaterialLayerSetUsage since RE-58 (row ifc-material-layer-sets); no licensed CI file has a 2024 wall with a coloured layer.",
      "user_impact": "In the viewer and exported glTF, walls show their construction: each layer as a band in its material's colour instead of one grey block."
    },
    {
      "id": "element-record-slab-layers",
      "area": "elements",
      "name": "Floors, roofs and ceilings drawn as their layers stacked top first in their materials' colours (glTF and viewer, Revit 2024 and 2025)",
      "status": "partial",
      "evidence": "partition_schema_mvp::attach_slab_layers (SLAB_LAYERS_FIELD) and element_layers_from_fields; ifc::ElementLayers::stacked; ifc::body_geometry::stacked_extrusion_meshes; ifc::gltf per-layer nodes; tools/re/slab_layers_vs_ifc.py; examples/probe_re53_wall_layers.rs; reports/element-framing/RE-57-slab-layers.md",
      "notes": "A floor, building pad, roof or ceiling with a type (RE-44) gets its type's layers (RE-53; roof types framed behind their name, RE-56), top first, where they add up to its record box's height within 1e-4 ft; the glTF export cuts its extruded body into one prism per layer from the top down. Measured end to end against Revit's own IFC4 exports (IfcOpenShell 0.8.5): Snowdon Towers (local only) 236 compared slabs with every layer's bottom and top within 1.4e-6 ft and 358 of 358 colours equal; 2024_Core_Interior 68 of 68 floors within 3.4e-6 ft, 69 of 69 colours; RE1 Architecture's 8 floors and ceilings (2025) cannot be measured by height because Revit styles each body with one material, and their 8 colours equal Revit's in its layer order. Status is partial: slabs whose layers do not add up to their box (tapered insulation, floors with taller boxes) and sloped roofs stay whole. The IFC export writes these layers as IfcMaterialLayerSetUsage since RE-58 (row ifc-material-layer-sets).",
      "user_impact": "In the viewer and exported glTF, floors, roofs and ceilings show their build-up: finish, structure and insulation as coloured bands."
    },
    {
      "id": "ifc-material-layer-sets",
      "area": "ifc",
      "name": "Material names by ElementId, and IfcMaterialLayerSetUsage for layered walls, floors, roofs and ceilings (Revit 2024 and 2025)",
      "status": "partial",
      "evidence": "partition_materials::name_at / scan_material_names (material_name_frame_tag, material_name_end_tag, NAME_PARAMETER); partition_schema_mvp::attach_wall_layers / attach_slab_layers (layer names); ifc::material_layer_sets_from_layers and material_layer_set_usage; ifc::IfcModel::material_layer_usages; ifc::step_writer IFCMATERIALLAYERSETUSAGE; ifc::scene_graph::PanelLayers; examples/probe_re58_material_names.rs; tools/re/layer_sets_vs_ifc.py; reports/element-framing/RE-58-material-layer-sets.md; reports/element-framing/RE-61-system-family-names.md",
      "notes": "A material's name is in its own object (RE-53's 01 00 00 00 + u64 id, class tag at +0x47): in its own name field after two to four zero u32s; or, where that field holds parameter entries first (strings i64 id + u32 n + UTF-16, integers i64 id + u32), as the one u32 n + UTF-16 ending at the first ff ff ff ff 17 0c (2024) or 6b 0c (2025) past the tag; or in a framed string (ff ff ff ff 49 01 / 5c 01) or an i64 -1001203 entry further on. Copies that disagree, and names read for two materials, are dropped. Named: Snowdon Towers 219 of 220, 2024_Core_Interior 78 of 86, RE1 Architecture 69 of 69, Projeto1 and teste_export_2025 114 of 114, the MIT tutorial house 171 of 171 in 2024 and 2025. Every named material Revit's own export styles by that name has Revit's colour for it, 174 of 174. The IFC export writes a type's layers as IfcMaterialLayerSet (one per type name and layer sequence; each layer its material and thickness) and each element's IfcMaterialLayerSetUsage: floors, roofs and ceilings AXIS3 NEGATIVE from their top, walls along the local axis their exterior faces, exterior layer first; only where the body is an extrusion whose extent across the layers equals their sum within 1e-4 ft. Measured end to end (IfcOpenShell 0.8.5) against Revit's own exports: layer names Revit's, in Revit's order, on 3,321 of 3,322 Snowdon layers, 89 of 89 Core Interior and 37 of 37 RE1; whole sets on Revit's body within 0.001 ft on 1,196 of 1,207 Snowdon elements (the 11 are RE-53's soffit walls whose stud layer Revit cuts back), 88 of 88 Core Interior, 15 of 15 RE1. RE1's 2025 export writes its own 4 layer sets, and rvt-rs writes the same 4 with the same thicknesses in the same order; Revit's own usage disagrees with its own body on RE1's 6 ceilings and 2 floors, and rvt-rs follows the body. Status is partial: sloped roofs, walls drawn whole (#358) and slabs whose layers do not add up to their box get no layer set; Snowdon 1924356 and 8 of Core Interior's family materials are unnamed; releases before 2024 read no names and keep the string heuristic for the IFC's material list, which on 2024 and 2025 is the file's named material records (#34); family instances get no material. RE-61 (2026-09-24): each set is named Family:Type as Revit names it; Revit stores no system family name, and a type with compound layers is a Basic Wall, Floor, Compound Ceiling, Basic Roof or Pad by its category (partition_schema_mvp::layered_system_family); set names equal the Family:Type of Revit's element names on 1,210 of 1,210 Snowdon elements, 88 of 88 Core Interior and 15 of 15 RE1, whose own four layer sets carry the same names.",
      "user_impact": "Exported IFC carries each wall's, floor's, roof's and ceiling's build-up (every layer's material and thickness, placed on the element) for quantity take-off and energy tools, and the viewer lists a selected element's layers."
    },
    {
      "id": "element-record-wall-centreline-bodies",
      "area": "elements",
      "name": "Wall bodies from their centreline at their type's thickness (Revit 2024 and 2025)",
      "status": "partial",
      "evidence": "partition_compound_structure::wall_orientation / scan_wall_orientations; partition_schema_mvp::attach_wall_layers (m_wall_axis_*, m_wall_type_thickness) and wall_axis_from_fields; ifc::export_content::wall_centreline_body (BodySource partition_wall_centreline); tools/re/wall_bodies_vs_ifc.py (rvt-gltf GLB against Revit's IFC4 bodies); reports/element-framing/RE-54-wall-bodies.md; partition_compound_structure::scan_wall_lines (WALL_LINE_SUPPORTED_REVIT_VERSIONS); reports/element-framing/RE-55-wall-lines-2025.md; partition_compound_structure::scan_wall_join_partners, element_record_wall_joins::butt_joins (m_wall_join_*_reach / _wall / _through; JoinReachStart / JoinReachEnd, JoinStartWallElementId / JoinEndWallElementId, JoinStartRunsThrough / JoinEndRunsThrough), scene_graph::element_info_panel joins; element_record_wall_joins::layer_reaches (m_wall_join_*_layer_reaches), ifc::export_content::layered_wall_profile (BodySource partition_wall_centreline_layer_joins), body_geometry::clip_ring_to_band, examples/probe_re71_layered_joins.rs, tools/re/wall_layer_joins_vs_ifc.py, reports/element-framing/RE-71-layered-wall-joins.md, examples/probe_re70_wall_join_lists.rs, tools/re/wall_join_lists_vs_ifc.py, reports/element-framing/RE-70-wall-join-lists.md; element_record_wall_joins::tee_join, examples/probe_re73_tee_joins.rs, tools/re/wall_tee_joins_vs_ifc.py, reports/element-framing/RE-73-wall-tee-joins.md; element_record_wall_joins::angled_layer_edges / angled_tee_edges (m_wall_join_*_layer_edge_reaches), tools/re/wall_angled_joins_vs_ifc.py, reports/element-framing/RE-74-angled-wall-joins.md; partition_beam_axes::bounded_arc_at / scan_first_bounded_arcs, partition_compound_structure::scan_wall_arcs, partition_schema_mvp::WALL_ARC_FIELDS / wall_arc_from_fields, ifc::export_content::wall_arc_outline (BodySource partition_wall_arc), examples/probe_re75_wall_arcs.rs, tools/re/wall_arcs_vs_ifc.py, reports/element-framing/RE-75-curved-walls.md",
      "notes": "The bounded line a wall's data stores (RE-49) is its centreline whatever its location-line setting: Revit's IFC4 body lies exactly half the type's layer sum (RE-53) either side of it on 964 of Snowdon Towers' 1,054 walls, all with 1 in the word after the setting; none of the 23 with 0 or 2 is centred. A 2024 wall with word 1 is built as that rectangle where the record box does not already give it: an axis-parallel wall keeps RE-26's trimmed run and keeps its box when the box is thinner than the type (a cut wall); an angled wall is the rectangle its box closes on (1e-4 ft), or runs joint to joint unless its box exceeds that rectangle's by more than 2 x max(T, 1 ft). Measured end to end on Snowdon Towers (local only) with rvt-gltf against Revit's IFC4 bodies (IfcOpenShell 0.8.5): angled walls' faces within 0.001 ft on 75 of 102 (0 before) and ends on 13 (1 before); axis-parallel faces on 927 of 976 (918 before); per wall 71 better, 1,003 unchanged, 4 worse; walls drawn in layers 967 (883 before). Core Interior is unchanged (IFC byte-identical but for timestamps). Status is partial: angled walls' ends at joints are not modelled (Revit butt-joins 115 of 156 end faces at two-wall joints), cut, profiled and word 0/2 walls keep their box, and releases before 2024 have no location line read. On Revit 2025 (RE-55) the same fields read the same on all 50 walls of a house saved in 2024 and 2025; against Revit's own 2025 exports, RE1 Architecture's 7 and Projeto1's 1 axis-parallel walls stay exact and 2 of teste_export_2025's 3 angled walls, drawn as boxes up to 38 ft off before, have Revit's faces and ends (the third is the wall that export shows in a later state, RE-44). RE-70 reads which wall runs through a butt join from the walls' own join lists (07 00 00 00, a per-document tag, 02 00 00 00, a zero u32 on 2025, a count and u32 + u64 ElementId + u32 entries): where two centrelines end at one point, the wall naming the other reaches its far face and the named one stops at its near face. On Snowdon Towers this agrees with every clean joint in Revit's IFC4 bodies (194 perpendicular and 18 angled ends, 2 undecided, none contradicted), and on five 2024 and 2025 files with the record box at 1,110 axis-parallel ends (47 undecided, 2 contradicted, both irregular in Revit's body). Ends take it only where both walls have a single layer: Revit cleans layered joins layer by layer (631 of 667 multi-layer ends irregular, 0 of 158 single-layer). Snowdon's walls with both of Revit's ends go from 338 to 346 axis-parallel, 9 better and none worse; Core Interior, RE1 and the MIT house are unchanged. Layered joins, angled joins at other than a right angle and T junctions keep their ends. Every decided end, layered or not, names its partner wall and whether it runs through in the IFC and in the viewer's element panel: 825 ends on 600 of Snowdon's 1,078 walls, 236 on 185 of Core Interior's 360. RE-71 draws layered joins layer by layer: counted from the outside of the corner, layer i of the through wall reaches the outer edge of the partner's layer i and layer i of the stopping wall its inner edge. It is applied where both walls show the same layer functions in the same order from the corner and share base and top, 528 Snowdon ends, of which 521 are right in every layer (1,382 of 1,402 layer ends); the body is then an IfcArbitraryClosedProfileDef staircase. Snowdon walls with both of Revit's ends: 516 of 976 axis-parallel (346 before) and 18 of 102 angled (13); GLB layer ends matching Revit's body 3,925 of 5,634 (3,078). Six Snowdon ends Revit draws as clean butts, which no field read separates (the join-list entry words and k were checked): 16 Snowdon layer ends get worse. RE1's one layered joint is a clean butt too, but RE1's export (IFC2X3 CV2.0, Revit 2026.2) writes each wall as one IfcRectangleProfileDef extrusion and cannot show layered ends; against it one wall's end gets worse. RE-73 draws T joints, where a wall's end lies part way along one other perpendicular wall's centreline at least half its width from that wall's ends: counted from the face it meets, each layer passes the other wall's layers of a larger function number and stops at the first of an equal or smaller one, and a single-layer wall stops at the face. On Snowdon 280 of 313 measured T ends are right in every layer (960 of 1,025 layer ends); walls with both of Revit's ends 675 of 976 axis-parallel (516 before) and 18 of 102 angled (unchanged); GLB layer ends 4,290 of 5,634 (3,925). 16 Snowdon T ends are clean stops at the face, which nothing read separates (wall types, the record box, RE-26's trims, the join lists, heights and layer materials were checked): 13 Snowdon walls lose an end they had. RE1's three T ends are clean stops in its export, which writes each wall as one rectangle; against it 3 of RE1's 7 walls lose an end. Core Interior's 202 T ends are right and its bodies unchanged; each now names the wall it stops against. RE-74 applies RE-70's, RE-71's and RE-73's joins where walls meet at 45 to 135 degrees, each layer ending where its two edges meet the other wall's line (a slanted IfcArbitraryClosedProfileDef edge). Revit draws Snowdon's 82.5 to 114.5 degree corners this way and none of its 14 L ends at sharper corners (7.5, 24.5 degrees) or shallower bends (148 to 172.5 degrees). Snowdon walls with both of Revit's ends: 34 of 102 angled (18 before), 689 of 976 axis-parallel (675); every angled L join drawn right along every layer edge (48 edges), 18 of 22 angled T joints (187 of 195 edges); GLB layer ends 4,394 of 5,634 (4,290); one wall worse (its interior finish). The other local files are byte-identical. RE-75 reads a curved wall's location arc: the RE-49 record with u64 1 before its tag (0 for a line) holds start and end angle, unit X and Y axes, radius and centre. On Snowdon's 24 walls with an arc axis in Revit's export, centre and radius match within 0.001 ft; each is drawn as the ring sector R +/- T/2 (IfcArbitraryClosedProfileDef, chords within 0.0005 ft, BodySource partition_wall_arc): faces within 0.001 ft on 23 (0 before), ends within 0.625 ft on all 48 (untrimmed at joins), median footprint overlap 0.95 (0.03 before). wall_bodies_vs_ifc.py scores walls with an arc axis by radius and angle as their own kind.",
      "user_impact": "Angled walls export and display as walls of their real thickness along their line instead of as the box around them, and more walls show their layers."
    },
    {
      "id": "revit-global-ids",
      "area": "ifc",
      "name": "Revit's own IFC GlobalIds for elements, rooms and storeys, rebuilt from Global/History and Global/ElemTable",
      "status": "partial",
      "evidence": "revit_global_ids::EpisodeHistory / revit_ifc_global_id / revit_global_ids; ifc::IfcModel::global_ids (RevitGlobalIds); tests/revit_global_ids.rs against 2024_Core_Interior_slim.ifc and the four RE1 exports (CI tier 2); unit tests pinned to Snowdon GlobalIds; examples/probe_re48_revit_global_ids.rs; reports/element-framing/RE-48-revit-global-ids.md",
      "notes": "Global/History lists every editing episode's GUID newest first (u32 count at +0x0e, a u32-counted table at +0x66, then the count again and count entries of a Windows-order GUID and the byte 0x28); a 40-byte Global/ElemTable record holds its element's episode number at +0x18, whose GUID is entry count-1-episode. The GlobalId is that GUID with the record's first ElementId XORed into its last 32 bits (an element whose id has since changed, RE-41, keeps its first), in IFC base-64. Per Tag against Revit's own exports, every element rvt-rs exports that Revit's holds has Revit's GlobalId: Core Interior 854 of 854, RE1 Architecture/Mechanical/Plumbing/Electrical 73/73/123/12, teste_export_2025 8, Projeto1 2, Snowdon Towers 5,945 of 5,945 (local only), with no duplicate GlobalId in any export. Rooms take the same rule (Core 116 of 116, RE1 11 of 11), and a storey takes its Level's (Core 15 of 15, Snowdon 18 of 18 since RE-51). Revit's own synthesised parts (stair components, a ramp's flight, extra slab pieces) have GlobalIds of Revit's making and are not written by rvt-rs; a Tag carried by several rvt-rs entities gives the rebuilt GlobalId to the first only. Revit 2023 and earlier (28-byte ElemTable records) keep generated GlobalIds, as do the project, site, building, openings, further pieces of an element, relationships and property sets. Since #400 a generated GlobalId is a SHA-256 of a fixed namespace, the document's Unique Document GUID and the entity's own identity (ElementId and piece, filling element, participants), never its position in the file: Snowdon Towers exported before and after RE-75's curved walls keeps all 20,149 GlobalIds (7,740 before #400). Status is partial: Revit 2023 and earlier, the project, site and building, and storeys whose Level is not recovered keep generated GlobalIds. Where it applies it is exact against Revit's own exports of two releases (Core Interior 2024; RE1 2025), CI-gated.",
      "user_impact": "An IFC file from rvt-rs and Revit's own IFC of the same model name every element, room and storey with the same GlobalId, so diffs line up and BCF issues, clash results and federated links carry over between them."
    },
    {
      "id": "element-record-revit-class",
      "area": "elements",
      "name": "Each record-backed element's Revit class, from the schema's definition ordinal (RE-76)",
      "status": "partial",
      "evidence": "formats::schema_classes (the Formats/Latest grammar, whole stream since #410); PartitionElementRecord::class_tag (+0x4a); partition_schema_mvp::attach_revit_classes; examples/probe_re76_schema_class_tags.rs; reports/element-framing/RE-76-schema-class-tags.md",
      "notes": "A class's serialization tag is its definition ordinal in the file's own schema (12 for the first, one more per definition, top-level or inline), and an element record's u16 at +0x4a is its class under that numbering. The grammar reads the whole schema on 2016, 2024, 2025 and 2026 samples (3,792 to 4,690 classes). Every record of ten categories resolves on Core Interior, RE1 Architecture and Snowdon Towers: walls SWall, ArcWall, FaceWall; floors Floor; rooms RoomElem; roofs ProfileRoof; stairs StairsElement; families FamilyInstance or, for type symbols, FamilySymbol. Snowdon's 32 ArcWall records are exactly the 32 walls RE-75 found storing an arc. Status is partial: SchemaTable.tag, class_tag_map and the Global/Latest walker still use the u16 after each class name (#154).",
      "user_impact": "Every exported element carries RevitClass, so curved walls, face-based walls, in-place families and type symbols can be told apart in the IFC and the viewer."
    },
    {
      "id": "elem-table-owner",
      "area": "elements",
      "name": "Owner ElementId per Global/ElemTable record",
      "status": "experimental",
      "evidence": "elem_table::ElemRecord::owner_id; tests/elem_table_corpus.rs owner_ids_are_declared_element_ids (Einhoven 338 of 2,614, Core Interior 22,368 of 26,425, every value declared); reports/element-framing/RE-31-elemtable-owner-field.md",
      "notes": "The field the layout detector anchors on (u32 at +0 on the 28-byte layout, u64 at +4 on the 40-byte one) is an ElementId when not all 0xFF. On Core Interior 87% of set values also appear in the element's own partition reference list, against a chance rate well under 1%. By category the named element is the curtain wall for curtain panels, mullions and grids, the sketch for sketch lines, and the model group for grouped elements; wall-to-wall, floor-to-floor and symbol-to-symbol pairs are recorded, not attributed, and 304 records on Core Interior name themselves. Not used by the IFC export: the reference export writes no curtain-wall aggregation or group to hold it to.",
      "user_impact": "rvt-elem-table and the library report, per ElemTable record, which element it belongs to; the IFC export does not use it yet."
    },
    {
      "id": "element-record-door-window-host-binding",
      "area": "elements",
      "name": "Door and Window host-wall binding from Revit 2024 partition element records",
      "status": "verified",
      "evidence": "partition_element_records::decode_reference_list + partition_schema_mvp::opening_instances_from_records; tests/iter_elements_typed.rs::core_interior_2024_door_window_host_wall_binding compares the emitted (host wall, opening) pairs against the reference export's own IfcRelVoidsElement + IfcRelFillsElement chain and requires exact set equality (138/138, 0 wrong, 0 missing); the magnetar-2024-core-interior-slim OctetProof verdict carries relations.IFCRELFILLSELEMENT inside the claimed surface, where rvt-rs, IfcOpenShell 0.8.5 and IFClite 7.1.1 emit byte-identical 138-pair sets",
      "notes": "Scope is two BuiltInCategories (OST_Doors, OST_Windows), one release band (Revit 2024) and one recorded edge. The carrier is a counted reference list that follows the element record's bounding box: a u32 length at +0x88 and that many u64 slots at +0x8c. The host is the slot immediately before the record's own ElementId, accepted only when it is one of the 360 ElementIds the RE-21 instance rule selects as exported walls, so a wrong read fails closed instead of inventing a host. Measured on 2024_Core_Interior.rvt: 273 of 273 door/window records give the correct host, and the emitted IfcRelFillsElement pair set equals Revit's own exactly - 138 of 138, no wrong host, no missing pair, no extra pair. Three fixed-shape readings were rejected and are recorded in RE-23: u64 at +0xac is right on 186 of 273 records, u64 at +0xa4 on 81, and 'the last two slots are (host, self)' on 192 - the list length varies (6 on every door record, 18 on every window record, up to 101 elsewhere) and 81 door records continue past their own id with a trailing type-symbol slot. The predecessor read is not a category discriminator: 528 of 638 wall records also have a wall predecessor, which is why the rule is applied only to the two categories RE-21 already identifies by BuiltInCategory. Emission is the IFC4 chain Revit uses - IfcOpeningElement (bodied with the door/window's own record bounding box, placed on the element's placement, not a wall-relative cut) plus IfcRelVoidsElement and IfcRelFillsElement. The opening's Tag repeats the filling element's, which is what the reference export does on all 138. The export's other 63 IfcOpeningElement - 42 in slabs, 20 in shading devices, 1 unfilled wall penetration - are not recovered: rvt-rs emits no opening that is not filled by a recovered element. This is not evidence for any other category or release.",
      "user_impact": "Doors and windows come out of a Revit 2024 project as IFC4 fills of a real host wall, so a viewer subtracts the opening from the wall and shows the door in it - matching Revit's own export relationship for relationship."
    },
    {
      "id": "element-record-slab-plan-profile",
      "area": "elements",
      "name": "Slab plan profile from Revit 2024 OST_SketchLines element records",
      "status": "partial",
      "evidence": "element_record_plan_profiles::plan_profiles_from_sketch_line_records wired through partition_schema_mvp::slabs_from_partition_category_records; reports/element-framing/RE-25-slab-plan-profiles.md; gated by tests/iter_elements_typed.rs::core_interior_2024_slab_plan_profiles (80 of 100 record-backed plates resolve a profile, and the 20 that do not are exactly the export's IFCSHADINGDEVICE Tag set) plus the unit gates in src/element_record_plan_profiles.rs",
      "user_impact": "A Revit 2024 floor slab exports with the polygon its sketch actually encloses - including its voids - instead of the bounding rectangle, so a courtyard ring comes out as a ring.",
      "notes": "Scope is one BuiltInCategory (OST_SketchLines, -2000045), one release band (Revit 2024), one recorded edge, and only the plates the RE-22 instance rule already recovers. The join is exact and byte-anchored: the last slot of the second counted reference list at +0x88 names the sketched element, and the 3688 sketch-line records on 2024_Core_Interior.rvt name 126 owners including all 80 exported IFCSLAB and all 20 exported IFCSHADINGDEVICE ids. No geometry takes part in the join. The reconstruction is a closure rule over the recorded segment bounding boxes, not a fit: a box degenerate on one axis contributes its endpoints, every other box is placed only when exactly one pair of still-open vertices fits it, and an ambiguous segment, a vertex that does not finish at degree 2, an unused edge, a zero-extent box or a loop under three vertices rejects the whole element. Measured against Revit's own export (IfcOpenShell 0.8.5, project plan frame, cyclic sequences with orientation normalised, tolerance 1e-3 ft per vertex): 80 of 80 slab profiles exact across 122 loops - 80 outer plus 42 rectangular voids - worst vertex deviation 1.563e-12 ft, and 2.842e-14 ft re-measured on the emitted IFC after the metre round-trip. 42 perimeter plates emit IFCARBITRARYPROFILEDEFWITHVOIDS (26-vertex outer ring, one rectangular void) and 38 emit IFCARBITRARYCLOSEDPROFILEDEF. Status is partial rather than verified because that agreement is measured and regression-gated but is not a field of either OctetProof claimed surface: no witness compares profile geometry, so this has not met the two-independent-witnesses bar in docs/verification-protocol.md. The 20 exported IFCSHADINGDEVICE are rotated plates whose 57 sketch-line boxes include one of zero extent and 21 diagonals with no axis-parallel anchor; the closure declines all 20 and they keep the record box rectangle with ProfileResolved false. The pre-RE-25 plan-loop scan is a measured dead end here: 2317 closed plan-polyline candidates across all eight inflated partitions, none with the plan bounds of any recovered plate, and no ordered vertex run of the export's polygon anywhere in the file at any stride. Walls, doors, windows and columns still carry the record bounding box; base/top Level ElementId binding stays open (#86). This is not evidence for any other category or release. A sketch of several separate loops is several pieces, each with the voids inside it; the exporter writes each further piece as another element with the element's Tag and a ':2', ':3' name suffix, as Revit's export does (#331): on Snowdon Towers (local only) the five such slabs match Revit's pieces. Loops that cross give no profile."
    },
    {
      "id": "element-record-wall-join-trim",
      "area": "elements",
      "name": "Wall body from Revit 2024 element records: run cut back by the joins the record names, plus thin-axis thickness",
      "status": "partial",
      "evidence": "element_record_wall_joins::joined_walls and ::join_trims wired through partition_schema_mvp::wall_instances_from_records; reports/element-framing/RE-26-world-coordinate-residuals.md and reports/element-framing/RE-29-join-reference-list.md; gated by tests/iter_elements_typed.rs::core_interior_2024_wall_join_trimmed_bodies (360 of 360 walls resolve, 322 take a trim), ::core_interior_2024_wall_joins_are_named_in_the_reference_list, the unit gates in src/element_record_wall_joins.rs including the_surviving_side_of_a_true_l_corner_is_still_over_trimmed, and the pinned ObjectPlacement x Position composition of wall 20800 in tools/ci/ifc_schema_arity.py",
      "user_impact": "A Revit 2024 wall exports at the length its joins actually leave it, with the wall's real thickness, instead of the untrimmed centreline-to-centreline box - and the join partners are read out of the file rather than inferred from geometry alone.",
      "notes": "Scope is one BuiltInCategory (OST_Walls, -2000011), one release band (Revit 2024), one recorded edge, and only axis-parallel walls - every wall on this file is axis-parallel, and a box with no distinguishable thin plan axis is declined. Three measured facts back it. First, the box's thin plan extent is the wall's thickness: on the newest frame of each element it equals the nominal thickness of the IfcWallType Revit's export assigns on 360 of 360 walls. Second, every non-zero delta between the box and Revit's Axis polyline over all 720 wall ends is exactly 0.25, 0.3333 or 0.75 ft - half of the 6-inch, 8-inch and 18-inch wall thicknesses on the file - and zero everywhere else, so an end is cut by half the thickness of the perpendicular recovered wall whose centreline lands on it. Third, since #274 (RE-29) a candidate must additionally be NAMED in this record's counted reference list at +0x88, and is tested for reach against its own cut-back run rather than its recorded one. All inputs are recorded bounding boxes and nothing is fitted; candidates disagreeing about their thickness decline the whole element, and a trim that would collapse the run declines it too. Measured against Revit's own export (world axis-aligned bounding box, max of the two corner deltas, tolerance 1e-3 ft; RE-26 used IfcOpenShell 0.8.5, RE-29 used tools/re/ifc_world_aabb.py, which reproduces every RE-26 table exactly): 336 of 360 walls exact at RE-26 and 351 of 360 after RE-29, up from 27 before either; correct wall ends 689 -> 703 (membership) -> 711 of 720; mean residual 0.2701 -> 0.0220 -> 0.0081 ft. Per element 15 walls improve, 345 are unchanged and 0 regress. Status is partial, not verified, for two reasons. The 9 ends still wrong are all over-trims at one side of a TRUE L CORNER - two walls whose runs both stop at the other's centreline with nothing continuing past, where Revit cuts exactly one of the pair. Two such corners exist on this file (one repeated on eight storeys) and no feature in the record orders the two sides: the survivor is the thicker wall once and an equal-thickness wall once, the x wall once and the y wall once, the higher ElementId once and the lower once, the longer run once and the shorter once, and the prologue flag words do not order them consistently either. The solver keeps cutting both rather than guessing, so the residual is recorded and pinned rather than hidden. And no witness compares body geometry, so this has not met the two-independent-witnesses bar in docs/verification-protocol.md. This models Revit's join cleanup on top of a carrier it does read; it does not read the per-pair butt/mitre decision itself. Emitted product counts, relations, storeys and the diagnostics sidecar are unchanged. This is not evidence for any other category or release."
    },
    {
      "id": "element-record-column-type-symbol",
      "area": "elements",
      "name": "Column section and join-cut body from Revit 2024 element records",
      "status": "partial",
      "evidence": "partition_element_records::PartitionElementRecord::type_symbol_reference and element_record_column_cuts::{difference_box, column_cut_boxes} wired through partition_schema_mvp::column_instances_from_records; reports/element-framing/RE-26-world-coordinate-residuals.md and reports/element-framing/RE-29-join-reference-list.md; gated by tests/iter_elements_typed.rs::core_interior_2024_column_type_symbol_join (256 of 256 columns join to symbol 5755) and ::core_interior_2024_column_join_cut_plan_extents (the cut plan rectangles reproduce Revit's own body-extent histogram exactly: 176 x 2.0x2.0, 38 x 1.6667x1.6667, 20 x 1.75x1.6667, 18 x 1.4167x1.6667, 4 x 2.0x1.6667 ft), plus the unit gates in src/partition_element_records.rs and src/element_record_column_cuts.rs",
      "user_impact": "A recovered column's profile is the one its family type declares, and its body is the prism minus the walls that cut it, so a column that Revit trims where a wall passes through it no longer exports oversized.",
      "notes": "Scope is one BuiltInCategory (OST_Columns, -2000100), one release band (Revit 2024) and one recorded edge. The type join is exact and byte-anchored: the last slot before the record's own ElementId in the counted reference list at +0x88 that is itself an OST_Columns record carrying placement kind 0xffff8000 (the type-symbol envelopes of RE-21 section 5). All 256 exported columns have exactly one candidate before their own id and it is 5755 (Column_Sqaure : 24 inch x 24 inch), which is the IfcColumnType.Tag Revit's own export writes for every one of them. The join ships with a fail-closed guard: the type section and the instance plan envelope must agree to 1e-6 ft or the join is dropped and the element keeps its envelope with ProfileResolved false. RE-26 left the body residual open - 176 of 256 columns were world-exact and the 80 that were not had a body Revit cuts inset from the full prism, which no section can produce. #274 (RE-29) closes it from the same +0x88 list: a column record names the walls that cut it, and the exported body is the record prism minus the UNTRIMMED record prisms of those walls - 256 of 256 exact at a worst residual of 9.4e-13 ft and a mean of 0.0000 ft, against 176 of 256 at 0.5833 ft / 0.1217 ft before, with 80 improved, 176 unchanged and 0 regressed. Substituting the join-trimmed wall runs scores 190 of 256, so a wall runs its full recorded length into a column it cuts. element_record_column_cuts::difference_box is fail-closed: it answers only when the surviving material fills its own bounding box exactly by volume, which on this file is true on every one of the 80 columns whose box shrinks and false on every one of the 69 interior slots, Ls and crosses, so a decline never emits material Revit removed and the emitted solid is never larger on any axis than what Revit kept. Status stays partial because the section is still only the symbol's bounding box and the cut is still a box difference rather than a recovered solid, and because no witness compares profile or body geometry - the OctetProof claimed surface scores counts and relations, not shapes. The one emitted count that moves is IFCPROPERTYSINGLEVALUE 9231 -> 9311, the new JoinCutWallCount provenance row per cut column; BodySource becomes partition_element_record_join_cut on those columns. This is not evidence for any other category or release."
    },
    {
      "id": "element-record-wall-type-join",
      "area": "elements",
      "name": "Wall type record and the wall to IfcWallType join from Revit 2024 element records",
      "status": "partial",
      "evidence": "rvt::partition_type_records::{type_definition_ids, unique_type_reference}; reports/element-framing/RE-28-wall-type-records.md; gated by tests/iter_elements_typed.rs::core_interior_2024_wall_type_record_join and ::core_interior_2024_material_record_family plus the unit gates in src/partition_type_records.rs; recorded in the walls and materials rows of both magnetar count manifests (tests/fixtures/project-counts/2024-core-interior-slim.json and 2024-core-interior.json)",
      "user_impact": "None yet in the exported file. The join is available to library callers; it is deliberately not written onto the emitted wall, because adding a property would move the committed witness observation hashes.",
      "notes": "Scope is one BuiltInCategory (OST_Walls, -2000011), one release band (Revit 2024) and one recorded edge. A Revit wall TYPE has the bbox-less RE-24 record shape - the 88-byte prologue and the ff ff ff ff ab 05 marker at +0x50 with no bounding box - and a third value of the RE-21 placement-kind word at +0x42, 0xffff8080, alongside a counted slot list starting at +0x56 that names the record's own id. Eight OST_Walls records carry that shape on 2024_Core_Interior.rvt (ids 1710, 1711, 3897, 11356, 17328, 17337, 17339, 17341); Revit's own export writes four of them as IfcWallType and never places the other four, so the record set is a superset by construction and is NOT used as an IfcWallType recovery on its own. What is measured is the join: every one of the 360 exported wall instance records names exactly ONE of the eight in its +0x88 reference list - never zero, never two - and it is the IfcWallType.Tag the export assigns, 360 of 360 with no wrong answer and no miss, against 356 of 360 for RE-26's positional references[1] read. The recovered distribution equals the export's own IfcRelDefinesByType distribution exactly: 129 / 127 / 100 / 4. That also settles RE-26 section 9 and #240: the four 18-inch Basement walls read 1851 at references[1] because the list is ascending and 1851 (a record of category -2009014, not a wall type) sorts first - an index artifact, not a second id space or a type-graph indirection. Status is partial, not verified, for two reasons. First, nothing is emitted: no IFC entity, property or relation changes, tests/fixtures/synthetic-project.ifc is byte-identical, every committed OctetProof observation and verdict is untouched, and no count in either manifest moves. Second, no witness scores a wall-to-type relation, so this has not met the two-independent-witnesses bar in docs/verification-protocol.md. Separately and explicitly NOT claimed: compound layer thicknesses. A 128 KiB sweep centred on each of the four exported wall-type records finds 0 runs of consecutive f64 summing to that type's nominal width, and the paired reference export is a ReferenceView_V1.2 file carrying 0 IfcMaterialLayerSet, 0 IfcMaterialLayer and 0 IfcMaterialLayerSetUsage, so #88's layer-thickness criterion has no oracle on this artifact and can be neither met nor refuted here. No layer set is emitted and none is invented. This is not evidence for any other category or release. Since RE-44 (#322) the join feeds the export: each wall carries its type's name as the TypeName property, equal to Revit's on 360 of 360 walls here, and the same join holds on Revit 2025 (RE1 Architecture). Still no IfcWallType entity or IfcRelDefinesByType is written."
    },
    {
      "id": "element-record-room-recovery",
      "area": "elements",
      "name": "Room recovery from Revit 2024 OST_Rooms partition element records",
      "status": "verified",
      "evidence": "partition_element_records::OST_ROOMS + partition_schema_mvp::rooms_from_partition_category_records + partition_room_parameters::scan_room_parameters; tests/iter_elements_typed.rs::core_interior_2024_room_instances_names_and_envelopes scores the recovered set, names, numbers and Level bindings against Revit's own full-project export (116/116 on every one, tolerance 0); tests/fixtures/project-counts/2024-core-interior-slim.json gates diagnostics.exported.by_ifc_type.IFCSPACE at 116 tolerance 0 with rooms_spaces status known; and the magnetar-2024-core-interior-slim OctetProof verdict now carries entity_counts.IFCSPACE inside the claimed surface, where rvt-rs, IfcOpenShell 0.8.5 and IFClite 7.1.1 agree exactly",
      "user_impact": "A Revit 2024 project's rooms come out as IfcSpace with their real Revit name and number, on the storey Revit puts them on, at the right place in plan and at the right floor-to-ceiling height - instead of a handful of name-only rows with a placeholder body.",
      "notes": "Scope is one BuiltInCategory (OST_Rooms, -2000160), one release band (Revit 2024) and one recorded edge. The category was measured rather than assumed: a brute scan of all 23470 decodable element records on 2024_Core_Interior.rvt histogrammed every BuiltInCategory present and scored each one's #211 instance-rule selection against the 116 room ElementIds the reference export carries in IfcSpaceType.Tag; OST_Rooms selects 116 and they are exactly that set (FP 0, FN 0) and no other category intersects it at all. The number, name and host Level come from the room's own parameter block, anchored on the owning ElementId with a confirmation copy at +0x3c, the Level at +0x44, the number's UTF-16LE length prefix at +0x1f5 and the name's 8 bytes past the end of the number - the same owner-at-a-fixed-offset framing RE-22 uses for the IFC Export As override, read forward instead of backward. It accepts 116 of the 26425 ids Global/ElemTable declares and they are exactly the rooms; every room is framed twice and no pair disagrees; recovery is all-or-nothing per room. Scored against Revit's own export: 116 of 116 names equal IfcSpace.LongName, 116 of 116 numbers equal IfcSpace.Name. Storey containment is exact too - all 116 bind through the RE-27 Level reference, each of the ten Levels named maps to exactly one IfcBuildingStorey, and the parameter block's own Level slot agrees on 116 of 116. The body is the record's bounding box, which is not a placeholder here: measured on the emitted IFC against the export's swept areas in world coordinates, the plan envelope is exact on 116 of 116 (worst 1.640e-06 ft, the writer's six-decimal metre rounding) and the vertical extent is exact on 116 of 116 (worst 1.421e-14 ft). What is NOT recovered is the boundary polygon: 76 of the 116 boxes equal their room's polygon exactly in area and 40 over-state it, so ProfileResolved stays false and #90 stays open for the polygon. That is a measured negative - searching every inflated partition byte for each of the 116 polygons as an ordered run of packed f64 at strides of 2, 3, 4 and 6 doubles, in both windings, absolutely and translation-invariantly, finds a longest run of 3 vertices against polygons of 4 to 22 vertices, and none of the 126 OST_SketchLines owners RE-25 recovers is a room. A room's boundary is computed from its bounding elements, not stored. See reports/element-framing/RE-29-room-instance-and-envelope.md. This is not evidence for any other category or release."
    },
    {
      "id": "record-level-storeys",
      "area": "elements",
      "name": "Revit 2024 Level names and elevations from partition Level records",
      "status": "verified",
      "evidence": "partition_level_records::scan_partition_levels + ifc::apply_partition_level_storeys; on 2024_Core_Interior.rvt the fifteen standalone OST_Levels records each own exactly one name/elevation parameter block, and all fifteen (name, elevation) pairs equal an IfcBuildingStorey Name/Elevation in both paired Revit exports - Basement 2 -40, Basement 1 -20, Level 1 0, Mez 1-2 15, Level 3/4/4 - Wall Layouts 1/2/3 at 31/46/61, Level 6..13 at 76/91/106/121/136/151/166/185.5 ft, no wrong elevation, no missing storey, no extra storey; tests/fixtures/project-counts/2024-core-interior*.json gate diagnostics.exported.storey_count at 15 tolerance 0 with levels status known, and both OctetProof verdicts carry entity_counts.IFCBUILDINGSTOREY and storeys.IFCBUILDINGSTOREY inside the claimed surface, where rvt-rs, IfcOpenShell 0.8.5 and IFClite 7.1.1 emit byte-identical fifteen-pair sets",
      "notes": "Scope is one BuiltInCategory (OST_Levels), one release band (Revit 2024) and one recorded edge. A Level record carries the same 88-byte element-record prologue but no bounding box - it ends at +0x56 where a column record's bbox would start - so it is decoded by its own reader, anchored on the 6-byte ff ff ff ff ab 05 marker at +0x50 and gated on the leading ElementId being declared in Global/ElemTable. Which of the 75 OST_Levels records on this file is a Level element is the same #211 instance test used for walls and columns: no container reference at +0x32, placement kind 0xffffef7f at +0x42. That selects exactly 15. The name and elevation come from a parameter block whose owner slot is a u64 at value-0x47, followed by a 56-byte 0xff sentinel run, three zero bytes and the u32 UTF-16 length; the elevation is an f64 in feet 55 bytes past an 8-byte marker (05 00 00 00 48 02 00 00) searched forward from the end of the name, repeated 153 bytes later, and both copies must agree. The run between name and marker is variable (347 bytes for fourteen levels, 363 for Basement 2), which is why the marker is searched rather than assumed. Fail-closed twice: a level with no accepted block emits nothing, and the whole recovery is discarded unless every Level record owns exactly one block and no two levels share an elevation - a partial recovery is never emitted as a smaller building. The names are asserted because the file states the pairing (the block is keyed by the Level's own ElementId), not because they were joined by rank; #213's rank join is what this replaces. Recovering a Level's own ElementId is not the same as binding a Floor or a Room to one - level-elementid-storey-bind stays unsupported (#86 / RE-20). Storey containment measured on this storey set was 801 of 872 elements when it landed and is 853 of 872 after #219 / RE-27 bound the record-named Level; see record-level-reference-storey-bind. This is not evidence for any other category or release.",
      "user_impact": "A Revit 2024 project exports with its real Level names and heights - Basement 2, Mez 1-2, Level 13 - instead of elevation-derived labels, and every storey the project has is present rather than only the ones with a column standing on them."
    },
    {
      "id": "record-level-storeys-2025",
      "area": "elements",
      "name": "Level names and elevations on every Revit 2024 and 2025 project, and the storeys they make",
      "status": "partial",
      "evidence": "partition_level_records::elevation_marker / record_marker / decode_name_block_at_with / find_name_blocks_with / decode_record_at_with / owned_level_ids / scan_partition_levels; tests/level_names.rs against the four RE1 exports (CI tier 2); unit tests in partition_level_records; examples/probe_re51_level_names.rs; reports/element-framing/RE-51-level-names.md",
      "notes": "RE-24's name block (owner ElementId at name-0x47, a 56-byte 0xff run, three zero bytes, u32 length, UTF-16 name) holds every Level's name and elevation on Snowdon Towers (2024) and the RE1, Projeto1 and teste_export_2025 models (2025), where it opens the Level's element data. The elevation marker is 05 00 00 00 and a per-release u16 (0x0248 on 2024, 0x025d on 2025); four plan extents follow and the elevation is 55 bytes past the marker. RE-24's last two marker bytes were the first extent's low bytes, zero on Core Interior's round extents. The confirming copy is the one preceded by the same 24 bytes as the first (153 bytes on on Core, 161 on Snowdon, 246 and 181 on RE1). Level frames in the second prologue take their partition record's id (RE-35), and a Level whose block names another element after its own id is not a storey. Measured against Revit's own exports: every storey written has Revit's name, elevation and GlobalId on Snowdon Towers (18 of 18, local only), each RE1 model (2 of 2, CI), Projeto1 and teste_export_2025 (2 of 2, local only) and Core Interior (15 of 15, unchanged); on Snowdon 3,379 of 3,380 elements are contained in Revit's storey, on RE1 Architecture 14 of 14. Status is partial: Revit 2023 and earlier are not read, what makes a Level a storey in Revit's export is not read, and Snowdon's structural model is refused because 8 of its 19 Levels carry 01 00 00 before the name length where every measured Level carries zeros.",
      "user_impact": "Projects saved in Revit 2024 and 2025 export and display their storeys with the Level names the team uses (L2, R1, Parking) at the right heights, with the same GlobalIds as Revit's own export, and elements sit on the storey Revit puts them on."
    },
    {
      "id": "record-elevation-storey-bind",
      "area": "elements",
      "name": "Storey elevations and containment from Revit 2024 element-record bounding boxes",
      "status": "verified",
      "evidence": "element_record_storeys + ifc::apply_element_record_storeys; on 2024_Core_Interior.rvt the 256 OST_Columns records carry 11 distinct base elevations (0, 31, 46, 61, 76, 91, 106, 121, 136, 151, 166 ft) and all 11 equal an IfcBuildingStorey.Elevation in the paired Revit export, measured with IfcOpenShell 0.8.5; IfcRelContainedInSpatialStructure 1 -> 11 with 794 of 872 elements bound after #212 (all 256 columns, all 132 doors, 355 of 360 walls, 51 of 100 record-backed plates), measured on the commit before #218 replaced the storey source",
      "notes": "Elevations only. The join is elevation equality within 1e-3 ft and fails closed on both a miss and an ambiguity, and the storey set is derived this way only when the recovered Level rows carry no elevation of their own. It does not recover which named Level sits at an elevation - 12 name candidates against 11 measured elevations is not a pairing, and a rank join mislabels (Level 6 at 91 ft where the export puts Level 7) - so storeys are labelled 'Elevation N ft'. Level ElementId binding stays unsupported (#86 / RE-20). Scope is Revit 2024 partition element records: only OST_Columns *supplies* elevations, a restriction measured in RE-21 (doors 11 of 11 also land on storeys, walls 12 of 13 - they add -40 ft at the cost of one non-storey elevation - and windows 0 of 6, because a window base is a sill height). Every record-bodied element *binds* to the column-derived set by exact match. The export's other four storeys (-40, -20, 15, 185.5 ft) carry no column record and are not recovered. Re-measured in RE-22 (#212): plates bind by their record TOP face, not their base — Revit hangs a floor below the level that hosts it, so 0 of 40 distinct slab base elevations is a storey elevation while 51 of 100 plates have a top that equals one, every one of them the storey Revit's own export assigns. Slab records are still not an elevation *source*: their 26 distinct top elevations would add -40 ft and 185.5 ft (two of the four storeys this recovery misses) at the cost of 13 values that are not storey elevations, each 0.1667 ft below a real one, so STOREY_ELEVATION_SOURCE_TYPES stays IFCCOLUMN alone. Superseded as the primary storey source on 2026-08-30 by record-level-storeys (#218, RE-24), which reads the Revit Level elements themselves and recovers all 15 storeys with their names; this path now runs only when no Level records are recoverable, and the binding half it contributes is unchanged.",
      "user_impact": "Columns from a Revit 2024 project land in storeys at their real elevations instead of all defaulting to the first storey; the storeys are labelled by elevation, not by Revit Level name."
    },
    {
      "id": "record-level-reference-storey-bind",
      "area": "elements",
      "name": "Storey containment from the Level ElementIds a Revit 2024 or 2025 element record names",
      "status": "verified",
      "evidence": "element_record_level_refs::unique_level_reference + ifc::apply_record_level_reference_storeys; on 2024_Core_Interior.rvt the counted reference list at +0x88 names exactly one of the fifteen recovered Levels on 121 of 146 OST_Floors, 267 of 320 OST_Doors, 206 of 1487 OST_Walls, 6 of 7 OST_Windows and 1 of 2 OST_BuildingPad placed instances, and on 0 of 463 OST_Columns, which all name two; over the 537 elements the #212 / #213 elevation join also answers, the two joins agree 537 of 537 with 0 disagreements; emitted storey containment 801 -> 969 of 970 building elements after #90 / RE-29 added the 116 rooms (853 of 872 when this landed) (IFCSLAB 44 -> 80 of 80, IFCSHADINGDEVICE 10 -> 20 of 20, IFCWINDOW 0 -> 6 of 6), gated by tests/iter_elements_typed.rs::core_interior_2024_storey_containment_is_evidence_backed; RE-59: element_record_level_refs::named_levels / base_constraint_level + partition_schema_mvp::resolve_base_constraint_levels (m_constraintLevelIds, LevelBindSource partition_element_record_base_constraint); examples/probe_re59_base_constraint_levels.rs; tools/re/storeys_vs_ifc.py; reports/element-framing/RE-59-base-constraint-levels.md; the same test now gates 970 of 970; RE-60: element_record_level_refs::level_at_or_below + partition_level_records::scan_level_objects + partition_schema_mvp::resolve_hosted_levels (m_referenceIds; LevelBindSource partition_element_record_host / partition_element_record_level_object); examples/probe_re60_hosted_levels.rs; reports/element-framing/RE-60-hosted-levels.md; RE-68: partition_schema_mvp::resolve_base_at_level / resolve_remaining_levels; element_record_level_refs::elevation_band_level; examples/probe_re68_remaining_levels.rs; tools/re/storeys_vs_ifc.py --any-copy; reports/element-framing/RE-68-remaining-storeys.md; LevelBindSource partition_element_record_plumbing_level_object (RE-68 §6)",
      "notes": "Scope is Revit 2024 partition element records, and the join is stated by the file rather than inferred: the host Level is a plain ElementId slot in the same counted list RE-23 reads for a door's host wall. Fail-closed three times over. A record that names no Level, or two or more, resolves to nothing and keeps whatever the elevation join gives it - a Revit column or wall carries a base and a top constraint and both are in the list, with nothing in the bytes saying which slot is which, so 344 of 344 OST_Columns records and 432 of 638 OST_Walls records that name a Level at all are declined. The id set the uniqueness test runs against is deliberately the broader one (every standalone OST_Levels record, not only the fifteen the RE-24 name / elevation recovery validates), so a Level the full recovery would drop still makes a record ambiguous; the binding half then uses only the validated fifteen, and a named Level outside them binds nothing. This is NOT the #86 / RE-20 typed level bind: level_bind.rs binds a typed Floor or Room through an m_level_id schema field, that field is still unrecovered, and level-elementid-storey-bind stays unsupported. On a release or file where partition_element_records or partition_level_records decline, the level-id set is empty and nothing changes - verified unchanged on the 2023 Einhoven sample. The reference side is measured for the rooms only (#90, RE-29): each of the ten Levels the 116 room records name maps to exactly one IfcBuildingStorey of the full-project export, and it is the storey that export aggregates the room into, 116 of 116. The rest are measured by RE-59 below. Elements with no recovered storey are now contained in the IfcBuilding rather than in storey[0], which had put 71 elements - some at 185 ft - into Basement 2 at -40 ft. See reports/element-framing/RE-27-level-reference-storey-bind.md. RE-59 (2026-09-23): a record naming exactly two Levels names its base and top constraint in no fixed order (lower first on 452 of Snowdon's 650 and 194 of Core Interior's 493), and its base constraint - the higher of the two at or below the record's base, else the lower; declined when they share an elevation - is the storey Revit's own IFC export contains it in on 650 of 650 Snowdon Towers, 482 of 482 Core Interior and 7 of 7 RE1 Architecture records. With it, measured per element against Revit's IfcRelContainedInSpatialStructure (a part takes its aggregate whole's storey; tools/re/storeys_vs_ifc.py): Core Interior 854 of 854 in Revit's storey (970 of 970 building elements bound through their own Level: 488 naming one, 482 naming two; the last unbound wall now bound); Snowdon 5,612 same (3,975 before), 294 on no storey (1,931 before; the 294 name no Level), 45 different (45 before); RE1 34 same and 0 on no storey (7 before), its other 39 contained by Revit in rooms; the MIT tutorial house 1 element on no storey (230 before). Records naming three or more Levels stay unresolved. RE-60 (2026-09-24): for a record naming no Level, a railing takes the Level of the one stair or ramp it names (Revit's storey on 73 of 74 Snowdon railings), and any other element takes the Level carried by the objects it names (01 00 00 00 + u64 id + u64 Level id; 6,435 on Snowdon) only where that is also the highest Level at or below its base (Revit's storey on 161 of 161; each reading alone is wrong on 38 and 8 light fixtures). Snowdon: 5,805 in Revit's storey, 102 on no storey, 44 different (one new: railing 1646280). RE-68 (2026-09-24, measured on Snowdon Towers against every copy Revit writes): a stair or ramp with no Level whose base sits exactly at one Level takes it, so a multistory stair's railings follow it; an element takes the Level of the one element of its own class its record names (the parts of a nested light fixture); slab edges, light fixtures, wall sweeps, generic models, hardscape and stairs take the Level their base gives, within a band that picks the Level just above within 0.25 ft, the Level at or below from 0.5 ft, and none in between. Snowdon elements on no storey 102 -> 5 (4 plumbing fixtures, which follow their Level object, and 1 slab edge in the band's closed part), 97 placed, 0 wrong. RE-68 §6 (#369): a plumbing fixture takes the Level its Level objects carry on its own, whatever its base gives (Revit's storey on 4 of 4 on Snowdon; for light fixtures, slab edges and wall sweeps that Level alone is wrong on 73 of 214, so they still need it to agree with their base). Snowdon: 5,949 in Revit's storey, 1 on no storey (slab edge 2060948), 1 on another (railing 1646280); the other local files are byte-identical.",
      "user_impact": "A Revit 2024 project exports with its floor plates, shading plates and windows on the Revit Level that hosts them, instead of unplaced because a plate hangs two inches below its level and a window sits at its sill height above it; and an element whose storey is unknown is visibly unplaced in the building rather than silently filed under the lowest basement."
    },
    {
      "id": "schema-field-wall",
      "area": "elements",
      "name": "Schema-field Wall decode (non-ArcWall)",
      "status": "unsupported",
      "evidence": "RE-19 negative on magnetar corpora; unchanged by RE-21",
      "notes": "No fail-closed schema-field Wall / 2024 ArcWall envelope suitable for production decode beyond the gated 2023 ArcWall path. RE-21/#211 recovers Wall instances from a different carrier (the partition element record's BuiltInCategory field, see element-record-instance-recovery); the schema-field mechanism this row names is still unsolved, and diagnostics keep reporting schema_field_wall_instances as unsupported.",
      "user_impact": "Arbitrary walls are not recovered as typed Walls."
    },
    {
      "id": "typed-door-window",
      "area": "elements",
      "name": "Typed Door and Window discrimination from the ArcWallRectOpening index",
      "status": "unsupported",
      "evidence": "RE-19: no reliable Door vs Window discriminator in the opening-index bytes; unchanged by RE-21 and RE-23",
      "notes": "RE-21/#211 recovers Door and Window *instances* and RE-23/#222 binds them to their host wall, both from the partition element record (see element-record-instance-recovery and element-record-door-window-host-binding). Neither touches the carrier this row names: the 2024 ArcWallRectOpening index bytes still hold no Door vs Window discriminator and no host claim, and its rows still surface only ElemTable-confirmed related ids. RE-19's negative stands.",
      "user_impact": "Opening-index rows are not turned into typed Doors or Windows, and carry no host claim of their own."
    },
    {
      "id": "level-elementid-storey-bind",
      "area": "elements",
      "name": "Floor/Room storey bind via Level ElementIds",
      "status": "unsupported",
      "evidence": "RE-20: proximity scans noise-dominated. Its premise that Level is absent from Formats was an artifact of the 64 KB schema scan: the whole schema carries Level (class 2422 on Revit 2024, #410). Storeys are bound from element records instead (record-level-storeys, record-level-reference-storey-bind)",
      "notes": "Bind plumbing stays fail-closed / corpus-idle. Floors/Rooms remain Unassigned by evidence.",
      "user_impact": "Scene-tree storey grouping for Floors/Rooms stays Unassigned."
    },
    {
      "id": "aparameter-host-joins",
      "area": "parameters",
      "name": "AProperty* host parameter joins",
      "status": "unsupported",
      "evidence": "Production iter_elements / Global/Latest candidate scans; #35 idle on magnetar corpora",
      "notes": "Honest empty Parameters row until carriers and host joins are proven.",
      "user_impact": "Element parameters are not exposed as a completed feature."
    },
    {
      "id": "ifc4-export-real-projects",
      "area": "ifc",
      "name": "IFC4 export from real Revit project files",
      "status": "partial",
      "evidence": "Synthetic fixtures validate; 2023 ArcWall IfcWall + partition Level/Floor/Room/Material emission; 2024 record-backed IfcWall / IfcDoor / IfcWindow / IfcColumn / IfcSlab / IfcShadingDevice with placement, envelope solid, measured slab thickness, recovered slab plan profiles (IfcArbitraryClosedProfileDef / IfcArbitraryProfileDefWithVoids) and storey containment; rvt-ifc --mode / --diagnostics",
      "notes": "Correct writer path exists. Real-file typed inputs remain incomplete. Scaffold success is not typed conversion.",
      "user_impact": "Valid IFC scaffold and narrow typed subsets; not general RVT-to-IFC."
    },
    {
      "id": "csv-schedules",
      "area": "ifc",
      "name": "Element and room schedules as CSV (rvt-schedule, viewer, Python)",
      "status": "partial",
      "evidence": "ifc::schedule_csv unit tests; tests/rvt_schedule_cli.rs (tier-1 shape; 2024_Core_Interior 970 elements / 360 walls / 132 hosted doors / 6 windows / 256 columns / 116 rooms); viewer Playwright download test",
      "notes": "One row per decoded element or room, so coverage is exactly the decode coverage: typed rows on Revit 2024 project files with element records, scaffold-only elsewhere. Unknown values are empty cells. No room area: room bodies are record bounding boxes, not boundaries.",
      "user_impact": "A spreadsheet of walls, doors, windows, columns, slabs and rooms without Revit, on files whose elements decode."
    },
    {
      "id": "converter-grade-rvt-ifc",
      "area": "ifc",
      "name": "Converter-grade production RVT-to-IFC",
      "status": "unsupported",
      "evidence": "docs/status.md; ROADMAP out-of-scope until corpus-proven typed elements + geometry",
      "notes": "Explicit non-claim (COR-001). Do not advertise rvt-rs as a production Revit IFC converter.",
      "user_impact": "Use Revit/API exporters or wait for MVP milestones when evidence lands."
    },
    {
      "id": "browser-viewer",
      "area": "viewer",
      "name": "Zero-upload browser viewer",
      "status": "partial",
      "evidence": "GitHub Pages; WASM no-network import gate; File Status / storey grouping",
      "notes": "Geometry and export readiness reflect decoded coverage. Synthetic fixtures are scaffold-only.",
      "user_impact": "Useful local inspection without upload."
    },
    {
      "id": "stream-level-writer",
      "area": "writer",
      "name": "Stream-level CFB writer / patch",
      "status": "partial",
      "evidence": "Always-on gen-fixture + MIT empty.rfa patch corpus; optional Autodesk matrix",
      "notes": "Controlled stream replacement only. Not semantic Revit editing.",
      "user_impact": "Useful for forensic/stream patches, not model authoring."
    },
    {
      "id": "semantic-revit-editing",
      "area": "writer",
      "name": "Semantic Revit field-level editing",
      "status": "unsupported",
      "evidence": "ADR-002 semantic write API gate",
      "notes": "No stable semantic write API until decoder/validation evidence is strong enough.",
      "user_impact": "Do not expect round-trip model edits."
    },
    {
      "id": "python-package",
      "area": "bindings",
      "name": "Python package (rvt)",
      "status": "partial",
      "evidence": "CI wheel builds; pytest; maturin/pyo3 bindings",
      "notes": "Useful for metadata/schema automation. Same honesty bounds as the Rust core.",
      "user_impact": "Scriptable inspection; not a full converter SDK."
    },
    {
      "id": "inspect-cli",
      "area": "cli",
      "name": "User-facing rvt-inspect CLI",
      "status": "partial",
      "evidence": "rvt-inspect human + JSON; export readiness; diagnostics sidecar fields",
      "notes": "Support triage without Revit internals. Reports align with this matrix.",
      "user_impact": "Clear health / coverage / next-steps for a single file."
    },
    {
      "id": "community-corpus-scaffold",
      "area": "validation",
      "name": "Community corpus open/schema/scaffold IFC check",
      "status": "partial",
      "evidence": "docs/corpus-hunt-2026-04-21.md: 222/223 real files pass open → schema → scaffold IFC",
      "notes": "Proves container/schema/scaffold health. Does not prove typed element recovery.",
      "user_impact": "Confidence in open/inspect on public samples; not conversion proof."
    },
    {
      "id": "export-source-coverage",
      "area": "diagnostics",
      "name": "Measured export source-coverage fractions (A10)",
      "status": "partial",
      "evidence": "src/ifc/mod.rs SourceCoverageDiagnostics::measure; docs/export-diagnostics.md",
      "notes": "A10: exported/geometry fractions measured from diagnostics counts; decoded_element_fraction divides by the distinct ElementIds Global/ElemTable declares, only when walker ≤ that count (fail closed otherwise); the header element_count it used before 2026-09-22 is a per-release constant. Not converter-grade completeness.",
      "user_impact": "Diagnostics sidecar reports honest measured fractions when denominators exist; otherwise unset/null."
    }
  ]
}