Skip to content
v0.18.0 npm

The 1889 tower, from Eiffel's plates

The eiffel demo builds the tower as it stood in 1889. Its large structure is Eiffel's own: every panel node sits where Plate XXXI of his 1900 album puts it. Everything below the panel scale — lattice struts, gusset plates, rivets — is placed by rule. The parts the album draws whole are built from their own figures: the uprights' sections, the stairs, the lifts, the ground, and the rooms of the first floor and the summit. This guide covers the sources, the rules, how the model was audited against the plates, and what it costs to cook.

The eiffel demo at the laced depth: the whole tower in three-quarter view on four stone pedestals, drawn in grey lines and wash on white paper, its lattice shadow laid on the paper to the right.
The laced depth, as the demo draws it: one CPU cook, one mesh per part class, one fixed sun 57° up. Open it at demos/eiffel/?laced.
312.275metres to the tip of the flag rod, 1889
29panels, from Plate XXXI
3.95 Mtriangles, laced and rivets
1,205,292rivet heads, as instances
6.16 scook, rivets depth
814[sourced] tags in SOURCES.md

Scope

Plates for the large structure, rules for everything smaller.

The tower's geometry divides at the panel. Above that scale the demo copies numbers: the node table of Plate XXXI, the floor levels of Plate I, the arch radii of Plates VII and XI, and the member sections printed in Plate XXXI's tables.

Below it, the demo states rules and applies them to every member the table implies. The rules say which bracing pattern a panel carries, how a web member is laced, where a strut stops short of the upright it meets, and where a rivet goes. The web members are not traced from a drawing.

Some parts are placed from figures of their own. The list grew with the model:

  • the summit's diagonal girders and its rooms;
  • the leg entretoises and one plan girder;
  • the uprights, plate by plate from Plate XXXI's sections, and their splices;
  • the arches' ironwork;
  • the plinths, supports and foundations;
  • the lift cabins and the stairs;
  • the erection's timber pylons and scaffolds.

Each depth is a different graph, not a different view of one.

  • Skeleton draws every web member as a solid bar of its outer section, and the built-up uprights as their web plates alone. The plan bracing's lattice girders are the exception: they are open chords with a Warren web at every depth.
  • Laced replaces each of those solid bars with four corner angles and their lacing. In the legs and the shaft, each is also trimmed at its joints and given gusset plates. Laced also adds three more things: what stands out of the uprights' webs (the cover strips over their butts, and in panels 1–5 the fins and channels), the arches' volutes and macarons, and the plates where two bars cross.
  • Rivets adds the rivets as instances.

Lacing and rivets are each set to off, streamed or full. Full cooks the depth once, for the whole tower. Streamed cooks it a cell at a time round the camera (see Detail).

Two more graphs sit beside these. ?rivets=full is the rivets depth cooked full, plus the rows along the built-up uprights as Plate IX draws them (panels 6–11's taken as in panels 1–5 [estimated]); those rows have no streamed form. ?build is the erection (see Erection).

The host page supplies materials, a light rig, a ground plane and unit assets for the instances: the rivet head, and the cabochon, gas globe, baluster and Roux chain pieces that the fittings and the chains resolve against. It generates no other geometry and repairs none.

It does act on what the graph hands it, at load or over time. It applies each rivet head's hand-drive, moves the lift cars, their machinery and their chains every frame, runs the flag as a cloth, reveals the erection day by day, and clips the tower in section (see Rendering).

Panel 2 of one leg at the skeleton depth: the web members as solid bars.

skeleton

The same pose at the laced depth: every bar is now four corner angles with X lacing.

laced

The same pose at the rivets depth: rivet heads at every lacing crossing and along the uprights' seams.

rivets

One camera pose at the three depths: panel 2 of a leg, on its lit face. The rivet heads in the third frame are about 19 mm in radius, the nominal head (each is driven a little differently; see Rivets), and resolve only at full size.

Provenance

The primary source is Eiffel's 1900 plate album.

G. Eiffel, La Tour de trois cents mètres (Lemercier, 1900), is on Gallica as full-resolution scans, each plate in two halves of about 6000 × 8800 px.

Every number read from a source is recorded in demos/eiffel/SOURCES.md with its source key and one of four tags:

  • sourced: printed on a drawing or stated by a source.
  • derived: computed from sourced numbers, with the arithmetic shown.
  • estimated: read off a drawing by eye, or a printed digit that is ambiguous.
  • measured: a pixel reading calibrated on a printed dimension. This tag is used from §10 of SOURCES.md on.

Where sources disagree, every value is listed. The constants the demo sets itself are marked [estimated] in the code: the 50 mm joint clearance, the gusset reach, the 100 mm joint pitch and the 19 mm rivet head.

814[sourced]
223[derived]
198[estimated]
185[measured]
2,635lines in SOURCES.md
PlateContentWhat the demo takes from itCited
XXXIElevation diagram at 1:1000, panel weights, member sectionsThe node table: height, outer and inner half-width of all 29 panels. Upright counts and member sections; the uprights' built-up sections, plate by plate.68
IGeneral elevation at 1:1000 with levelsFloor and terrace levels, the datum, the ground dimension chain, platform sides. The reference for the silhouette audits.84
II–VIFoundations, masonry, the machine room, supportsThe massifs and caissons, the plinths' dress, the stone seats, cast shoes and anchor bolts, the machine room under pile 3.66
VIIPanels 1–29 drawn in their own inclined face planes at 1:200In-plane closure checks, arch radii, belts, base ties, panel 29, the summit.193
VIII, IXPlan bracing; detail sheets at 1:100 and 1:50Plan bracing pattern and depth; strut, lacing and gusset details; the uprights' splices and seams.37
X, XIIDetail sheets: base ties, belts, joints, lacingThe splice at the bend into panel 5, the lacing's crossing rivets, the rivets on the belts' and ties' chord flats.22
XIDecorative arches, arch girders, first-floor girdersThe grande poutre, the arch's lower radius, the arcade's montants, the arches' cast ironwork.45
XIII–XXXGalleries, floors, fit-out, pavilions, lifts, stairs, names, erectionThe 1889 first floor, the upper floors, the summit, the lifts and stairs, the 72 names (XIX), the erection's cranes and temporary works (XXIV, XXV).505

The conflict rules are applied the same way throughout:

  • A plate beats a rounded official figure (57.63 m over the official 57 m).
  • A plate at a larger scale beats one at a smaller scale.
  • A printed number beats a pixel height.
  • A height is read on its plate's own vertical scale, never the horizontal one. Near its foot, Plate I is drawn at 15.27 px/m vertically against 16.14 px/m across (above the first floor, about 16.1 px/m). A reading of Plate XI taken with its horizontal scale put the arch foot 0.5 m low.
  • Where the album shows a post-1889 state, the 1889 state is taken from the text volume or from a figure titled for the 1889 Exposition.

A later reading does not overwrite an earlier one. The later section of SOURCES.md names what it supersedes, so a value is quoted from the last section that gives it.

Secondary sources are cross-checks, not inputs. C. Chouard's 2001 transcription of Eiffel's documents agrees with the plate's full widths, compared at two decimals, at 26 of 28 nodes. It differs at two: at IX it gives 16.47 against the plate's 16.46244, and at XXII 55.293 against 55.300, the value the plate's 27.10 and 14.10 give [derived].

A 2020 thesis that modelled the tower from the same drawings is cited where it is right and flagged where it is not. It reads the 36.00 m datum as 36 m of masonry, and gives panel 5 the slope of panels 1–4.

The large structure

Every upright is straight within a panel and turns only at a plate node.

Plate XXXI prints, for each panel, its height and its full widths at its bottom node. Which widths it prints depends on the height:

  • Below the second floor: the inner width and the leg's side.
  • From there to the merge at XI: all three, the outer and inner widths and the leg's side.
  • Above XI: one total width.

The model halves these into half-widths (defined below). The nodes are named by the plate's own Roman numerals, counting down from XXVIII at the bottom of panel 1 to I at the bottom of panel 28.

spec.ts carries the table as 30 rows: the supports (panel 1's line extended to the support centres), the 28 nodes XXVIII to I, and the top of panel 28. Nothing between two rows is stored. A height between nodes is linear interpolation, which is exact because the uprights are straight. The curve of the tower's profile is a 29-sided polyline.

XXVIIIXXVIIXXVIXXVXXIVXXIIIXXIIXXIXXXIXXVIIIXVIIXVIXVXIVXIIIXIIXIXIXVIIIVIIVIVIVIIIIIItop 28 57.63 1st floor115.73 2nd floor195.93 lift landing276.13 3rd floor293.215 lantern terrace300.515 top terrace312.275 flagpole top 16 uprights4 legs × a, b, c, d 12 uprightsa ends at XVII 8 uprightslegs merge at XI panels 1–515.00 m square legs panels 6–11legs taper to 10.41 m panels 12–17b and d converge panels 19–28each ×0.94612 of the last panel 29prismatic 10 m box 50 m
The node table, plotted. Each green mark is a row of NODES in demos/eiffel/spec.ts (Plate XXXI): outer silhouette in white, inner in grey, a horizontal at every node. Bracing is drawn as one X per panel; the real per-panel patterns are in The lattice. Floor decks at their Plate I sides. To scale, 2 units per metre.

Heights use two datums:

  • y is height above the ground at the tower's centre, the datum of the official floor heights.
  • h is height above the support centres, the datum Plate XXXI is drawn on.

The two differ by a constant: y = h + 2.50. A half-width is the horizontal distance from the tower's axis to a member's axis, measured perpendicular to a face, as a face-on elevation shows it.

PLAN AT NODE XXVIII, y 6.4 tower axis at (0, 0); x right, z down +x +z +z tower face: plane of the outer faces c b a d copy 1: (x, z) → (z, −x) copy 2: (−x, −z) copy 3: (−z, x) +x+z leg, built 56.2435 outer half-width 41.2435 inner half-width measured square to the faces (Plate XXXI prints the full widths) axis to c: 79.5403 = 56.2435 · √2, not a plate width axis 20 m +x+z LEG AT XXVIII, enlarged one leg is built here and copied to the other three a s = 2 (41.2435, 41.2435) d s = 3 (56.2435, 41.2435) b s = 1 (41.2435, 56.2435) c s = 0 (56.2435, 56.2435) face 0: c → b, outer face 2: a → d, inner face 1: b → a, inner face 3: d → c, outer ring order c, b, a, d = station s 0, 1, 2, 3 face f runs s = f → f + 1 faces touching a are inner 15.000 axis to axis (outer − inner, panels 1–5) 15.000 to the tower axis each upright: a 0.877 square in plan, its faces in the tower's faces; the axis leans 35.4° along (−x, −z): square to it the box is a lozenge 5 m
Plan at node XXVIII, y 6.4, to scale. The +x+z leg as built, its uprights c, b, a, d in ring order s = 0 to 3: faces 0 and 3 are outer, faces 1 and 2 touch a. The other three legs are its quadrant copies. The piles follow one compass, the plates': 1 Nord, 2 Est, 3 Sud, 4 Ouest, clockwise from above. The built leg is pile 4, and spec.ts carries the compass as data (PILES, FACES). Each lower upright is the text volume's 0.877 m square in plan [sourced, text], from which Plate XXXI's web widths follow. Its faces lie in the tower's face planes (see Uprights). The half-widths here, halved from the plate's full widths and measured from the tower axis square to a face, are 41.2435 inner and 56.2435 outer, the outer width at XXVIII being the inner + 30 [derived]. The distance to c along the diagonal, 79.5403, is not a half-width. spec.ts (Plate XXXI), legs.ts, lattice.ts.

The table closes. Summing Plate XXXI's printed panel heights from the supports gives the top of panel 29 at h = 273.63038 m; the plate prints the third floor at 273.63 m. The residual is 0.38 mm over 273.6 m. The structural nodes XXIII and XVII sit 0.23 m below the first and second floors, which the chain also reproduces exactly.

The node table and the floor levels are byte-identical in the first commit and the latest. spec.ts grew from 207 lines to 1,422 between them — the audit rounds, then the uprights as built, their splices and the plinths — and no node moved.

Architecture

TypeScript computes the plate data. The graph does the repeating, with library nodes only.

Eight modules build the tower: legs, shaft, first floor, summit, platforms, lifts, the flag and the ground. Under ?build, the erection's works and cranes join them. Each module runs at graph-build time. It reads spec.ts and computes what its parts need in TypeScript: station positions, masks, face normals, end angles, outlines. It then embeds the results in standard PCG-TS nodes. Each table becomes the literal rows of one pointList. What a point list cannot hold, per-primitive columns and masks, becomes field parameters.

Some outline work is the graph's too. A floor's deck is its outline less its wells, cut by polygonBoolean, and so is a summit slab with a well in it.

The TypeScript also draws the pieces that occur once, or differ each time they occur. What repeats, it draws once, and the graph copies it:

  • a platform's fit-out, in the frame of one face, copied onto its faces;
  • a bay, copied onto the bay stations;
  • the boxes: one extruded unit box, copied onto a point per block;
  • the ground's supports and massifs, and the lifts' machinery and wheels: one template each, copied onto their seats.

The copies are graph nodes, not vertex tables. The graph uses 22 node types at the skeleton depth, 24 laced and 27 with rivets, none of them custom, so it serializes to JSON. A test (tests/eiffelSkeleton.test.ts) asserts that the skeleton graph's round trip cooks to byte-identical positions.

BUILD TIME · TYPESCRIPT, BEFORE ANY COOK COOK · ONCE DRAW · THE THREE.JS PAGE spec.ts tables read off the plates NODES Plate XXXI, 30 rows MEMBERS section half-widths LEG_BELTS belts and bands FLOORS 57.63 … 312.275 FIRST_FLOOR · ARCH ENTRETOISE PLAN_BRACING MASQUE · … numbers, no nodes eight rule modules buildEiffel(1889, detail) legs.ts shaft.ts firstFloor.ts platforms.ts lifts.ts summit.ts + flag.ts, ground.ts helpers: lattice.ts, strut.ts compute stations, masks and tables; each source table enters as one pointList, columns and all: pointTable Graph standard library nodes setAttribute transformPoints orientAlongVector laced pointGrid pointList pointsToPath pathResample copyToPoints meshPrimitive sweepProfile promoteAttribute filterByExpression mergePrimitives removeAttribute mergePoints laced spawnInstances extrudePolygon connectByIndex 27 types, none custom JSON serializeGraph, then deserializeGraph: cooks to the same P, bytewise round-trips eiffelSkeleton.test.ts:2273 cook(graph) on the CPU outputs (10 of 17 classes) uprights bracing horizontals girders arches platforms masonry summit gussets laced + names rivets.* × 58 toBufferGeometry each geometry item Mesh MATERIALS[class] packedRivetMesh batches, against ASSETS instanced Mesh one per rivet batch rivet unit head rig. fitShadow shadow cam to mesh bounds meshes cast and receive shadow; rivet instances only receive the depth switch webMembers(…, detail) strut.ts:485, called for each web set by legs, shaft, firstFloor, summit DEPTH EACH WEB SET BECOMES GRAPH NODES OUTPUTS THE PAGE DRAWS skeleton no query faceSkin / faceBox one solid member, squared to faceN 1,704 22 types 67 16 classes + 51 more class meshes laced ?laced trimToJoints → latticeStruts 4 chords, 8 lacing bars, gussets 2,045 24 types 68 17 classes + 51 more class meshes rivets ?rivets the same + spawnInstances lacing, joint and seam rivets 3,427 27 types 126 68 as laced + 58 rivets.* class meshes + instanced rivet heads
Build time to draw. spec.ts holds the tables read off the plates. Eight rule modules and their helpers compute stations, masks, faceN and end sines in TypeScript and embed them in standard library nodes, the tables as pointList rows and the rest as field parameters; what repeats they draw once and copy in the graph. The graph serializes to JSON and cooks the skeleton depth back to byte-identical positions (tests/eiffelSkeleton.test.ts). At a whole depth the page cooks the graph once on the CPU; a streamed depth is cooked a cell at a time in a worker (see Detail). The page draws one mesh per class output, and one mesh per rivet batch, each head packed and rebuilt in the vertex shader. The depth reaches the modules as a Detail flag. Platforms, the flag and the ground do not take it, and the lifts take it and discard it. webMembers applies it to the web members. The legs and the shaft also use it to choose what stands out of the uprights and which crossings get plates, and the first floor uses it to add the arches' ironwork. The columns name a selection of the graph's 27 node types and of its 17 part classes. Not to scale.

Point lists and ramps

A table the TypeScript computes enters the graph through one helper, pointTable, as a single pointList node: a point per row, a column per attribute, the values written out.

Each column used to be a field instead, ramp(index(), stops) through indexTable, on a grid of points. Moving the tables to point lists took the skeleton graph from 2,363 nodes to 1,662 and its ramp stops from 161,856 to 16,073 (6e77795e). Those are the counts at that commit; the table below, recounted since, gives the skeleton 1,704 nodes. indexTable remains for what a point list cannot carry: primitive-domain columns such as faceN and the end sines, the edge rules' masks, and the chains' spawn.

The ramps that remain are compressed where they can be. A run collapses to its two ends whenever the ramp, interpolating between them, stores every entry of the run bit for bit. That holds for a run of equal values, and for a straight flight's treads, which are affine in the tread index. The largest ramp left has 344 stops at the skeleton depth and 3,432 laced.

The library's ramp was not always exact at a stop. An input landing exactly on an interior stop was answered as v0 + (v1 − v0)·1, which can round off the stop's own value. The byte-identity test that moved the tables to point lists found it, and the ramp now returns the stop's value there (fd41338d).

A ramp of more than 16 stops finds each point's segment by bisection, so a table with a stop per point costs n log n, not n². It used to scan its stops from the first. That is why the platforms module once cooked its prism lists in runs of at most 1,024 points: splitting took its cook from 0.80 s to 0.31 s then. The runs are gone with the lists they split. The platforms' boxes are copies of one unit box, and the few prism lists left are one table each.

Outputs and cost

Each module returns meshes grouped by part class, and the graph declares one output per class. The page also gets an output for each piece it moves or instances; under ?build, one for each of the erection's works; and, at the rivets depth, one for each rivet set, which is its own spawn:

OutputsCountWhat they areDepths
Part classes17uprights, bracing, horizontals, girders, arches, platforms, deckUndersides (the 1st and 2nd floors' bottom faces, split out of the platforms' decks), stairs, masonry, foundations, summit, gussets, names, machinery, groundIron, glass and silveredevery depth; gussets laced and rivets only
Moved or instanced51the lifts' cars, cabin glass, cables, pulleys, wheels, pistons, carriages and trucks (43), their four chains, three batches of fittings and the flagevery depth
works.*10the erection's temporary works?build only
Rivet sets581,205,292 heads in all; at ?rivets=full, 59 sets and 1,326,388 headsrivets, ?rivets=full

So the skeleton has 67 outputs (no gussets), laced 68, rivets 126 and ?rivets=full 127, as the next table shows.

DepthGraph nodesOutputsMesh trianglesInstancesCookGraph JSON
skeleton1,70467765,4242,648922 ms4.0 MB
laced2,045683,948,2242,6482.87 s6.1 MB
rivets3,4271263,948,2241,207,9406.16 s10.4 MB
rivets=full3,4481273,948,2241,329,036—10.7 MB

The columns come from two dates. Graph nodes, Outputs and Instances were recounted on 2026-10-18 from the page's depth queries, after deckUndersides became a class of its own (71193103). Mesh triangles, Cook and Graph JSON are from the measurement of 2026-10-02 (109f23f9).

  • Instances match tests/eiffel.golden.json and count every batch. The 2,648 at every depth are the fittings and the lift chains.
  • Mesh triangles are the cooked geometry alone. The instances draw their assets' triangles on top: 580,800 for the fittings and chains at every depth, and 80 for each rivet head. The page's total is 1,346,224 for the skeleton, 4,529,024 laced, 101,179,584 at rivets and 110,809,024 at ?rivets=full.
  • Cook: the measurement ran on an Intel i9-13900KF under Node 22.20, headless: a CPU cook of a freshly built graph through the page's own depth queries, median of seven after a warmup, the depths interleaved, the process pinned to the performance cores. The column is the median of three such processes. On the efficiency cores the same cooks took 1.7 to 1.8 times as long when last compared. ?rivets=full was not timed under this protocol; one cold, unpinned cook took 12.4 s.
  • Graph JSON is the compact serialized form, MB = 10⁶ bytes.

The machine was shared and busier than at the previous measurement: other processes held a median of 3.6 to 11.4 logical CPUs. Three control processes cooking the graph of the previous measurement (f2d5bc8) ran alternately in the same session. They came out within a few percent of these, so the cook time has not moved beyond noise. The node count and the JSON fell by about a third, the node count mostly from the move to point lists. The cooked outputs at all four depths hash identically to the committed golden file, tests/eiffel.golden.json.

0.0M0.2M0.4M0.6M0.8M1.0M1.2M1.4Mskeletonlaced and rivetstriangles per outputgirders1,331,784 · 85,896 skeletonbracing1,101,504 · 11,792 skeletonhorizontals419,136 · 4,920 skeletonarches307,392 · 112,704 skeletonplatforms223,761uprights190,736 · 16,880 skeletonstairs90,984gussets64,440 · absent at skeletonnames53,532masonry28,352silvered28,352summit20,970machinery17,280groundIron14,968glass14,880foundations9,644deckUndersides245
Where the triangles are. Triangles per part-class output, as the committed golden file (tests/eiffel.golden.json) records them at 71193103. Lacing multiplies the classes that hold web members: girders 85,896 to 1,331,784, bracing 11,792 to 1,101,504, horizontals 4,920 to 419,136. It adds the gusset plates (64,440). It also changes two classes without web members: the uprights, 16,880 to 190,736, as their cover strips, fins and channels appear, and the arches, 112,704 to 307,392, with their volutes and macarons. The other eleven classes do not change with depth: platforms (223,761), stairs (90,984), names (53,532), masonry and silvered (28,352 each), summit (20,970), machinery (17,280), groundIron (14,968), glass (14,880), foundations (9,644) and deckUndersides (245). The deckUndersides class holds the decks' bottom faces, split out of platforms (53f551f0); the two sum to the platforms' old 224,006. The rivets depth adds no triangles. Its 1,205,292 heads are instances of one 80-triangle head, which the page packs in 24 bytes and rebuilds in the vertex shader (64 bytes a matrix on the fallback path).

Rules

Regions differ by which rules apply, not by how they are built.

Each region is a lattice of stations: a ring of positions repeated at every level, with station (s, k) stored as point k·R + s. A leg's ring is its four corner uprights, in the order c, b, a, d; the shaft above node XI has eight, the four corners and the four face centres. A web member is an edge rule: one connectByIndex node joining each station to the station ds along the ring and dk levels up, masked by a table that says where the rule applies.

ONE LEG'S STATION LATTICE, UNROLLED schematic: levels evenly spaced, not to scale c s 0 b s 1 a s 2 d s 3 c s 0 (again: ring closed) face 0 face 1 face 2 face 3 k level y 0 supports 2.5 1 XXVIII 6.4 2 mid 11.9 3 XXVII 17.4 4 mid 22.9 5 XXVI 28.4 6 mid 33.9 7 XXV 39.4 8 mid 44.9 9 XXIV 50.4 10 XXIII 57.4 11 XXII 68.4 12 XXI 79.4 13 XX 90.4 14 XIX 100.6 15 XVIII 110.6 16 XVII 115.5 17 XVI 126.8 18 XV 137.5 19 XIV 148.1 20 XIII 158.6 21 XII 169.1 22 XI 179.1 0 1 2 3 88 89 90 91 12 12 13 21 23 a's stations: no rule keeps them panels 1–4: span 2 each diagonal runs node to node, crossing on the mid-level panel 5: span 0 belt girder instead panels 6–9: span 1 one cross per face panels 10–11: span 0 10: its own 8-station lattice 11: belt girder instead panels 12–17: span 1 faces 0 and 3 only: a ends at XVII supports: no rule XI: crosses end here; no horizontal edges(lat, id, ds, dk, keep) one member from (s, k) to (s + ds, k + dk) at every station the mask keeps (s, k) Horizontals ds 1, dk 0 CrossA ds +1, dk span CrossB ds −1, dk span (from s = f + 1) connectByIndex target: (k + dk)·R + ((s + ds) mod R) or −1 where keep(s, k) is false R = 4, station index = k·4 + s mod is floored: (0 − 1) mod 4 = 3 station 12: s 0 (c), k 3 (XXVII), span 2 Horizontals 3·4 + 1 = 13 CrossA2 5·4 + 1 = 21 CrossB2 5·4 + 3 = 23 the masks, per rule crossSpan(k): 2 at XXVIII–XXV, 1 at XXIII–XX and XVII–XII, else 0 Horizontals: not at XXVIII, XXIV, XXIII, XVIII, XVII (plates stand there) from XVII up: faces 1 and 2 dropped one grid of 92 stations throughout; regions differ only in which rules keep a station
One leg's 92 stations, unrolled (schematic, not to scale): 23 levels, c, b, a, d on each, station index k·4 + s, with c repeated at the right because the ring is closed. Every rule is edges(lat, id, ds, dk, keep), whose connectByIndex target is (k + dk)·R + ((s + ds) mod R), or −1 where the mask drops the station. Horizontals, CrossA and CrossB differ in ds, dk and their masks; the span is 2 in panels 1–4, 1 in panels 6–9 and 12–17, and 0 elsewhere.

Where one station grid describes a region, its parts differ only by which masks are set. The bracing, region by region:

WhereBracing
Panels 1–4Two diagonals per face that run node to node and cross on a mid-panel horizontal. A laced central post runs through the crossing, parallel to the uprights.
Panel 5A lattice belt.
Panels 6–9One cross per face, with a laced central post through the crossing as in panels 1–4.
Panel 10Two X's per face, either side of a post, over a band of flats.
Panel 11A lattice belt.
Panels 12–17One X per panel. Above node XVII the inner upright a has ended, and in panels 12–16 a gap X between adjacent legs braces b of one leg to d of the next.
Above XIThe four legs are one shaft, and every face is two X-braced bays.
Nodes from XVI upEvery node except IX carries a horizontal truss across the tower, its pattern chosen by the node's name.

Panel 10, the central posts, the gap X's and the base ties have station grids of their own. The belts, the Masque screen, panel 10's band and the entretoises are explicit member lists.

A central post lands on its horizontals rather than stopping clear of them. Its end is placed where its far corner clears the horizontal, so the near corner buries in the member (6687913e).

c b XXVIII y 6.4 XXVII y 17.4 XXVI y 28.4 XXV y 39.4 XXIV y 50.4 panel 1 mid 11.9 panel 2 mid 22.9 panel 3 mid 33.9 panel 4 mid 44.9 no horizontal: base tie A's top chord tie A's bottom chord, 2.987 below no horizontal: the panel 5 belt's chord plate stands here 12.3116 in the plane (11.0 in y) 15.000, b to c at every height A B 34.9° 61.8° A = CrossA2: c at XXVII → b at XXVI length 23.938, endSin 0.5716 (34.9° to c) B = CrossB2: b at XXVII → c at XXVI length 15.533, endSin 0.8809 (61.8° to b) endSin: sine of the angle a member makes with the upright at its end; at laced depth it (as endSinMin, equal here) sets where the strut stops FACE 0 (c → b), PANELS 1–4, IN ITS OWN PLANE u = world x across the face; v = height measured up the face, which leans 26.69° back: 1 m of v is 0.8935 m of y a parallelogram: c and b both lean 24.2° in this plane, so the two diagonals of a panel are not mirror images each panel: diagonals node to node (span 2), crossing on the mid horizontal; a central post parallel to the uprights through the crossing, node to node, ends on the node horizontals (not XXVIII, XXIV) drawn at width (plain depth): uprights: 0.877 plan square, 0.800 wide in this plane diagonals, horizontals, posts: 800 struts (half 0.40) 5 m in the plane
Face 0 (c to b) over panels 1–4, to scale in its own plane: u is world x, v is measured up the face, which leans 26.69°. The struts are 800s at their skeleton width; the uprights are the built-up boxes of Uprights, not a swept square. The legs are parallel prisms here, so the face is a parallelogram and panel 2's two diagonals are not mirror images: A is 23.938 m long and meets c at endSin 0.5716; B is 15.533 m and meets b at endSin 0.8809. At the laced depth that sine sets where each strut stops short of its upright.

The upright lattice adds a vertex 0.01 m either side of every node where the slope changes, except XVII, where the upright is split instead. The kinked nodes are computed from the table, not listed. The extra vertices date from when an upright was a square section swept along its chain. A sweep puts one ring per vertex in the bisecting plane, and the extra vertices kept the skin between two kinks from twisting. The leg and shaft uprights are no longer swept. Their chains are the axes the built-up plates are set from, and the extra vertices stay because tests read them.

Members

The uprights are built plate by plate, as Plate XXXI's sections make them.

A leg's four uprights, the arbalétriers, are box columns riveted up from plates and angles. Plate XXXI's tables print the makeup of each one, panel by panel, and the model builds them from those tables (builtup.ts, UPRIGHT_SECTIONS and UPPER_SECTIONS in spec.ts).

Below XVII the box is a constant 0.877 m square in plan. Its faces lie in the tower's face planes, and its plates grow inward as they thicken [sourced, text]. Cut square to the leaning axis, that section is a lozenge, not a square: 101° 38′ in panels 1–4, the angle Plate IX fig. 28 prints, which the node table reproduces as 101.64° [derived]. No square section swept along the axis can take that shape. So each panel's upright is four web plates, each a flat box in its own face, straight within the panel. The swept skin this replaced was 0.82 m square below the first floor and rolled 11.6° off the x-facing faces.

The built plates check against Plate XXXI. Their widths come within 2 mm of every web width it prints from XXVIII to XIX: 800 in panels 1–4, 822 in 5, 832 to 866 in 6–10 [derived]. Panel 11's widths miss by up to 7 mm, because the plate prints XIX's for XVIII. Rebuilt from their parts, the plate's member areas and its Ω (the four arbalétriers' total per panel) close exactly, except at XX b, where the plate's own arithmetic is 2 mm² out.

At the turns

Where the column turns, at XXIV to XVIII, the two panels' faces meet on the node's horizontal line. That line crosses a plate's square end at up to 24°, and a box cannot mitre on it. So each plate ends where its centre line crosses the node's horizontal plane, and runs on a little to close the sliver.

Measured on the cooked plates, no point of the true faces round a turn is left uncovered. At worst a plate stands 21 mm proud of the face or 14 mm short of it. A notch under 10 mm wide stays open at the corners just above each turn, deepest at XXIV and XXIII, up to 7.3 mm. The thickness steps at the node, inside the box, where it cannot be seen.

What stands out of the box

The skeleton depth draws the webs alone. From the laced depth up, what stands out of them is drawn too, so the envelope anything else is fitted to is the same at every depth. Nothing inside the box is drawn. Below XVII three things stand out:

  • On every face, a cover strip, the C.J., over the butt of the two half plates (205 × 12 or 200 × 12 in panels 1–5).
  • On the exterior faces of panels 1–5, two angles back to back on it, their outstanding legs a fin 24 mm thick standing 100 mm proud [sourced, Plate IX figs. 23–24].
  • On the interior faces of panels 1–5, a channel of two angles, heels 385 mm apart, the seat of the montants' gussets [sourced, Plate IX fig. 21].

Above XVII the legs' b, c and d and the shaft are 0.50 m boxes, the 500 taken as a plan square [derived]. At the merge at XI the axes of b and d of neighbouring legs stand 0.50 m apart, so the boxes touch. From panel 18 up, b and d are one T, 1,000 wide in panel 18 and 500 × 500 from panel 19 [sourced]. The shaft's corner c is a closed box through panel 18 and an open L in panels 19–28, closed by 8 mm plates 1.05 m either side of each node [estimated, measured off the joint plates] (6cc56d70, 247084e0).

Where the webs land

A web member's joint meets that box. Below XVII its r in the joint rule is the box's in-plane half-width plus what stands out of the face it lands on: the channel in panels 1–5, the C.J. in 6–11. Its gusset plates stand 8 mm off the webs they lap (see Joints). Where the first floor lands on the uprights is in Regions.

Splices

A column was erected in tronçons and spliced under cover plates. Where the joints fall depends on the panel:

PanelsJoints atSource
1–4L/6, L/2 and 5L/6: three tronçons of « 4,50 m », thirds of a length « en nombre rond de 13,50 m »[sourced, text p. 75]
5Both its bends, and the 4.01335 m that Plate X fig. 19 prints[sourced]
6–11As in panels 1–4[derived]
above XVIIround(L/6); the 6 m is the middle of the text's « 5 à 7 mètres »[rule]; the 6 m [estimated]

Every ply of a joint ends in the one plane the text names, « même plan normal » [sourced]. The exception is panel 5's bends, which Plate X fig. 19 draws level: there the plies end on the node's horizontal plane.

The covers on panels 1–5 are Plate IX's, at their printed sizes: a 290 × 15 web cover on an exterior face and a 380 × 12 centre cover on an interior one. Panels 6–11 take Plate X's, with lengths taken as panels 1–4's [estimated]. Above XVII there is one 12 mm cover over each outer plate [estimated] (SPLICE, a58ec0e9).

Their rivet fields are measured on Plate IX: every 0.076 m in staggered columns on a web cover (fig. 23), every 0.064 m in a grid on an interior one (fig. 21) [measured]. They add 271,144 heads at the rivets depth, 22.5% of the total. Commit a58ec0e9 records what it left: 98 splice heads within a head's width of the first floor's rows, and 1,104 covers 1–10 mm into joint plates (up to 41 mm for the fin angles' covers).

Members

Every web member's section is squared to the face it leans in.

Every edge rule also writes the unit normal of the lattice face its member lies in, as a primitive attribute faceN. It is the cross product of the ring segment and the line joining that segment's midpoints on the two levels, which is exact for the trapezoid faces of a tapering section. A member's section axes follow from it, as N = normalize(F − T(F·T)) and B = N × T, so its section sits flat in its face. With the default +y up hint the section would be squared to the vertical instead. In panel 1, where the faces lean 26.7°, that rolls one diagonal's section 30.4° off the face its rivets sit on, and the other diagonal's 39.5°.

panel 1, face c–b of a leg: T, faceN and the section axes the diagonal c(XXVIII) → b(XXVII); orthographic view of the true geometry c, XXVIII b, XXVIII b, XXVII c, XXVII along rise T N = F = faceN B = N × T 26.69° F the face edge-on, seen along x it leans 26.69° from vertical faceN = along × rise, normalized: (0, −0.449, −0.893) its sign is not normalized the member's section, looking along the member (B, N) frame, T toward the viewer; the two to one scale T (a) squared to faceN N B face T (b) squared to a +y up hint N B face +y squared to T 30.4° the up hint sets the roll of a swept square about T N = normalize(F − T (F · T)), B = N × T this member lies in its face: F · T = 0, so N = F exactly (a) two faces of the square lie in the face plane, where its rivets sit (b) sweep's default up hint +y: this member's square rolls 30.4° off the face, since +y is not in the face plane (it leans 26.69°)
Squaring a section to its face. Left, an orthographic view of panel 1's face c–b (not to one scale): the diagonal c(XXVIII) → b(XXVII), its tangent T, and faceN = along × rise = (0, −0.449, −0.893), leaning 26.69° from vertical; N = normalize(F − T(F·T)), B = N × T, and for a member lying in its face N = F. Right, the member's 0.80 m section seen along T, to one scale: (a) squared to faceN, two faces in the face plane; (b) squared to a +y up hint, rolled 30.4° off the face. The other panel-1 diagonal rolls 39.5°.

One detail makes this work in a graph. Edge-rule members share their end points, and a sweep resolves its up hint per point, so a shared point cannot carry two faces' normals. Each member is therefore first resampled onto two points of its own with pathResample, which carries the member's primitive faceN down onto them. The few uprights that are still swept, panel 29's and the summit girders', take a constant +x hint instead, which keeps a near-vertical member square to the tower's faces. The leg and shaft uprights are built from plates (see Uprights).

Members

A laced strut is four corner angles and two zigzags per face.

At the laced depth a web member's path becomes four chord paths, offset to the corners of its section, and eight lacing paths: two per face, in opposite phase, which puts one X in every lacing bay. The chords are swept as 90 mm squares and the lacing as 45 mm squares, both squared to faceN. The member types are the plates'. In the web the "800" strut runs to 100.6 m, the "600" to 110.6 m and the "500" above; the horizontals are 800 to 115.5 m and 600 above.

(a) laced 800 strut end view along the member, T out of the page N B T 0.80 (outer half-width 0.40) 0.71 (chord axes at ±0.355) chord angle 0.090 square CHORD 0.045 lacing bar 0.045 square LACE 0.0225 rivet head r 0.019 8 heads per lacing sample: 2 per chord N = faceN squared to T, B = N × T and 1 per face where its lacing crosses (b) lower upright c, panel 1 end view along the upright, as laced N B T inset 0.055 0.800 a side, 0.784 face to face fin channel 8 seam rows, a head every 0.125 0.877 square in plan; square to the axis, 78.4° / 101.6° 0.1 m (a) and (b) to one scale (c) unit rivet head rivetHead(), profile 1 0.7 base radius 1, rise 0.7 open at the base sphere r 1.0642857 centre y −0.3642857 polar angle 69.98° 16 around × 3 rings 80 triangles instance scale 0.019 19 mm radius, 13.3 high as in (a), (b)
End views, to one scale. (a) The laced 800 strut: four chord angles swept as 0.090 m squares with their axes at ±0.355 (outer half-width 0.40 less CHORD 0.045), 0.045 m lacing bars between chord axes, and 8 rivet heads on the chords' outer faces at every lacing sample, with one more at each crossing of a face's two zigzags. (b) An upright's seam rows: 0.055 m in from its corners, a head every 0.125 m, laid on the built-up box's true faces (see Uprights). (c) The nominal unit head the page supplies, which the graph's instances scale by 0.019: a spherical cap of base radius 1 and rise 0.7, 80 triangles. strut.ts, spec.ts MEMBERS, main.ts rivetHead.
one face of a laced 800 strut, in elevation the panel-1 diagonal between its joints: trimmed length 20.858, step 1.00, 22 samples … A B 0 1 2 19 20 20.858 arc 0: the strut's trimmed start true endpoint step 1.00 1.00 1.00 last bay 0.858 0.71 35.4° 39.6° atan(0.71 / 1.00) = 35.4° to the axis: zigzags A and B run in opposite phase (phase = index mod 2), one X per bay a head at every sample on both chords and at every crossing, none under a plate: 58 on this face, 246 on this member the other steps, one bay each, to the same scale 800 · outer 0.40 LACING_STEP 1.00 35.4° 600 · outer 0.30 LACING_STEP 0.94 28.5° 500 · outer 0.25 LACING_STEP 0.68 31.1° no step table · 800 step = 2w = 0.71 45.0°
One face of the panel-1 diagonal's strut between its joints, to scale, samples 3 to 18 cut out. The trimmed member, 20.858 m long, is resampled every 1.00 m from its start and at its true endpoint — 22 samples, and a last bay of 0.858 m; two zigzags in opposite phase make one X per bay at 35.4° to the axis, with a lacing rivet at every sample on both chords and one at each crossing of the zigzags. Heads within 0.07 m of a joint plate, or under a crossing plate, are dropped, so the count is not a fixed multiple of the samples. Below, one bay at each LACING_STEP stop, and at the rule used without a table (step = chord-to-chord width, 45°). Sample count checked against a cook of buildLegs.

In the legs and the shaft the lacing step is set per member, by the member's own outer width: 1.00 m for the 800, 0.94 m for the 600 (measured on the shaft's horizontals), 0.68 m for the 500. The first-floor and summit webs and the first floor's entretoises lace at 45°, and the legs' entretoises at 0.79 m. Where the plates show a different step in a particular panel, a height band overrides it — the legs' 500s in panels 12–17 run at steps from 0.69 to 1.01 m, each read from Plate VII. The step is a field evaluated once per member on the primitive domain, from the member's mean height.

Members

A strut stops where its joint starts. The gusset plates cover the rest.

A web member run from upright axis to upright axis puts its lacing inside the upright's box. The first version did exactly that, with up to 15% of the shaft's lacing inside an upright. So each member end that meets an upright is trimmed to its joint start: the distance along the member at which its section clears the upright by 50 mm. A point a along the member and w off its axis lies a·sin t − w·cos t from the upright's axis, where t is the angle between member and column, which gives

jointStart = (r + 0.05 + w·cos t) / sin t

with r the upright's half-width at that height and w the member's in-plane half-width. Below XVII, r is the built-up box's greatest in-plane half-width plus whatever stands out of the face the member lands on: the channel in panels 1–5 or the C.J. in 6–11 (0.5021 m at XXVIII). Above XVII it is the 0.50 m box's 0.25.

The edge rule records the angle per member end, as the sine against the column's bisector at the station. At a kinked node it uses the smallest of the sines against the bisector and the segments below and above, because at XXIII the bisector would place a horizontal's bottom chord 20 mm farther from the upright than it is.

the panel-1 diagonal leaving upright c at node XXVIII, in the face plane turned so the member runs left to right, seen from inside the leg; to scale t 34.86° 0.400 upright c web 0.400 off the axis channel 0.102 out node XXVIII CLEAR 0.05 past the upright's face gusset plate strut 0.011 plate near edge jointStart 1.540 2.073 plate far edge jointStart = (r + CLEAR + (w + CHORD) cos t) / sin t = (0.5021 + 0.05 + 0.40 × 0.8205) / 0.5716 = 1.540 along the member r: the web, 0.400, + the channel, 0.102 (jointUpright: what stands out of the face) CLEAR: 0.05 w + CHORD: 0.355 + 0.045 = 0.40 t: 34.86°, sin t = endSin 0.5716 a point a along the member and v across it is a sin t − v cos t from the upright's axis gusset plate, 8 mm, one each side of the face near = (face − across cos t) / sin t = 0.011 face: the web, 0.400 (jointFace) far = jointStart + 1.5 w = 2.073 width = 2 across = 2 × 0.48 = 0.96 joint rivets on the node's 0.1 grid past jointStart arcs 1.6 … 2.0: 5 samples × 4 heads = 20, on each plate's outer face over the chords before the trim: the same strut run axis to axis chords, lacing and lacing rivets start at the node, inside the upright's box in red, inside the box: the first bay's lacing and the start of the second's, the top chord's first 1.2 m, the bottom chord's first 0.2 m, and the rivets at sample 0, at sample 1 on the top chord and at the first crossing gusset plates not drawn
The panel-1 diagonal leaving upright c at node XXVIII, in its face plane, to scale. The strut starts about 1.540 m from the node, where its near chord's outer edge clears the built-up upright and the channel standing out of its face by CLEAR 0.05 m; the pair of 8 mm gusset plates, 0.96 m wide, runs from about 0.011 m to 2.073 m, with joint rivets every 0.1 m from 1.6 to 2.0, four heads per sample, 20 an end. Below, in red, the same strut untrimmed, as the code ran before 610092b (drawn with today's lacing step). Its first bay of lacing, the chord ends and the rivets at the first two samples lie inside the upright's box. Values checked against a cook of buildLegs.

Each end then receives a pair of gusset plates, one either side of the member's face plane, from the upright's face to jointStart + reach. The plates of the members meeting at a node overlap to form the node's gusset. No rule needs to know which members meet there, because each member places its own plates. Below XVII the plates meet the built-up uprights through a table of their own, jointFace, and stand 8 mm off the webs they lap.

While the joints were built (b0d0f01), an independent check found 496 joint rivets (1.7%) up to 127 mm inside an upright: the clearance had been a fixed distance along the member. Solved from the meeting angle, as above, it left none inside, with a minimum clearance of 173 mm.

The second kind of plate is over a crossing. Wherever two laced struts cross in a lattice face, the tower rivets a plate over them, one either side. That happens where a panel's two diagonals bisect each other, where a post lands on a horizontal, or both at once. The sources name two. Plate VII fig. 2 draws the plate at the gap X's centre, and Plate XIV fig. 5 bis the « 2 Goussets 1.130 × 10 » where the summit's diagonal girders cross [sourced].

The model treats every crossing in a lattice face as the same joint [rule] and builds them all in one place, crossing.ts: 444 crossings and 888 plates in the legs and the shaft, and 240 crossings and 480 plates on the first floor (641c6174, 7fa7b878, 37f318ed).

A plate is 10 mm thick and lies in the members' shared face plane. It sits under the corner angles' outer face, so the angles run over its edges while it covers the lacing between them. How far under, and how wide across an 800, are set by eye rather than read off a plate [estimated]. They are the two values the page lets a URL override (?plateSet, ?plateEdge). No lacing rivet is placed under a plate or on its rows (63de7af1).

A gusset plate where a horizontal and a diagonal meet a corner upright, with rows of rivet heads on the plate and along the upright.
A joint at the rivets depth. The horizontal and the diagonal stop at their joint starts; their gusset plates overlap at the upright.

Members

Rules place the rivets, each emits an instance batch, and every head is driven by hand.

Three rules place most of the rivets: 878,108 of the 1,205,292 heads at the rivets depth, 72.9%.

  • Lacing rivets are where a lacing bar meets a chord: eight per strut section at every lacing sample, on the chords' outer faces, and one at every crossing of a face's two zigzags. Plate X figs. 12–13 draw those crossing rivets on the 800s of panels 6–9; riveting every laced strut's crossings that way is inferred from them (d01814e1).
  • Seam rivets fasten a box member's corner angle to the plates it joins. On an upright they run in rows 55 mm in from each corner, every 125 mm, laid on the built-up box's true faces and centred on the stretches between nodes, plate ends and splice covers. The first floor's chords and posts carry the same rule.
  • Joint rivets fasten a strut's two chords to each gusset plate, every 100 mm over the plate's reach, and never nearer the upright than the joint start.

No lacing head is placed under a joint or crossing plate, nor on its rows. No source prints a seam pitch; 125 mm is measured on Plate IX's gusset detail. Where a pitch is printed or measurable elsewhere, that figure's own is used: the splice covers' fields, and the spacings Plate X prints along the base ties' bars.

RuleWhere it comes fromHeads at ?rivets
seamBox members' corner angles; 125 mm measured on Plate IX513,544
lacingChords at every lacing sample, and the zigzags' crossings323,772
splice coversThe tronçons' cover plates, fields measured on Plate IX figs. 21, 23271,144
jointStruts' chords to their gusset plates40,792
crossing platesRows round each crossing plate's edge29,304
chord flatsThe belts' and base ties' flats, as Plates X and XII draw them19,008
summitBar ends, plate-girder rows, lattice nodes7,728

Summed over the rivet outputs of tests/eiffel.golden.json at the rivets depth; the seven rows total 1,205,292. ?rivets=full adds about 120,000 more, the rows along the built-up uprights as Plate IX draws them in panels 1–5, with panels 6–11's taken as in panels 1–5 [estimated].

Against the book. The rules are not stretched to reach a total. Eiffel's text prints one count: 1,050,846 rivets driven on site, on the Champ de Mars [sourced, text p. 113]. The 2.5 million often quoted for the whole tower is not printed in it, and the model keeps it only as a flagged secondary figure.

Each rivet output is classed shop or site by one rule: site where the pieces it fastens arrived separately [rule, from the text pp. 75 and 100] (rivetClass, 352b0c47). Counting a rivet drawn with both heads once, the model drives 334,659 site rivets, 32% of the book's (tests/eiffelRivetCount.test.ts). SOURCES.md §27.4 sets out where the rest is. Some is site riveting the model does not draw, such as the platforms' floors, the stairs and the lifts' girders. Some the model draws but classes shop, such as the first floor's joints. The test holds the model under the book's count, never over it, rather than tuning toward it.

The page draws site heads a step lighter and rougher than shop heads [estimated], and the "Site rivets" checkbox or ?site picks them out.

Hand-driven heads. A head closed with a hammer is never quite the drawn one. Every rivet output carries a per-head headDrive value, drawn by randomField from the head's position and seed, so it is the same on every cook. The page turns it, once, at load, into a head up to 4% wider or narrower, up to 12% higher or lower, leaning up to 3°, turned, and set up to 1.2 mm off centre, with its rim kept on the plate (heads.ts, 84e32257). The four amounts are [estimated]: no source measures a head. The turn has no amount, because it can take any angle about the head's axis.

Close view of a gusset plate on a corner upright: the upright's two seam rows at 125 mm pitch, the plate's rivet rows, and a rivet at each lacing crossing on the struts.
Seam, joint and lacing rivets, 3 m from a gusset plate at the rivets depth. The nominal head is 19 mm in radius and 13.3 mm high; each is driven a little differently.

Each rule ends in a spawnInstances node that emits a batch of transforms against the asset id "rivet". The batch carries the true size in its matrices, so a host resolves it against a unit head: a spherical cap of base radius 1 and rise 0.7, 80 triangles.

This page does not draw the matrices. It packs each head into 24 bytes (its seat, a rotation, a width and a rise), folds the drive in, and rebuilds the head in the vertex shader (rivetPack.ts, 13532b4c). ?cpuRivets restores the matrix path.

Rivets receive shadows but do not cast them. A 4096² map over the whole tower gets about 9 to 10 cm a texel on its longer side under this light, so a head 13 mm high throws nothing it could resolve, and casting would put every instance into the shadow pass.

Before the first rivet commit landed, an independent check found rivets up to 19 cm inside a plate. A size table stepped 1 cm past a node, and the skin read that as an 11 m taper; the commit fixed it. After the first audit round, every rivet the three rules then placed sat within 0.04 mm of its steel. The later rules record their own residuals in their commits; the splices' are above. One known residual at full depth, first-floor heads crowding the rows as built, is pinned by tests/eiffelRivetCount.test.ts.

Nothing exactly coplanar. The tower is drawn from one set of dimensions, so its parts meet EXACTLY: a member's end face on the horizontal it lands on, a rivet's rim on the plate it fastens. Coplanar is the one thing a depth buffer cannot resolve. The page's buffer is logarithmic, so its resolution is set by far alone. The depth written is

log2(1 + w) / log2(1 + far)

and one step of a 24-bit buffer is 0.27 mm at the 608 m the page opens on. Faces that meet exactly flickered against each other as the camera turned.

So at the end, everything the graph outputs except the chains, the flag and the erection's works is moved off its plane by up to 1 mm per axis: every mesh class, the lift parts, the fittings and the rivet batches. A ruler laid on the built model therefore reads neither the residuals above nor the exact dimensions. The offset is keyed on point identity rather than on an array slot, so it is a property of the built tower and not of a cook order.

It was measured at the laced depth when it went in (1c11fcd5). Of the 13,716 gusset points that then lay exactly in an upright's face plane, none still did, and about three quarters cleared a depth step at the opening pose. The cost is that two coincident points carrying different seeds part company. That opens cracks of about a millimetre where one member lands on another: a fifth of a pixel here, a pixel in the closest figure on this page, and visible only if the camera is driven to its 0.2 m limit.

Library

One leg is built, and copied to four quadrants with its directions turned.

Every leg member is built once, in the +x+z quadrant, and copied onto four targets rotated 0, 90, 180 and 270° about the vertical. The entretoises are the exception: they are built twice, as the Roux and Otis variants, and each is copied onto two legs.

copyToPoints composed each copy's position and rotation, but every other column rode along unturned. Copied as-is, three of the four legs would carry a faceN pointing the source quadrant's way, and a box copied onto a turned target would be lit as if it had not turned. From its second commit the demo corrected faceN after the quadrant copy, with a chain of three nodes and a quaternion written in the field grammar. That was recorded as a gap in the library, and fixed there.

WITHOUT rotateAttrs copyToPoints {topology: "keep"} every copy keeps the source's faceN copyToPoints before 1a40d99, or today without the param WITH rotateAttrs · copyMembersToQuadrants copyToPoints {topology: "keep", rotateAttrs: ["faceN"]} lattice.ts:684 · faceN turns with its copy +x +z 4 targets at 0,0,0 +90° XXVIII XXVII 0° 90° 180° 270° +x +z 4 targets at 0,0,0 +90° 0° 90° 180° 270° plan from +y · legs at XXVIII, XXVII dashed arrow: faceN's horizontal part targets: quadrants(), rot (0, index·90, 0) the source, built in +x+z COPY faceN (x, y, z) IN PLAN 0° (0, −0.449, −0.893) 90° (0, −0.449, −0.893) along the face 180° (0, −0.449, −0.893) reversed 270° (0, −0.449, −0.893) along the face COPY faceN (x, y, z) 0° (0, −0.449, −0.893) 90° (−0.893, −0.449, 0) 180° (0, −0.449, 0.893) 270° (0.893, −0.449, 0) WHY normal IS LISTED TOO gusset plates: a unit box, copied onto each plate's rot and scale copyToPoints {keep, rotateAttrs: ["normal"]} +x −x +z −z ±y meshPrimitive box: 6 faces, 4 points each, +x −x +y −y +z −z (strut.ts:753) without it normals keep the unit box's directions with it each normal turns by the copy's rot, length kept The scale runs along the box's own axes, so each face normal stays on a scale axis and rotation alone is exact. Turn and scale drawn here are illustrative, seen from +y.
One leg, four copies. The leg is built in the +x+z quadrant and copied onto four targets turned index·90° about +y. Without rotateAttrs, every copy keeps the source's faceN (0, −0.449, −0.893): its horizontal part then runs along the face on the 90° and 270° copies and is reversed on the 180° copy. With rotateAttrs: ["faceN"] it turns with its copy. The plan is to scale, with leg outlines at XXVIII and XXVII; arrow lengths, and the turn and scale in the box panel, are illustrative.

copyToPoints now takes rotateAttrs: a list of f32 tuple-3 columns turned by each copy's target rotation, on the point, vertex and primitive domains. It applies the rotation and, under mirrorAware (on by default), the signs of the target's scale, so a mirrored copy's normals stay outward and its faces' winding is reversed. It never applies the scale's magnitude. No eiffel target is mirrored, so for the tower it is rotation only.

It refuses P, rot, scale, seed and the bounds by name, with a message listing the valid columns. The default is an empty list and byte-identical to the old behaviour, so the corpus golden file did not move. The demo switched over in two steps:

  • The leg members (12f0ec8): no output count changed. The turned normals agreed with the workaround to one f32 ulp, and positions moved by at most 7.6 µm.
  • The box normals (09c2f9a): 17.18 million position values stayed bit-identical, with normals within 2.1 × 10⁻⁷.

Library

Where the tower needed a node the library lacked, the node went into the library.

rotateAttrs set the pattern: a workaround in the demo is recorded as a gap in PCG-TS and closed there, and the demo then drops the workaround. The tower has driven several more. Each is a general node or mode, documented in the node reference; none is specific to the tower.

Library changeWhat it doesWhere the tower uses it
pointListA literal, serializable point table: rows of points with their attribute columns (733754fd)Every point table the TypeScript computes enters as one, through pointTable (6e77795e): 289 to 345 of them per depth
ramp, on a stopAn input exactly on an interior stop returns that stop's own value, on the CPU and in the GPU compiler's ramp alike (fd41338d)Every indexTable column; found by the point-list change's byte-identity test
polygonBooleanPlanar union, intersection and difference of outlines with holes (caec7ce3)A floor deck less its wells (platforms.ts, 0ad6c362); the summit's slabs with their wells (summit.ts, c0255f8e)
extrudePolygon capsHoled and concave outlines capped by ear clipping, holes marked by holeOf (caec7ce3)Extrudes what polygonBoolean returns
sweepProfilerect and polygon sections (6d7094ec); end caps retriangulated as corner fans (caec7ce3)Flat bars and the arches' ironwork are rect sweeps (strut.ts, ironwork.ts; 6f9da5b5)
pathResample fit, alignfit: exact | ceil | round | floor and align: start | center for spacing mode (6d7094ec)align: "center" centres the uprights' seam rows on their stretches (builtup.ts); fit: "round" sets the panels of the shaft's plan-girder webs (shaft.ts; 3cad47c9)
meshPrimitive normalsOpt-in normals: true (6d7094ec)Gusset plates and the lift boxes (6f9da5b5, c51c5777); the page's zero-normal repair was deleted (3aa4f14d)
filterPrimitivesByBoundsvertex: "boundingBox", a whole-primitive bounds test (6d7094ec)A streamed region's seam gate (region.ts, c51c5777)
smoothNormalsShades a faceted curve round and keeps an edge sharp (8103b857)Round parts such as the reservoir and the crescents (chord.ts, 9cb09d6e)
copyToPoints mirrorAwareA mirrored target keeps normals outward and reverses winding (caec7ce3)Nothing: no eiffel target is mirrored, so no output moved

The same piece of work added four nodes the tower does not use: pathClip, segmentCrossings, bridgePaths and splitPolygons, along with transferByIndex's outNames. They came out of what the demo's TypeScript was doing — finding where bars cross, cutting a path, bridging two — and each has a teaching graph in the corpus (basics-clip-a-path, basics-where-bars-cross, basics-bridge-two-paths, basics-split-a-window). No eiffel graph uses them: the tower still finds its crossings in crossing.ts. One library trap is still open: mergePrimitives fills a column missing on one input with zeros, which a lit material shades black.

Regions

Under each upright: a support, a massif, and on the river side a caisson.

The ground module (ground.ts, SOURCES.md §31) builds what the tower stands on, from the text's pp. 85–104 and Plates II to VI. It works to one convention the plates disagree with: the model's ground is a single plane at the tower centre's 33.50 m, y 0, while the Champ de Mars round the piles stood higher. Everything under it is placed at its printed cote, and what the plates carry up to the higher ground is cut off just under the model's.

The supports. Each of the sixteen uprights ends on a stack built along its own axis and square to it (6ce08cbb). From the bottom up, with the bolts that hold it down:

PartWhat it isSource
stoneTwo courses of Château-Landon stone, each 0.50 m thick, irregular octagons of 4.10 × 4.00 and 3.00 × 3.15 mthe thickness [sourced: the plates, and p. 87 read on its image, where the OCR prints 0,30]; the octagons [sourced, Plate IV fig. 8]
sabotA cast-iron shoe, 1.800 m across its flats and 0.915 m high[sourced, Plate VI]
shims50 mm of iron shims[sourced]
contre-sabotCast steel; its ledge is printed 1.200 × 1.375 m and drawn 1.5% larger, so the whole of the built-up upright's end, fins included, lies on it[estimated]
anchor boltsTwo an upright, 32 in all, 0.10 m across and 7.80 m long, 1.47 m apart, running 6.20 m down the axis to their anchor shoes[sourced, p. 87, Plate VI fig. 1]

The two 800 t jacks that set the tower moved from support to support and were not left in place [derived from pp. 102 and 117], so none is built.

One departure carries through the whole ground. The model's uprights run down to the sommiers' centre, Plate XXXI's support node, where the iron really ended 1.10 m higher up the axis (Plate VI fig. 8's chain, 200 + 715 + 50 + 125 + 10 mm). The stack is therefore built under the model's upright, and the supports and the massifs under them stand 1.10 m down the axis from their printed cotes [derived] (SUPPORT.stack).

The foundations. Sixteen massifs, one under each upright's stones, are truncated pyramids of rubble in cement on the tower's diagonal: a vertical front, a top square to the upright, a rear face parallel to the leg [sourced, pp. 86–93, Plates III and IV; the lengths derived from the printed profile]. Piles 2 and 3 stand on 10 × 6 m concrete footings on the gravel [sourced, p. 86].

Piles 1 and 4, on the Seine side, stand on eight compressed-air caissons, one under each massif. Each is 15.24 × 6.00 m (the first one sunk, pile 4's N° 1, 14.24 m long) with rounded corners, filled solid with concrete in its iron shell [sourced, pp. 88–93, Plates II and IV]. The corner radius, 1.0 m, is derived from the printed area, « 90,60 m q ».

Between them stand the walls of piles 1, 2 and 4, as the 18 piers of a printed run whose arches spring above the model's ground [sourced run; which end it starts from estimated]. Under piles 2 and 3 are two cast-iron earthing pipes each, 0.30 m across and 18.00 m long [sourced, p. 86, Plate II; their depth derived] (c3ae6979). Not built: the caissons' shafts and airlocks, and the lift massif's pit and cage; that massif takes the same rule as the other three [estimated].

All of it is under the ground plane. The page's section view opens it on the plane Plate XX's diagonal section is drawn on, x + z = 0, keeping pile 4's half and taking away the ground over pile 2's [the plane sourced; keeping pile 4 rather than pile 2 the model's choice] (see Rendering).

The machine room under pile 3 has its floor at 30.50, y −3.00 [sourced, p. 88 and Plate III fig. 4] (18827652). It holds the boilers, the pump-engines and the coal store, as massing where Plate III fig. 4 draws them at 1:200 [measured: positions read at 1:200, ±0.3–0.5 m; unprinted sizes estimated], and the Otis lift's motor girders run through it. A vaulted flue, 1.30 × 1.70 m, runs « 116m23 » to the chimney [sourced, Plate IV fig. 15]. The chimney, 17.23 m over its footing [sourced], is still modelled, as a solid the clearance checks read, but no longer drawn.

The plinths (b78ce979) are Plate V's:

  • a gorge traced in 11 tiers on fig. 17 [measured, scaled to the model's height];
  • the inner corner swept on an arc of 3.29 m, with a bench along it, its sill 0.70 × 0.10 and its seat 0.48 m [the arc measured on fig. 12; the bench's existence p. 95; its sill and seat sourced, fig. 19];
  • rusticated courses of blocks 1.53 × 0.50 m, staggered, 88 to a plinth [measured, fig. 15];
  • a door 2.58 m wide on each inner face [sourced, fig. 16].

The open point is the height. Plate V puts the plinth's foot on the piles' floor, 1.58 m above the model's ground. The model keeps its plinth on its flat ground, so it stands 1.58 m low. Raising it means raising the ground round the piles and moving the arch's piers with it, and SOURCES.md §31.2 leaves that for a decision.

The piles are numbered as on the plates: 1 Nord, 2 Est, 3 Sud, 4 Ouest, clockwise from above; pile 4 is the +x+z leg the model builds and copies. The supports and massifs are copied in the graph onto the same quadrant targets as the legs. Tests: eiffelSupports, eiffelFoundations, eiffelPlinths, eiffelGround.

Regions

The first floor and the summit: the arch is solved, the rooms are read off their figures.

The first floor is built on one face, in two planes, and copied to the four faces. The two planes are the face plane and a plane 15.00 m inboard, at the legs' inner uprights. Its arch is two concentric circles, radii 36.4064 and 40.32373 m, turning to a lower arc of 44.5258 m where it meets the upright. There are 14 montants per half, at a 4.011° pitch, and each of the arcade's heads is the incircle of three lines, solved by Cramer's rule. If the montants and the upright disagree, the builder throws, naming the montant and the plate's rule.

axis FIRST-FLOOR ARCH, +z FACE, RIGHT HALF: TRUE ELEVATION world x and y; the arch's plane leans 26.688° back, so its circles show as ellipses, height × 0.89346 centre, y 6.872 R 36.4064 intrados R 40.32373 extrados (both in the face plane) lower arc's centre (−7.407, 3.900) R 44.5258 lower arc turns onto R 44.5258 at 65.81° from the crown, 1.00 from b's axis (33.210, 20.199) 39.40 crown clear band 3.9173 (3.50 in y) 37.119, half the clear span 74.238 at y 3.90, where the lower arc turns vertical 1 2 3 4 5 6 7 8 9 10 11 12 13 14 montants 1–14 at 4.011° (2.5488 along the intrados) 1–8 to the chord, 9–14 to b arcade chord (membrure) 749 deep, axis y 43.40 b c pedestal, top 5.55 pier: 6 boxes, collar top 4.30 bracket to b: ties at 12.52, 11.45, 9.11 5 m 6 7 chord OPENING 6|7, ENLARGED tangent to two montant faces and the chord centre (15.898, 41.921), r 1.2813 in plane found by Cramer's rule on the three lines 9 10 b OPENING 9|10, ENLARGED the line above is b's face, not the chord centre (23.544, 37.947), r 1.2699 in plane 4|5 … 7|8 sit under the chord, 8|9 … 10|11 under b's face
The right half of the first-floor arch on the +z face, a true elevation to scale. The arch's plane leans 26.688°, so its circles appear as ellipses with height ×0.89346; every radius given is the in-plane value. Intrados R 36.4064, crown 39.40 m above the ground; extrados R 40.32373; band 3.9173. The intrados turns onto R 44.5258 where it meets b. Montants 1–8 of 14 run to the arcade chord, 9–14 to b. Each opening's head is the circle tangent to three lines; two are enlarged at right. firstFloor.ts, spec.ts (Plates VII, XI).
The first floor at the laced depth: the gallery arcade, the pavilions on each face, the skirt with the lettered names, and the arch below.
The 1889 first floor: gallery arcade, pavilions and the skirt, on which the 72 names are lettered in gilt capitals (see Names).

The arch lands on the uprights as they were built. The first floor's landing ends meet b's web, and its members land on the webs of b, d and a, never on the tip of a fin, as Plate XI figs. 3, 5 and 11 draw them on b's (0d114215).

Ironwork

The arch's ironwork is Plate XI fig. 14's decorated panel (d40142a9). Each panel has a round-headed frame « Ame 160×7 », three bars « Corn 100×45 », four cast volutes « Volute en fonte 80×45 » and macarons [sourced, Plate XI fig. 14 and p. 80], 125 mm across [measured on its 1:10 detail].

The text puts the motif on the arch's inner and outer faces and leaves the other two plain. On every tower face the model dresses the decorated side of both arches, the one in the face plane and the one 15 m inboard: 26 panels each, 208 in all. It keeps a volute only where half its width clears every panel edge and the frame and bars [rule], which leaves 752. The frame and the bars are built at every depth, the volutes and macarons from the laced depth up.

The 1889 dressing

The first floor is dressed as in 1889 (cdf757b9, ff08b7bc; SOURCES.md §10). One rule builds the four pavilions: cornice, posts, balustrade, pinnacles, timber screens. Each face then takes its own dressing from Plates XXIX and XXX: the Russe on the north-east face, the Français south-east, the Anglo-Américain south-west and the Flamand north-west. The Français and the Russe have entrance flights at their fronts as Plate XVIII draws them; the Bar Anglo-Américain and the Flamand have none [sourced; the Flamand read from the plate's Théâtre, estimated].

The gallery has 36 arcades [sourced: nine a face, and the ledger's « 36 arcades »]. Each is dressed once and placed on all 36: a stamped-zinc crest measured on Plate XIX fig. 1, and yellow glass cabochons in the crescent's twelve cells [the cabochons sourced, text; twelve counted on the figure], 432 in all [derived]. The balustrade has 720 balusters, the number the ledger records set (p. 217), not the 560 that p. 213 gives. The inner arcade is Plate XIII's; the gas globes' placing is assumed.

Glass is its own class (3b5a9668): the pavilions' doors, screens and tympana, and the summit's croisées. The arcade's crescents are « verres coulés étamés », silvered glass, and have a class of their own, silvered. The second floor is built in its 1889 state from Plate XIX fig. 7, its unprinted heights assumed.

The summit

The central shaft at the intermediate landing, seen from outside: the vertical lift's guide columns rising through the X-braced shaft.
The shaft at the landing, 195.93 m.
The summit: the glazed third-floor gallery, the dome of four half-arches, the lantern and the rod that carries the flag.
The summit, 276.13 to 312.275 m.

The summit was read wrong twice. Face-on, the structure above the third floor shows two 0.80 m columns at its corners, and two readings took them for a box. The third audit's blind reader established that they are two diagonal lattice girders, 14.870 m by 1.130 m, crossing over the axis: the north-south one continuous, the east-west one butting against its webs. Above them stand a dome of four half-arches, the lantern and the rod to 312.275 m. A plate of « 2 Goussets 1.130 × 10 » covers the girders' crossing (see Joints).

The top is built room by room (44c1d6cd; SOURCES.md §29):

  • The glazed gallery, with its 24 croisées [sourced, the cost table].
  • Eiffel's pavilion, 10.50 m square, with the rooms Plate XIX fig. 6 draws [sourced; the two unnamed laboratories read as electricity and meteorology, assumed].
  • Four contrefiches, 600 × 762 mm [sourced, Plate VII fig. 4].
  • The lantern, a decagon of 0.80 m sides [sourced, p. 79], its posts bearing on the cupola as built (26b76dc4).
  • The rod, the lightning conductor that carried the flag, 10.790 m long [sourced, Plates XV and XIX], with its tip at 312.275 [sourced, Plates VII and I].

The top's slabs are outlines with their wells cut out in the graph, and the last ladder bears on its manhole (c0255f8e). On the rod flies the tricolour at Plate XIX fig. 4's 5.50 × 3.00 m. The text gives 7.50 × 4.50; the model carries both and uses the plate's (b6fbc49d). The graph builds the flag flat, and the page flies it (see Rendering).

Regions

The 72 names are lettered, letter by letter, as real geometry.

Seventy-two names of French scientists and engineers run round the first floor's skirt, 18 a face, on a band 0.80 m high [sourced, Plate XIX fig. 1], 4.02 to 4.82 m below the gallery floor [the top measured]. Which names are on which face comes from Plate XIX's headers and from Barral's 1892 account. Their order left to right is observed on photographs, because Plate XIX's elevation shows only the south-east face (savants.ts; 411724c2). They are spelled as cast, without accents, as the photographs show them, and the list's « Couchy » is corrected to CAUCHY. The plaques are painted iron with the skirt; only the letters are gilt.

The letters are geometry, not a texture or a font. letters.ts draws 27 glyphs, A to Z and the hyphen of GAY-LUSSAC, each from stem, bar and ring pieces [rule]. Their stroke weights are fitted to a photograph, and their proportions are the module's own estimates [estimated]. No 1889 photograph is sharp enough to read the letterform, so the forms follow the 1986–87 restoration [observed]. The cap height is about 0.60 m, from Barral (p. 19, a secondary source) and a measure on a photograph; Eiffel's book gives none.

The layout is computed in TypeScript. In the graph, the glyphs' pieces enter as one pointList of outlines, become closed paths, are extruded together by one extrudePolygon, and are copied by copyToPoints onto 491 places: 490 letters and one hyphen.

Regions

1,710 steps from the ground to the summit, counted against the book.

Eiffel's 1900 book prints Hénocque's count of the stairs: 1,710 steps, in five runs of 347, 327, 456, 455 and 125 [sourced, p. 304]. The model builds all five (stairs.ts, 9e20c20a; SOURCES.md §11), each from what the plates and the text give for it and a rule where they stop:

RunFrom, toWhat it isBookTreads placed
groundGround to the first floorStraight flights with landings, up in pile 2 and down in pile 4 [sourced, Plate XX's Nota, p. 81]; each panel of the leg one repeat of Plate XXVIII fig. 5, flights of 24 and 21 risers, of 0.15933 m but for a 0.172 and two 0.1498, between three landings, 276 running risers in all [sourced]; 1.00 m wide [sourced, Plate XX's Nota]347332 × 2
e1First to second floorFour helical stairs 1.60 m across, one a pile [sourced, Plate XX]; each built as five helices of 56, 51, 50, 46 and 61 risers [rule] joined by flights of 11 risers, 0.207 on 0.255 treads [sourced, text p. 81], and a last flight of 19327326 × 4
e2Second floor to the intermediate landingA service helix of 435 risers [rule] and flights of 16 and 5 [sourced, Plate XX fig. 2]456454
e3The intermediate landing to the third floor« 314 marches sur 54m88 » and a connecting flight of 13 [sourced], an upper helix of 128 [rule]455455
summitThe third floor to the topA small spiral to Eiffel's apartment, three steep flights, the campanile's spiral of « 22 marches de 178 » [sourced, Plate XV fig. 2], and three ladders125121

Treads placed: 2,998 at every depth, from 1,688 built; the ground run is copied onto its two piles and e1 onto all four. A run places fewer treads than the book counts steps where a slab or a landing takes a tread's place. Counted from stairRuns() at 109f23f9, 2026-10-02.

Two numbers depart from a source, and are recorded as such. The plates give the ground run 351 steps against the book's 347; the model keeps 347, an open decision in stairs.ts. The e1 helices are printed at 0.178 m risers, and the model runs them at 0.1710 to 0.1745, 0.1728 on average, to land on its floors. Not built: e2's foot of « 11 marches », and the ground stair's arrival turns. Separately, e1's last flight clashes with the Otis car, a bounded exemption recorded in SOURCES.md.

The stairs are drawn for eye level as well as for a distance (bdf4a963): landings as sectors, headroom over the turns, the core's door, the manhole at the top. Tests hold the clearance and the headroom (eiffelStairsClearance, eiffelStairsHeadroom). Treads run into their newels, posts reach their rails, and newels and stringers stand on floors (023d40f8; see Nothing on air). The geometry is TypeScript producing boxes and lines. lifts.ts turns them into graph nodes (meshPrimitive, copyToPoints, the quadrant copy, and pointsToPath for the rails), and the page's walk, ?walk, climbs all 1,710 steps on the stairs' own lines.

Regions

Three makers' lifts: the graph builds the parts the page moves, the page runs the clock.

The cars. The 1889 tower had lifts by three makers, and the model builds all six cars (lifts.ts; SOURCES.md §11, §34). Their routes are as the plates and the text give them [sourced, Plates XX and XXI and the text], and the cars are the book's:

MakerWhereRouteCar
OtisPiles 1 and 3Ground to the second floorDouble-deck, for 50 people
Roux, Combaluzier et LepapePiles 2 and 4Ground to the first floorDouble-deck, for 100; its dimensions measured on a figure, not printed
EdouxOn the axis; a vertical liftSecond floor to third, in two stages of 80.20 m through the intermediate landing at 195.93 mFor 65, at 3.570 × 3.900 m [sourced, Plate XXIII ter fig. 20]

The leg cars are cabins, with frames, doors, glass and benches (61d4df05).

The tracks. The inclined tracks follow the legs' curve, because each rail station is anchored to the inner corner a at every Plate XXXI node; a track kinks where its leg kinks. The Otis track follows the pile's diagonal from 54° 35′ at the bottom to 77° 30′ at the top [sourced, text pp. 144, 344]. The model's rail is a polyline that holds 54.6° to node XXIV, steepens to 77.05° in panel 10 and runs at 74.11° in panel 11. The Roux track's top angle, about 72°, follows from its printed length alone [derived]. The tracks run on below the ground into their pits.

The third audit round found tracks passing through solid decks (90 intersecting triangle pairs); the decks now have their openings from Plate XVI.

What balances what. Each maker solved the counterweight differently, and the model builds each mechanism as drawn.

  • Edoux. The lower car is the counterweight of the upper, hung on four cables over pulleys at the top [sourced], so the two cars move in opposition [derived]. Its piston, 0.32 m across and 80.96 m long, is derived; the 80.20 m stroke and an 81.06 m cylinder are printed (the cylinder in the text, p. 172).
  • Otis. Water drives a piston in a cylinder 1.065 m across and 12.674 m long, reeved through a twelve-fold tackle, « palan à 12 brins », so its carriage travels a twelfth of the car's run. The counterweight truck travels a third, 129.96 / 3 = 43.32 m, on its own track [sourced, Plate XXI].
  • Roux. The car is pushed up by an articulated chain of mostly one-metre pieces round a 3.864 m driving wheel. It is balanced by lead counterweight pistons of about 143 kg: eleven in the text, ten in its fig. 144 [sourced].

The machinery hangs on steel that carries it: shafts in bearings, hangers, wheels (17c1647e).

Graph and page. The graph builds the geometry, and each part the page moves is its own output: 43 car.* meshes (cars, cabin glass, cables, pulleys, wheels, pistons, carriages, trucks) and the Roux chains as four chain.* outputs of instances. The cars are placed per leg, not copied, because each is its own mesh the page moves on its own. lifts.ts also publishes each car's motion law; the page owns the clock and moves the parts every frame.

By default all six run station to station at about 1 m/s, a speed chosen for the page; the real Roux car took about a minute to the first floor. The Otis car tips as its rail bends. ?still parks them where the graph built them, each leg's car at a different height.

Departures, each recorded in SOURCES.md: the rail stands 4.35 m off its corner where 3.3 is measured off Plate XX; the Roux car arrives at the first floor tipped 11.7°, where the book has it level; the Otis car tips 8.6° against about 11.9°; the Otis pulleys are drawn flat, and its counterweight track runs about 58 m against the text's 45. Not built: the Roux cylinders and Galle chains, the Otis « garant » cables, the Edoux racks and pipes.

Regions

The erection, 1887–89: every piece dated, the temporary works and cranes built.

?build opens a different graph: the tower's modules plus the erection's temporary works and cranes (erection.ts, cranes.ts, chronology.ts; SOURCES.md §32; f15598c0). The graph carries no time. At load the page dates every triangle, then reveals the tower day by day under a date scrubber with a play button. The span runs from the foundations begun on 26 January 1887 [sourced, p. 86] to the last lift into service on 19 June 1889 [sourced, p. 118]: about 29 months, in about 40 s of play.

The pace is mainly the book's. Its milestones, all but the first two from its table of the erection's periods, are these:

DateMilestoneSource
1 July 1887the iron begun[sourced, p. 102]
10 October 188728 m reached[sourced, p. 103]
4 December 1887the four first panels[sourced, pp. 112–113]
18 December 1887 – 1 March 1888the girders[sourced, pp. 112–113]
31 March 1889the arrival at the top, and the flag hoisted the same day[sourced, pp. 112–113; p. 311]
1 June 1889the end of riveting[sourced, pp. 112–113]

A few dates are estimated or read off photographs: the scaffolds struck, the legs reaching the first floor. The cranes' days are estimated. 18 July 1887 is the book's date for a photograph of pile 4's first tronçons, not the day they were begun: they are already past mid-panel 1 in it [sourced, p. 103].

Between milestones, rules take over:

  • An erection front of height against date, shared by all four legs [rule]. Its stops are read off the book's fig. 123 and photographs [sourced stops], and it is interpolated linearly between them [estimated].
  • Each piece is dated by the height of its top, and an upright by its tronçon [rule].
  • The girders' order is assumed.
  • Site rivets appear at the book's counts for each period [sourced counts; the mapping a rule].

Temporary works are Plate XXV's (6670c21f):

  • four timber pylons a leg, on bases 8.50 m across, 27.41 m tall on the figures where the text says 27.40 [sourced, Plate XXV figs. 1–3, 13–14, pp. 103–104];
  • the sand boxes, 0.40 × 0.50 m, and temporary shoes [sourced, Plate XXV figs. 3–12];
  • four scaffolds « de 41 m de hauteur » [sourced, pp. 107–108, Plate XXV figs. 15–18], their plan positions measured off fig. 1.

When each went up and came down is mostly assumed.

Cranes are creeper cranes of Plate XXIV's design, rated at 3,000 kg [sourced, text p. 105; the geometry Plate XXIV figs. 2–4] (c3a0ba32). They work in six runs, never all at once: four leg cranes on the lift girders, and two upper cranes on the central guide post. How far a crane steps each time is a rule of the model's [assumed]. The works are the graph's ten works.* outputs, built only under ?build, and the page moves the cranes.

Not built: the hydraulic jacks, the shear legs, the steam hoists, the working floors and the piles. Known departures: the cranes intersect panels 12–13, and the post cranes' jibs cross panel 29's uprights and bracing, the stair's flights and the summit's rooms. Build, rotate and walk are exclusive, and turning build on reopens the page on the whole site.

The host page

The page draws what the graph settled, under a sun fitted to it.

Cook and draw

At a whole depth the page cooks the graph once on the CPU; a streamed depth is cooked by cells (see Detail). The page converts each class output to a buffer geometry, packs each rivet batch for the vertex shader (see Rivets), and instances the fittings and the chains against its unit assets. The depth buffer is logarithmic, because the camera may orbit to within 0.2 m of a rivet head and draw back to 1.5 km for the whole tower.

The page draws the result as an ink-and-wash elevation (ink.ts, wash.ts, drawing.ts; 5017ed2c). A prepass draws every visible surface's normal and depth. A full-screen pass then inks a contour where the depth jumps and a crease where the normal turns. Under the lines lies a wash: the scene lit by the page's lights and shadow, with every material's paint replaced by a palette generated from one main colour. Nothing shines. By default the palette is mono, so the tower is one grey in steps of value on white paper.

The palette

Each class still has its own material, and what a class is to the palette is its role:

ClassDrawn as
the iron classesthe steps of value their materials hold against one another, so the classes stay apart only enough to tell them apart up close
the masonry and the foundationsstone
platforms, the floors and their fit-outthe paper's lightness, by default
deckUndersides, the 1st and 2nd floors' bottom facesa role of its own: a dark grey, lightness 0.4 by default, so a floor seen from under the tower reads as the mass it is, not as the paper (53f551f0)
the names' lettersbrighter than the paper: a light band on the skirt
the erection's timber, the temporary worksa role of its own, drawn pale: lightness 0.97 by default, so the scaffolding reads as something the iron was put up inside, not as part of it (71419e98)
glassa pale grey at an opacity of 0.55; it takes no line and casts no shadow
shop rivets, site rivetsa material each

Light and shadow

A braced panel under flat light is a hatched silhouette. Lit from one side by a directional sun, it casts its web onto the members behind it and onto the ground, and the depth of the lattice becomes legible. The wash keeps that light under the lines.

The lights are the page's rig (rig.ts), set by the drawing (drawingLook in drawing.ts; f94841e9):

LightSettingBy default
keywhite, 57° up at an azimuth of −15°; the one light that casts a shadowon
against the keystraight against it at a quarter of its strength, shadowless; it lights from the other side what the sun leaves in shadeon
hemisphere ambientwhite, at 0.15 of the painton
ambient occlusion—on
from under the towershining straight up, shadowlessoff
square to the sunlevel and square to the sun, shadowlessoff

The drawing has no sky, no fog and no vignette, and it draws no ground. A plane catches the tower's shadow on the ground, and the page lays it on the paper in a light grey.

The key's orthographic shadow camera is fitted by fitShadow to the bounds of what is drawn, the buried foundations left out; the figure below shows the fit. The shadow map is 4096². It is not redrawn as the camera orbits, so orbiting costs no shadow pass. It is redrawn when the visible classes change, when the section moves, at each day of the erection, when a streamed cell arrives, and on the walk's near-shadow refits.

That scheme holds because nothing that moves every frame casts a shadow: not the lift cars and their machinery, and not the flag. The rivets cast none either, for cost (see Rivets). The erection's cranes move only from day to day, so they do cast shadows.

y 0 sun.position = centre + dir · reach, reach = box diagonal 378.1 the camera looks at the centre, down −dir (dash-dot) target: box centre near 184.7 far 571.5 corners only far 739.8 with the ground hits top − bottom = 373.6 shadow 271.5 = 0.869 × 312.275 312.275 flagpole top 49.0° 50 m ELEVATION IN THE SUN'S VERTICAL PLANE · TO SCALE light x points out of the page · sun azimuth 132° from +x toward +z box fitShadow(box) · rig.ts:309 box union of the drawn meshes' bounds, foundations excepted: x ±70.128 · z −62.454 … 70.128 · y −12.848 … 312.278 dir = sunDirection(49°, −42°) elevation 49.0° · shadow 0.869 m per m of height 8 corners + 8 ground hits → light space a ground hit = corner − dir · min(y / dir.y, 1.5 · reach): same light x and y as its corner, deeper z, so the ground hits only move far bounds ± 0.02 · reach = 7.6 m left/right ±104.0 · bottom/top ±186.8 near 184.7 · far 739.8 shadow map 4096 × 4096 0.051 × 0.091 m per texel NOMINAL fallback box: 0.066 × 0.102 PLAN, SEEN FROM +Y sun to the left, light x down the page left -104.0 right +104.0 +x +z sun ground hits 50 m
fitShadow applied to the box the page fits after the cook. The figure is drawn for the key of the lit look the drawing replaced (look.ts): 49° up at an azimuth of −42°, which throws 0.869 m of shadow per metre of height, about 271.5 m from the rod's tip [computed from the angle]. The box holds every drawn class but the foundations, from below the ground, where the lift tracks end in their pits, to 312.278 at the rod's tip. Each corner's ground hit is added so the thrown shadow falls inside the depth range. A ground hit lies on its corner's ray along the light's direction, the light's z axis, so it keeps its corner's light-space x and y and moves only far. The box gains a margin of 2% of its diagonal on every side, and a ground hit's reach is capped at 1.5 diagonals. Elevation and plan are each to scale, at different scales.

The drawing's own key, 57° up, throws 0.649 m of shadow per metre, about 202.8 m from the rod's tip [computed from the angle]. Under the lit look's light, a texel of the 4096² map is about 9 to 10 cm on its longer side, as walkView.ts records it.

Normals

The page repairs no geometry. meshPrimitive writes no normals by default, and mergePrimitives fills a column missing on one input with its default, (0, 0, 0), which a lit material shades black. Several modules fell into this. They were fixed at source: meshPrimitive gained an opt-in normals: true, and the page's zero-normal repair was deleted (3aa4f14d). The mergePrimitives half of the trap is still open in the library's backlog. Round parts, the reservoir and the crescents, shade round through smoothNormals (9cb09d6e).

Motion and controls

The page also runs things in time, without recooking the graph. The lift cars and their machinery move every frame by the motion laws lifts.ts publishes (see Lifts). The flag is a simulated cloth: wool bunting on its cord in a gusting wind drawn from the seed 1889, solved in small XPBD steps (flag.ts, ceeb65f6). It is defined as a run from rest in fixed 1/60 s steps, so the frame at a time t is the same bytes however the page reached it. ?wave swaps it for the earlier wave, and ?still parks the cars and holds the wind.

The page's controls:

  • Section. The section box cuts the whole tower on a vertical plane parallel to Plate XX's diagonal section, and opens the ground under the cut (see Ground). A slider moves it in metre steps (?section=<m>; fcd79a0e).
  • Walk. ?walk climbs the 1,710 steps in first person on the stairs' own path (4dc794b2).
  • Orbit. WASD and Q/E move the orbiting camera.
  • Highlights. The highlight switches (?hl=) draw the stairs, the lift cars or the rivets over the rest of the tower, which is laid on the paper at the Ghost slider's opacity.
  • Copy view. "Copy view" copies a URL that reopens the exact frame, with its state as JSON.

The host page

Lacing and rivets can be cooked a cube at a time, round the camera.

The page's Detail section sets lacing and rivets each to off, streamed or full, and applies the choice with a reload (depth.ts; b7ce7138). Full is a depth's graph cooked once, as in the table in Architecture. In the URL, Full is whole. It is not ?rivets=full, which is whole rivets plus the built-up uprights' rows (see Commands).

Streamed is the one place the demo uses the library's runtime: grid levels and partitioned cooking (bands.ts, 866fd282). The page still cooks the whole tower, at the depth that is set full. Above it stands a World of up to two levels of cube cells:

LevelCell sideCooked when its centre is withinA pixel wide at
laced32 m120 m of the cameraa 45 mm lacing bar at 63 m
rivets16 m40 m of the cameraa 19 mm head at 27 m

Those defaults come from what a pixel holds on the page's camera at 1,080 rows [derived]. When both levels stream, a rivet cell cooks only once the laced cell holding it has, so heads never stand on a solid bar. A level's cellFilter skips the cubes that are only sky.

Each level cooks one regional graph (region.ts): the whole tower's graph at that depth, node for node and seed for seed, plus box filters, the gates, that each cell moves onto its own bounds, each widened by its halo. The regional graph has 2,251 nodes laced and 3,926 with rivets.

A laced cell puts out the classes the laced depth changes. Each comes out as the primitives whose bounding box meets the cell, so a member crossing a seam comes out of both cells whole, and the page clips it at the seam. The gate is one filterPrimitivesByBounds (c51c5777). A rivet cell puts out every head whose seat is in it, so each head is in exactly one cell. A cell is therefore exactly the whole cook inside it, and tests/eiffelBands.test.ts holds every cell of both levels to that, byte for byte.

The cooking runs in one Web Worker (bandsWorker.ts), at most four cell cooks an update in a 50 ms budget. A cook in flight is dropped when the camera moves more than the finest streamed cell: a rivet cell, or a laced one when only the lacing streams.

CellColdWarm
laced, 32 m, a heavy first-floor cell873 msabout 334 ms
laced, 32 m, a cell of leg alone—47 ms
rivets, 16 m499 msabout 91 ms

Measured 2026-10-02, headless in Node rather than in the page's worker, on the same Intel i9-13900KF as the table in Architecture; medians of seven (109f23f9). Cold is a level's first cell, which also does the tower-wide work above the gates. That work is cached, so later cells cost their own members (warm). The whole laced cook takes 2.87 s; a streamed view pays for the cells near the camera.

Method

One agent session wrote the rules. Other agents checked them against the plates.

The demo was built in one Claude Code session, from the first request at 18:09 on 2026-09-18 to the last commit at 14:17 the next day, in 18 commits. That was the first build; about 89 later commits have touched demos/eiffel since. The main thread launched 45 agents, and those launched their own readers and verifiers: 107 agent transcripts in all.

The choice at the start was between tracing members and stating rules. Rules were chosen, with the plates as the first authority for anything a rule takes as input, and an [estimated] tag where no source gives a number.

research, build, fixes independent check commit the commit records an independent check 20:00 22:00 00:00 02:00 04:00 06:00 08:00 10:00 12:00 18:33 14:17 18 Sep19 Sep research build audit fix research agent Eiffel's 1900 plates, read from full-resolution Gallica scans every number tagged sourced, derived or estimated arithmetic verifier 254 sub-checks, its own code, not shown the research script rule modules: main thread + builder agents 8 commits 19:55–22:08 e906699 … d49c1a4 57ac013 00:59 usage limit, 03:15–06:08 0fe8ba0 07:21 c39ee48 12:39 round 1 4 auditors read-only round 2 fresh auditors, had not seen the fixes blind plate readers: no model, code, spec round 3 4 auditors + blind helpers each re-reads the plates 4 fixers confirm each finding on the plate first 5 residual fixers confirm on the plates 4 fixers confirm on the plates with blind re-readers site, and one library change 1a40d99 13:45 copyToPoints rotateAttrs
The build as it ran, to scale in local time (+01:00), from 18:33 on 18 September to 14:17 on 19 September 2026. Each audit round (green) handed its findings to fixers who confirmed them on the plates, and each round's fixes landed in one commit — 57ac013, 0fe8ba0, c39ee48; the first two were audited by the next round, the third by none. Commit times from git log; agent launches and the usage-limit pause from the session transcript. A green mark over a commit means its message records an independent check.
01ResearchOne agent compiled the first sections of SOURCES.md (§0–§9) from the plates: it fetched the Gallica IIIF manifest, identified the plates from contact sheets, downloaded the full-resolution views, and read printed figures off cropped, magnified regions. Two web agents gathered the official figures and the profile mathematics.
02Arithmetic checkA separate agent re-derived every [derived] value of the first spec in its own code, told not to read the research agent's script: 254 sub-checks at 2 mm, 0.02° and 0.5% tolerances, all passing.
03Build, verifiedAgents that were not given the answer re-derived build commits. Three build commit messages record such a check (e906699, 0aca149, 1a806f0), as do two later ones: the library change 1a40d99 and the refactor 12f0ec8 that uses it. The first check, e906699's, found web members squared to the vertical, up to 30° off in panel 1 (30–40° by the fixing commit's measure), and the next commit fixed them. Later checks found rivets up to 19 cm inside plates and girder plaques cutting 21 mm into the uprights, each fixed in the same commit.
04AuditFour auditors — legs and shaft, first floor, summit, whole silhouette — each wrote down the expected value with its tag, measured the plate where the spec was silent or estimated, cooked the model and measured it, and tabulated ratio and verdict. They fixed nothing.
05FixOne fixer per module, each required to confirm every finding on the plate before changing a number, and to keep the better-supported value where it disagreed with the audit. Fixes were written up in SOURCES.md, most with a was/now table.
06Re-audit, blindRound two used fresh auditors that had not seen the fixes, told to trust neither the fixers' claims nor the code comments, and plate readers with no access to the model, the code, the spec or any report. Round three's auditors re-derived each expected value themselves with blind helpers, and listed the fixers' claims they could not reproduce.

Every pixel reading is calibrated on a dimension printed on the same figure. The silhouette audit deskewed the Plate I scan by 0.87° and rendered the model orthographically face-on at 4096² (9.30909 px/m). It took each row's full ink extent as the median over ±0.4 m. A row counted as off only if it was off by more than 3% under two calibrations: one isotropic, one mapped piecewise through the printed levels. Member sections were measured on the cooked meshes, sliced perpendicular to each member.

Plate I, figure 1, of Eiffel's 1900 album: the tower in elevation, rows mapped through its printed levels.

Plate I, 1900

The model in red over Plate I in grey at the first audit: the second-floor band is too shallow, and the plate's grey shows through between the floors, at the arch and at the summit.

round 1 · d49c1a4

The model in red over Plate I in grey at the third audit: the silhouettes coincide from the ground to the third floor.

round 3 · 0fe8ba0

The auditors' own composites: model in red, drawn orthographically at the plate's scale, over Plate I in grey. Left, the plate alone; centre, the model the first round audited; right, the model the third round audited. Plate I: G. Eiffel, La Tour de trois cents mètres, Planches (1900). Source gallica.bnf.fr / BnF.

round 1 · propD · the model before 57ac013 round 2 · re4 · after 57ac013 round 3 · fin · after 0fe8ba0 each bar spans plate calibrations A (isotropic) and B (piecewise through the printed levels), and any range a plate reading gave 0.4 0.4 0.5 0.5 0.6 0.6 0.7 0.7 0.8 0.8 0.9 0.9 1.00 1.00 1.1 1.1 1.2 1.2 1.3 1.3 model ÷ plate ±3 % modelplate (m) window, 1st–2nd floor clear height at the axis 1 43.7 32.9–33.0 2 33.62 32.89–33.03 3 33.62 32.89–33.03 opening at the arch feet clear width; read at y 5 · y 1–5 · y 1 and 3 1 80.7 74.2–74.6 2 73.48 73.3–74.6 3 73.3, 74.1 73.3–73.9 consoles, y 272 width under the 3rd floor 1 10.5 13.0–13.1 2 12.3 13.0–13.1 3 12.7 13.4–13.5 consoles, y 274 width under the 3rd floor 1 10.5 16.1–16.3 2 14.5 15.4–15.5 3 15.0 16.0–16.1 lantern body width at its widest 1 2.8 4.1 2 3.87 4.03–4.11 3 3.87 4.1 summit height 3rd floor to cupola top, pole excluded 1 24.7 27.4–28.4 2 27.7 27.2–28.2 3 27.7 27.2–28.2 legs and shaft widths at 14 heights, y 5–230; not flagged 1 2 3 2nd-floor band measured three ways; a line joins rounds only where the definition is shared window top → band top round 1; plate 98.5 → 121.5 (A) 1 9.4 22.8–23.0 lintel → mast tops rounds 2, 3; plate 98.55 → ≈121.2 (A) 2 19.2 22.7 3 23.35 22.7 lintel → gallery rail round 2; plate 98.55 → 117.6 (A) 2 19.2 18.9–19.05
Model ÷ plate for the silhouette quantities the first audit found off by more than 3% under both calibrations, as each round measured them against Plate I, with the leg and shaft widths that no round flagged, for comparison. Each bar spans calibration A (isotropic, 15.80 px/m) and B (piecewise through the printed levels), plus any range a plate reading gave. The second-floor band was measured over different spans in different rounds, so it is drawn apart and joined only where the definition is shared. Sources: the three rounds' audit reports.
Found by the first roundModelPlateRound twoCause, and the fix
Window between floors43.7 m33.0 m33.62 mThe second floor's band was 9.4 m deep against 23.0 m. It was rebuilt from Plates VII and XIX and checked on Plate I.
Opening at the arch feet80.7 m74.6 m (A)73.48 mThe arch was one circle in the inclined face plane; it is now two concentric circles turning to R 44.5258. The plate's 74.6 is calibration A's reading; SOURCES.md §1 gives 74.2.
Arch band2.5488 m3.9173 m—The montant pitch had been read as the band depth.
Gusset plates15 mm8 mm—Plate IX's detail; PLATE = 0.008.
Horizontals0.60 m0.80 m—Plates IX and VII: the "800" horizontal runs to the second floor.
Summit24.7 m27.4 m27.7 mThe lantern ended at its terrace, about 3 m short. It was rebuilt with its finial cage, and the third-floor room re-read as a glazed octagon under a 10.68 m apartment. The summit measured 27.43 m after the fix, by 57ac013's own measure.

Round three found the silhouette within 1–3% of Plate I from the ground to the third floor. It still found about 25 element rows off, among them the summit's plan, lift tracks through solid decks, and the pedestal and arch-foot heights. Its commit settled each on the plates (c39ee48). The pedestal, for one, now stands 5.55 m, between Plate I's 5.45–5.53 m and Plate VII's 5.65–5.74 m; the French Wikipedia's 4 m is drawn on no plate. No fourth round audited that commit.

Round three's open points are listed under "Disagreements and open points" in SOURCES.md §21–§24. The sections written since, through §34, carry their own, among them §27.4 on the rivet count and §31.2 on the plinth's height.

Method

A test asks of each piece at the plain depth what carries it.

The plate audits compare the model with the drawings. A fourth kind of check asks a question no drawing answers directly: does each piece stand on something?

tests/eiffelFloating.test.ts cooks the tower at the plain depth, with no lacing, gussets or rivets (47dd3c91). It reads none of the tower's instanced outputs, and runs a detector (tests/support/eiffelFloating.ts) over the faces. The detector reports two kinds of piece:

  • Floating. The cluster of faces the piece touches, within 8 mm, never reaches the ground.
  • Loose. The piece is a linear solid with an end that rests on nothing. A linear solid is at least 0.3 m long and four times as long as its section. A lying or raking piece needs both ends held; a standing one (plumb within 20°) needs only its foot.

An end buried inside a closed member counts as held, decided by a vote of three rays counting crossings. Before it reads the tower, the test runs the detector on a synthetic scene and requires it to report both kinds, so a detector that has stopped seeing cannot pass.

The first sweep found 1,445 floating clusters, about 1,100 of them in the stairs, and 1,992 loose ends. The fixes went into the modules, not into the test:

  • treads into their newels, and posts to their rails (023d40f8);
  • the lift machinery's shafts, bearings, hangers and wheels; the same commit also closed the legs' entretoises and seated pieces of the shaft and the summit (17c1647e);
  • the trellis laths, lantern roofs and knee struts and the arches' ironwork, reaching what carries them (2cf89f71);
  • posts at the entrance flights' rails [estimated] (47dd3c91).

The test first measured the mesh with its 1 mm offset on, and welded corners within 4 mm. A later commit, ed3070ad, switched the offset off and tightened the weld to 0.5 mm, and the commits since measure that way.

What remains, 8 floating clusters and 386 loose ends, is in an allow list of 35 entries. Each entry has its box, its exact count and its reason, so an entry that no longer matches fails as surely as a new floating piece. One entry is an open decision rather than a known departure: the entretoise at XXI in the Otis legs.

Siblings of the same kind: eiffelRivetClash (no lacing head inside a joint or crossing plate), eiffelStairsClearance and eiffelStairsHeadroom, eiffelArchEnds (the first floor lands on the web).

Limits

What the model does not claim.

  • Structure. The large structure is the plates' to the precision the plates print. Below the panel scale the model is a set of rules whose parameters are plate readings, often [estimated] or [measured]. Most members are where their rule puts them, not where the drawing shows that particular member.
  • Rivet pitches. They are measurements of a few details, each applied by rule wherever its kind of joint recurs: the seams' 125 mm from one gusset on Plate IX, the splice fields from two of its figures.
  • Rivet counts. The model draws 1,205,292 rivet heads. The only count the book prints is of site rivets: 1,050,846, of which the model draws 334,659, about a third. The 2.5 million often quoted for the whole tower is not printed there.
  • Letterforms. The names' letterforms follow the 1986–87 restoration, because no 1889 photograph is sharp enough to read them.
  • Later states. The 1900 album occasionally draws a post-1889 state. Where the text or an 1889 figure does not settle it, SOURCES.md says so.

Some departures are deliberate. Each is named where it occurs, and recorded beside the code that makes it or in SOURCES.md:

  • The ground is one flat plane, where the Champ de Mars stood higher round the piles, so the plinths stand 1.58 m low.
  • The supports and massifs sit 1.10 m down their uprights' axes, because the model's uprights run to Plate XXXI's support node.
  • The lifts run at a speed chosen for the page.
  • The erection's cranes pass through panels 12–13, and the post cranes' jibs through the iron and rooms above them.

The build draws two seeded values and no others: the 1 mm offset (jitterPoints) and each rivet's headDrive (randomField). Both are hashed from two inputs: the graph's seed, 1889, and each point's identity, which is its position and its seed column. Outside the graph, the page's flag draws its wind from the same number.

Nothing reads an unseeded source, so the same source cooks to the same bytes on every run. tests/eiffel.golden.json holds the hashes, and the demo's commits use them as their control when a refactor claims to change nothing.

Reproduce

Commands.

npm run examplesServe the demo at /demos/eiffel/. Depth: ?laced or ?rivets for a whole depth; =streamed or =off on either; ?rivets=full for whole rivets plus the built-up uprights' rows as built; ?bands to stream both. View: ?cam=px,py,pz,tx,ty,tz for a pose, ?fov=, ?still, ?hl=, ?site, ?section=<m>, ?walk with ?walkAt and ?walkNear, ?build[=yyyy-mm-dd], ?wave, ?cpuRivets. "Copy view" in the panel writes the URL for the current frame.
npx vitest run tests/eiffelThe demo's tests, one file per subject. Among them: the skeleton, which checks every upright vertex on the plate's profile, the 16/12/8 upright progression, members squared to their faces and the JSON round trip; the golden hashes; the built-up uprights, splices, rivet counts and heads; stairs, lifts, names, the ground and the erection; the streaming cells; and the floating-piece check.
npm run capture:guides -- --only=eiffelRegenerate the demo renders on this page from the shot list in scripts/capture-guides.mjs; run it after a lighting or material change. The audit overlays are records and are not in the shot list.

Next

Where to go from here

The demo is at demos/eiffel, and its graph is shown read-only in its control panel. demos/eiffel/SOURCES.md is the research behind spec.ts, source by source. Architecture is how the library underneath is put together, and the corpus gallery is every graph in the repository, cooked and shot.

← back to the landing page