Chapter 3 · Section 16
Gravity Rings: RingField and the Adaptive Ring Search
The BPT law
BPT Part 2.8 defines the beat the rings keep: the Local Universe Time Crystal duration is "the Pulse Rhythm: the time to cross the Pulse Diameter one way," ⧖⌂ = ↁℨ⧖ × 2ⁿ × f₀ⁿ × ⟪C⟫. BPT Part 7.5 (The Resonant Field of Becoming, Interference Dynamics) gives how pulses add where they meet: the Constructive Interference Amplitude, A⋓ = |Σ_i A_i e^(iφ_i)|, which is greatest "when phase relationships satisfy integer multiples of 2π (φ_i − φ_j = 2πn)", with destructive interference at (2n + 1)π making null zones of radius r∅ = λ/4. The game's rings join the two. The comment at rings.js:4–15 reads: "Every well of a universe pulses, and each pulse sends a ring outward at one cell per pulse: the Pulse Diameter per Rhythm." It cites BPT Part 2.8 for the Pulse Rhythm as the Time Crystal duration, Chapter 7 for interference and harmonics, and Chapter 5 for phase coupling. Where k rings meet in the same frame is a convergence of depth k: 2 at a vesica tip, 3 where three petals touch, 6 at a daisy's center (the flower of life). That each well sends rings is the game's reading of the Pulse Rhythm; BPT does not state rings from wells as such. Their search is a game method.
The established physics
None: a game method. Two circles meet at zero, one or two points, by plane geometry (circleMeets() rings.js:70).
The game's math
ensureRings() app.js:565 builds the field:
new RingField({ period: pulseLength = 2, speed: 1, maxRadius: max(rows, cols)/2,
eps: 0.35, maxRings: 10, maxSources: max(4000, ⌊rows·cols/4⌋), ringSources: 32 })
- Rings of a well,
radii(src, t)rings.js:42: one ring per period since birth, radius (age − k·period)·speed. They stop atmaxRadiusor at the newestmaxRings. - A ring through a point,
ringThrough()rings.js:55. A ring of radius d left at age − d/speed. It exists if that time is within eps/speed of a multiple of the period. - The search,
step(t)rings.js:87. It takes the youngestringSourceswells. For each pair, and each pair of their rings with both radii at least 2, it finds where the circles meet. Each meeting is checked against every other well's rings, and the depth is the count passing through. A meeting that recurs at the same rounded spot, seen on three beats with depth ≥ 2, is announced as a time crystal (a node). A crystal unseen for 4 periods is forgotten.alive(t)returns crystals seen within one period. The search is O(n²·r²·n) in the n wells searched. - The adaptive count,
stepFinds()app.js:1918–1923. The search is timed each step. If it took under 4 ms and more wells exist than are searched,ringSourcesgrows by 15 %. If it took over 8 ms, it shrinks by 15 %, never below 8 (ruling 2026-10-29: "swap that"; it was a fixed 32). - Node finds are off.
NODE_FINDS = false(app.js:231, ruling 2026-10-28): the rings still meet and are drawn, but their meetings are not collected.
The sources are the wells. rings.sources is the game's list of physical wells and sites, which every other Part reads.
In the game
The rings are drawn in the 2D view ("2D · the field" in the view menu, index.php:141), from the big bang on: every well and every site sends thin cyan circles out from its center, one a Tempo, fainter as they widen (drawGrid() app.js:4096–4101). Where rings meet in step the live crystals show as small diamonds, colored by their rarity's hue and larger for a greater depth, 3 + 1.5·depth pixels (app.js:4104–4107). The drawer switch "Show the wells' rings" (index.php:194, on by default) turns the circles off in 2D; in 3D the same switch shows or hides the halo ring round each well and each Proto Well (scene3d.js:1526, 1646). The 3D view does not draw the expanding rings. Its crystal markers, small glowing octahedra, at most 120 (setCrystals() scene3d.js:1773–1781), are cleared once the matter is drawn (scene3d.js:1775), and the rings only begin at the big bang, when the first wells and sites join rings.sources, so in 3D the meetings are not marked. The help card "Finds" tells the player: "Every well sends rings; where rings meet in step a harmonic node forms, deeper where more meet" (index.php:309). While NODE_FINDS is off the meetings are drawn but no node is added to the Collection.
The scale model
The rings are drawn in drawn cells and flips. One ring per Tempo per drawn well. The ring search covers only the youngest ringSources drawn wells, so convergences among older wells are not found. That only bears on what is found, not on any physics.
Where the game and the theory differ
addSource()rings.js:34 still drops the oldest source when the list passesmaxSources:if (this.sources.length > this.maxSources) this.sources.shift(). The comment at rings.js:26–27 says sources "are no longer dropped". That holds only while the list stays undermaxSources= max(4,000, ⌊rows·cols/4⌋): 4,000 on a phone and 16,384 on a desktop. On a desktop at level 202, the comment at matter.js:245 counts about 15,000 sites, which leaves room for fewer than 1,400 more wells. Past that, the oldest well or site is silently removed from gravity, the census and the lineage.- The rings live on a torus, but
circleMeets()rings.js:70 andringThrough()rings.js:55 use plain plane distance, andstep()skips meetings that fall outside the field (rings.js:101), so rings that meet across the seam are not found. The rings also stop at half the field,maxRadius= max(rows, cols)/2.
Fixed numbers
| Number | Where | What it is | Kind |
|---|---|---|---|
| 2 | pulseLength app.js:51 |
period: a ring per Tempo, two flips | BPT (Pulse Rhythm, Part 2.8) |
| 1 | app.js:565 | ring speed, a cell a flip | BPT (Pulse Diameter per Rhythm, Part 2.8) |
| 1/2 | app.js:565 | maxRadius, half the field | CAP (a fixed reach; the meeting geometry ignores the wrap, so half the field is where it stops being right) |
| 0.35 | app.js:565 | eps, how close a ring must pass | METHOD (tolerance on a rasterized ring) |
| 10 | app.js:565 | maxRings, newest rings checked | CAP (older rings are not searched) |
| 4000, 1/4 | app.js:565 | maxSources | CAP (past it the oldest well is dropped from the game) |
| 32 | app.js:565 | ringSources at start | CAP (the starting search count) |
| 3, 400, 0.6, 6, 24, ∞ | rings.js:19 | constructor defaults, all overridden by ensureRings() |
CAP (not in effect) |
| 2 | rings.js:99 | rings under radius 2 skipped: "right on a well is the well" | METHOD |
| 1e-9 | rings.js:79 | tangent test | METHOD |
| 3 | rings.js:126 | beats seen before a crystal is announced | METHOD (a pattern must repeat) |
| 2 | rings.js:126 | least depth of a crystal | BPT (a vesica, two rings; rings.js:13) |
| 4 | rings.js:129 | periods before a crystal is forgotten | RECORD |
| 1 | rings.js:133 | crystal alive within one period | DISPLAY |
| 4 ms, 8 ms | app.js:1922–1923 | search time budget | CAP (a device-time budget on the wells searched) |
| 1.15, 0.85 | app.js:1922–1923 | growth and shrink steps | METHOD (control-loop gains) |
| 8 | app.js:1923 | least wells searched | CAP |
| 12 | app.js:1934 | pulses between node pops (inert while NODE_FINDS is false) | DISPLAY |
| 3, 1.5 | drawGrid() app.js:4105 |
2D crystal diamond size, 3 + 1.5·depth px | DISPLAY |
| 120 | setCrystals() scene3d.js:1777 |
crystal markers drawn in 3D | DISPLAY |