Skip to content

Add Pixely.Observations, an append-only log readers drain at their own pace - #488

Merged
stanoddly merged 5 commits into
mainfrom
observation-log
Sep 10, 2026
Merged

Add Pixely.Observations, an append-only log readers drain at their own pace#488
stanoddly merged 5 commits into
mainfrom
observation-log

Conversation

@botoddly

@botoddly botoddly commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #486.

Game state answers what is true now. It does not answer a transition that no pair of reads one frame apart
contains: a unit that walked a path and arrived, one that appeared and was gone again, an action that resolved
and was reversed inside the same frame. Rules can resolve many such transitions between two reads, so any
"most recent transition" field is overwritten before a reader looks at it.

Pixely.Observations is a new project holding an append-only log that readers drain at their own pace, with no
dependency on anything else in the tree.

Shape

Three types, each with one job:

  • ObservationLog<TEntry> is the storage. It is constructed and handed to the other two, and has no other
    public members.
  • ObservationWriter<TEntry> appends and cannot read.
  • ObservationReader<TEntry> is one reader's position in the log, and cannot append.

A rule holding a writer has no way to drain the log, and a reader has no way to record an observation no rule
produced. That split is by type rather than by an interface projected over one class, so nothing has to trust a
narrower view of a wider object.

Each consumer constructs its own reader instead of being handed one, because every reader of a given log is the
same closed type and the container resolves by type. Constructing it also lets the consumer name itself, which
is what the stall message reports.

Entries

One log carries one entry type. To carry several kinds of entry in one order, TEntry is a tagged type the log
never looks inside. A value type keeps heterogeneous entries and no per-entry allocation from fighting each
other: entries go in by in, are stored inline in the ring, come out by copy, and the trim walks the reader
list with the struct enumerator rather than LINQ. A class entry type is allowed where a game prefers one, and
the log releases each slot as it trims so a drained entry is not held alive.

Addressing is the game's business. The perceiver rides in TEntry and the reader skips what it did not
perceive, so the log stays ignorant of participants.

Bounds

The buffer starts at 16 and doubles towards the maximum capacity given to the constructor, so a log is not
sized for its worst burst up front. It never shrinks, and it is reclaimed when its scope dies.

Maximum capacity is a stall detector rather than a working size. A reader that stops draining holds the trim
point where it stopped; once the log fills, appending throws and names the reader that stopped and how far
behind it is. Dropping the oldest entry instead would hide exactly that failure.

Tests

tests/Pixely.Observations.Tests covers ordering, a reader seeing only what follows its creation, independent
pacing between readers, growth past the initial capacity, growth while the retained entries are wrapped so the
copy runs in two segments, growth stopping at a maximum that doubling would overshoot, a maximum below the
initial capacity, wraparound under interleaved append and read, trimming holding steady over 3200 entries in a
32-slot log, the stall message naming one reader and naming several tied at the back, appending succeeding
again once the stalled reader drains, disposal freeing a stalled log while preserving what another reader still
needs, double disposal, reads after disposal, and a weak-reference check that a class entry really is released
when the log trims.

The documented registration was verified against the real DI source generator rather than asserted from the
docs, and the [Explicit] package archive test was run by filter to confirm the new assembly ships.

Not in this PR

Pixely.Events and Pixely.Architecture are untouched. Removing them is a separate change.

@stanoddly
stanoddly merged commit 19bc753 into main Sep 10, 2026
1 check passed
@stanoddly
stanoddly deleted the observation-log branch September 10, 2026 10:08
@github-actions

Copy link
Copy Markdown

✅ Development package Pixely 0.0.38 published successfully.

Workflow run (attempt 1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support an ordered record of transitions that readers drain at their own pace

2 participants