# Fly Brain: server-selected 80% activity upgrade

Client revision: 1.9.0. Wire format: `section_top80_v1`.

## Task for the server agent

Update the running Strix Halo neural worker to select the strongest 80% of neurons **independently in each section**, then return only selected nonzero spike counts in the compact format below. The Hostinger client, forwarding routes, and documentation are prepared for this contract. The worker must advertise support before the client sends the new parameter.

This supersedes the unactivated v1.8.0 proposal that sent complete snapshots and selected 80% in the browser. Do not implement that older proposal or its `activity_snapshot` parameter.

## Why this change

1. The old global top-1,000 response can contain only visual neurons, hiding weaker activity elsewhere. Per-section selection preserves representation of Central, Motor, and Other.
2. Selecting on the worker avoids transmitting the excluded 20%. Suppress selected zero values as well: the client knows the complete layout and paints missing values black.
3. Send compact indices and integer counts, not repeated neuron IDs, names, classes, floating-point rates, or voltages. Rate is exactly `count * 1000 / interval.duration_ms`.
4. Cache static section/retinal layout separately. A typical frame should contain only changing activity, with no repeated coordinates.
5. Use HTTP gzip on every network leg. Hostinger proxies negotiate/decode upstream compression and negotiate gzip with browsers. Add compression to the worker too.
6. Omit activity entirely while collapsed. Do not compute, serialize, or cache an activity payload when it was not requested. Existing 14 readouts and summary metrics continue to work.

The 80% rule does not guarantee a 20% reduction compared with an already sparse complete snapshot: excluded neurons may all be silent. Compact encoding and cached metadata supply additional savings. Compared with the old capped 1,000-neuron response, transmitting broader coverage may still use more bytes; measure both coverage and transferred size.

## Selection and display semantics

1. Section order is Visual, Central, Motor, Other. Classify `motor` or `descending` names as Motor; otherwise `ol_*`, `visual`, or `photoreceptor` as Visual; otherwise `cb_*`, `ascending`, or exactly `vnc_intrinsic` as Central; remaining neurons are Other. Force the known retinal indices into Visual. The reference encoder implements this exact order.
2. Within a section of N neurons, select `floor(4 * N / 5)`, including zero-valued candidates in the conceptual quota. Sort by descending interval spike count; break ties by ascending canonical graph index. Never select globally across sections.
3. Only selected neurons with a positive count need records. At least 20% remains black; more is black if neurons inside the quota are silent. Never fabricate activity to force exactly 80% to light up.
4. Use fixed logarithmic coloring: `t = log1p(clamp(rate_hz, 0, 500)) / log1p(500)`. Hue moves from blue at low values through cyan, green, yellow to red at 500+ Hz. All sections and frames use the same scale; do not renormalize against each frame's maximum or percentiles.
5. The retinal view uses the actual inferred UV coordinates used by this installed model's retinal sampler. Other groups use stable schematic grids. Do not substitute arbitrary retinal positions or claim anatomical depth for the schematic grids.

## Existing behavior to preserve

1. Independent image and pulse requests reset state. Video calls use the existing leased session and preserve state on pause/resume within the fixed lease.
2. Preserve 512 unique steps, the 900-second maximum/default lease, the shared 120-request/minute quota, authentication, and request-size limits.
3. Preserve idempotent session retries: the same request ID and input must return the same response without advancing state. Include `activity_view` in the request hash. Cache the exact activity payload with the existing result; do not regenerate it from a later brain state.
4. Compute the activity from the same `counts` and time interval used by the normal response, inside the worker's existing exclusive lock. Never run extra simulation steps to gather the sections.
5. Keep `top_k` accepting 0–1000 and existing response fields unchanged. The new client uses `top_k: 0` to avoid duplicate top-neuron records.

## Required capability advertisement

Add this object to `GET /v1/model` only after the layout endpoint and step response work:

```json
{
  "activity_view": {
    "formats": ["section_top80_v1"],
    "layout_path": "/v1/activity-layout",
    "layout_id": "<stable-layout-fingerprint>",
    "keep_fraction": 0.8,
    "max_color_hz": 500
  }
}
```

Use a layout ID that changes when graph order, section assignments, or retinal coordinates change. The reference implementation combines the existing graph hash with a hash of display order and UV coordinates. The client caches layout by this ID. Do not advertise the capability before it is usable; the pre-upgrade client view will show “Server update pending” while regular neural calls work.

## Static layout endpoint

`GET /v1/activity-layout` returns:

```json
{
  "format": "section_top80_v1",
  "layout_id": "<same-id-as-model>",
  "neurons": 166700,
  "sections": [
    {"name":"Visual", "offset":0, "count":"<actual integer>"},
    {"name":"Central", "offset":"<sum before this section>", "count":"<actual integer>"},
    {"name":"Motor", "offset":"<sum before this section>", "count":"<actual integer>"},
    {"name":"Other", "offset":"<sum before this section>", "count":"<actual integer>"}
  ],
  "retina_encoding": "u32le-u16le-u16le",
  "retina_base64": "<packed retinal records>"
}
```

The placeholders above must be replaced with real numbers. Sections occupy contiguous display-index ranges, covering every neuron exactly once; each section preserves canonical graph order. No per-neuron section labels or IDs are required in the response.

Each retinal record is exactly eight bytes: a uint32 little-endian **display index**, followed by uint16 little-endian U and V. Encode U/V as `round(clamp(uv,0,1) * 65535)`. Records use standard base64 as a single string. At 3,335 retinal neurons, this is 26,680 binary bytes / 35,576 base64 characters before HTTP compression, plus the small section header. Coordinate error is below 0.01 source-image pixels at 640×480. This quantization only affects display positioning, never the worker's original input sampling.

## Optional step parameter and response

The client sends:

```json
{"luminance":0.5,"duration_ms":100,"top_k":0,"activity_view":"section_top80_v1"}
```

The parameter is optional and has exactly one supported string value. Omission means no activity selection or serialization. Preserve the normal response and add:

```json
{
  "activity": {
    "format":"section_top80_v1",
    "layout_id":"<same-id-as-model>",
    "encoding":"delta-uvarint-count-v1",
    "data":"<base64 packed bytes>",
    "record_count":"<actual nonzero records integer>",
    "sections":[
      {"total":"<N>","selected":"<floor(4*N/5)>","active":"<positive neurons before cutoff>","transmitted":"<positive selected records>"}
    ]
  }
}
```

There must be **four** section-summary objects in the same order as the layout. All placeholder values are integers. `record_count` equals the sum of `transmitted`.

### Binary encoding

1. Convert selected canonical indices to their static display indices. Sort the records by ascending display index across all sections.
2. Set previous index to **−1**. For each record, write `display_index - previous` as an unsigned LEB128 integer; then write the positive integer spike count as unsigned LEB128. Set previous to display_index.
3. Unsigned LEB128 uses seven payload bits per byte; bit 7 indicates another byte follows. Integers must fit uint32 and use at most five bytes. Deltas are always positive. Concatenate the pairs, then standard-base64 encode once.
4. An empty result uses `data: ""` and `record_count: 0`. Do not send zero counts, duplicate indices, padding records, names, raw floats, or omitted-neuron lists.
5. This is a self-contained selected snapshot per step. It has no dependency on the previous frame, so pause/resume, retries, errors, and skipped frames cannot corrupt decoding. “Delta” refers only to index differences within one payload.

Typically a nearby-index/small-count record requires two binary bytes, compared with eight for a pair of uint32s and much more for an individual JSON object. Actual sizes depend on index gaps and count magnitude. Gzip compresses the enclosing JSON/base64 further.

## Supplied implementation and deployment

1. `neural_activity.py` is a ready-to-integrate encoder and layout builder using NumPy already required by the worker.
2. `neural_api.py` is a proposed integration based on the source currently published on Hostinger. Compare with the running worker and merge the small additions; do not overwrite newer or machine-specific changes blindly. Add the optional field, initialize `ActivityEncoder` once at startup, serve its layout, advertise the capability, add `activity_encoder.encode(counts)` only when requested, and enable Starlette `GZipMiddleware`.
3. `activity.mjs` is the deployed client's decoder, provided for protocol testing. Do not deploy a second client. The remaining job is the worker update.
4. Back up changed worker files under its `old/` directory, then validate Python syntax and run the cross-language test with the worker's Python environment and Node: `python test_activity.py | node test_activity.mjs`.
5. Restart through the worker's existing service manager when no session is active; a restart discards session state. Keep secrets, networking, certificates, and service configuration unchanged.
6. Verify the endpoints through `https://www.promptbox.cn/api/doomfly.php?path=...`, as well as directly with worker authentication. Hostinger already allows the new GET route. The new parameter is passed through unchanged.
7. Open Fly Brain Lab, expand Neuron activity and click **Check server update** (or reload once). The client rechecks model capability and starts using the new format automatically. No automatic polling or repeated capability checks are needed.
8. Update the published worker-source mirror and source archive after the actual worker is running the new version. Do not falsely label the running worker upgraded before deployment.

## Acceptance checks and measurements

1. Dense synthetic counts: exactly `floor(0.8*N)` records per section, independent of brightness/activity in other sections. Test unequal section sizes and equal-count ties.
2. Sparse/all-zero counts: fewer records or an empty payload; black counts remain honest. Verify full model population totals and original readouts remain unchanged by enabling the view.
3. Map the encoded values back through display order and compare with the selected worker counts. Test multi-byte index deltas/counts, malformed payload rejection, and retinal corner coordinates.
4. The same firing rate must yield the same color in successive frames and at different interval durations. Check low values are blue, intermediate values cyan/green/yellow, and saturation red.
5. Retry a session request and confirm identical activity bytes and no extra simulation step. Pause/resume continues within the same lease. Image calls remain independent.
6. With activity_view omitted, verify the response contains no activity field and no encoder work occurs. With top_k 0, top_spikes stays empty.
7. Verify Content-Encoding gzip when Accept-Encoding requests it, through worker → Hostinger API → browser bridge. Verify plain requests without gzip negotiation still work.
8. Measure uncompressed JSON and actual compressed transfer sizes for a dark image, bright image, patterned image, and representative video sequence. Compare against full individual JSON records and full sparse counts; do not assume bandwidth drops by exactly 20%.
9. Check cached layout reuse, layout invalidation after a changed layout_id, and bounded memory for up to 512 cached session replies. If required, cache compressed immutable activity bytes server-side without changing retry semantics.

## Documentation locations

1. Human reference: `https://www.promptbox.cn/api/docs.html#doomfly`.
2. Compact discovery: `https://www.promptbox.cn/api/catalog.json`.
3. Full helper contract: `https://www.promptbox.cn/api/catalog/doomfly.json`.
4. Agent index: `https://www.promptbox.cn/llms.txt`.
5. Fly Brain guide: `https://www.promptbox.cn/logi/logi_code/doomfly-api/` and `API.md`.
6. This protocol is mirrored as `activity-protocol.md` in that guide directory. Catalog and llms.txt are generated from docs.html by agent-resources.php; do not create competing static copies.

Mapping reference: [DOOMFLY retinal_samples](https://github.com/nftechie/doomfly/blob/main/doom/game.py) uses `x=u*(width-1)` and `y=v*(height-1)` for image sampling. Retinal firing is transformed by neural dynamics and need not reproduce the image photographically.
