Pet conformance: Rust core diverges from the canonical TypeScript on six of eight channels (portable red)
- Dominant language
- Rust
- Stars
- 41k
- Forks
- 3.6k
- Avg merge
- 14h 18m
- Merged PRs (30d)
- 286
Description
`portable` (Pet conformance) has been red on `v0914-chunk2` since `a06d2296` and has never run on `main`. Reproduced locally with `./pet/verify.sh --no-swift` and narrowed to something more specific than "parity mismatch".
## The state machine agrees; the field does not
`verify.sh` has `set -e`, so it dies on the first v1 diff and never reports v2. Running both by hand:
| Expression | Frames differing |
| --- | --- |
| v1 (`--legacy`, `PetSim::legacy_whale()`) | **68 of 68** |
| v2 (`PetSim::whale()`) | **53 of 68** |
The label column is **byte-identical** between TypeScript and Rust on both — `6 reasoning, 5 tool, 7 code, 5 filesystem, 5 network, 3 error, 5 code, 10 agent, 7 human, 14 reasoning, final`. Same frames, same channel sequence, same ordering. Only the digests differ, and they differ from **frame 0**, so this is not accumulated drift.
`digest()` quantizes the particle field onto a 64×32 grid over [-0.66, 0.66]² and FNV-1a's the counts plus the frame RGB. So the two cores agree on *what the pet is doing* and disagree on *where the particles are or what colour the frame is*.
## It is channel-dependent, which is the useful part
Breaking v2's 68 frames down by channel:
| Channel | Match | Differ |
| --- | --- | --- |
| `filesystem` | **5** | 0 |
| `network` | **5** | 0 |
| `code` | 3 | 9 |
| `human` | 1 | 6 |
| `reasoning` | 1 | 19 |
| `agent` | 0 | 10 |
| `error` | 0 | 3 |
| `tool` | 0 | 5 |
`filesystem` and `network` are in exact agreement. Everything else has drifted. That is a strong hint the divergence is in per-channel field expression rather than anything global like a float-precision or grid-quantisation difference — those would break all eight channels uniformly.
## Which side is canonical
TypeScript. `verify.sh` diffs the committed fixture against the TS output *before* it diffs TS against Rust:
```sh
if [ "$expression" = v1 ]; then diff -u "tests/fixtures/v1-$name-$mode.txt" "$stem-ts.txt"; fi
diff -u "$stem-ts.txt" "$stem-rs.txt"
```
The fixture diff passes and the TS↔Rust diff fails. So `PetSim.ts` still matches the recorded v1 contract and `crates/tui/src/tui/ambient_life/pet_sim.rs` — which `rs/pet_sim.rs` includes directly, so the Rust core *is* the product implementation — is the side that moved.
Last touched by `334c720057` ("feat(pet): express work through versioned particle fields") and `5450b09ff3`. Given the v1 path (`legacy_whale()`) differs in *every* frame, the versioning work looks like it did not preserve legacy field expression, and the v2 path drifted on six of eight channels besides.
## Scope
This is product logic, not a test defect, and reconciling it means making two independent implementations of a particle simulation agree channel by channel. It needs the design intent behind "versioned particle fields" — specifically whether Rust or TypeScript holds the intended v2 behaviour, since the fixture only pins v1.
Not a merge blocker: `portable` is not in the `protect-main` required set (that is `Lint`, `Test (windows-latest)`, `npm wrapper smoke (ubuntu-latest)`).
Reproduce: `cd pet && npm ci --ignore-scripts && ./verify.sh --no-swift`, or for the per-version split, run `node run-tape.ts run [--legacy] < tape.tsv` against `(cd rs && ./petsim [--legacy] < ../tape.tsv)`.
Contributor guide
Research direction
Run `cd pet && npm ci --ignore-scripts && ./verify.sh --no-swift`, then inspect `PetSim.ts` and `crates/tui/src/tui/ambient_life/pet_sim.rs`, including the `legacy_whale()` and `whale()` paths. Compare the per-channel outputs with the documented v1 and v2 splits; done means the fixture still passes and TypeScript and Rust digests agree across all frames and channels.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, shell, typescript
- Domain
- cli, testing
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100