Skip to content

M-MEM: multi-event-per-tick mapper (merge + arbitration) - #11

Merged
RobertMalczyk merged 7 commits into
mainfrom
feature/m-mem-multi-event
Jun 24, 2026
Merged

RobertMalczyk merged 7 commits into
mainfrom
feature/m-mem-multi-event

Conversation

@RobertMalczyk

Copy link
Copy Markdown
Owner

M-MEM — multi-event per tick

Lifts the one-RawEvent-per-tick constraint so a tick can carry 0, 1, or many events, each mapped+filtered and merged into the effective input, with deterministic multi-source arbitration. Engine-only, reusable; unblocks the moral M-J.3.3 witness fan-out (review R7).

Slices

  • M-MEM.0 — EffectiveInputVector → dict[str, list[SemanticInput]]; run_scenario groups same-tick events; tick maps+merges every event; update sums per-channel input lists; debug._inputs_dict serializes one input as the bare object (byte-identical) and several as a list.
  • M-MEM.1 — _provocation_score elects the strongest provoker as primary (ties keep scenario order); the per-source reactive signals key on it.
  • Docs propagated across spec + diagrams + README (docs/m_mem_PLAN.md).

Invariant

A ≤1-event tick is byte-identical with the pre-M-MEM engine — existing goldens unchanged.

Tests

Full suite 309 passed, 4 skipped after integrating current main; goldens byte-identical; ruff clean. New tests/test_multi_event.py (6): two same-tick events both land; strongest-provoker arbitration; gesture-vs-provoker; N-source fan-out shape; empty/single-event paths.

🤖 Generated with Claude Code

Robakk84 and others added 7 commits June 24, 2026 14:03
…mapper

Engine-only capability (reusable, not moral-specific) that lifts the one-RawEvent-per-tick constraint
(run_scenario's events_by_t dict silently drops same-tick collisions). Records the design: events list,
map+merge with per-source attribution, deterministic primary-provoker arbitration, opt-in multi-event trace
field, ≤1-event/tick byte-identical. Slices M-MEM.0 plumbing -> .1 arbitration -> .2 fan-out helper.
Unblocks the moral M-J.3.3 witness fan-out (review R7) once merged to main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016rWVvvhWijSuLMEhzjiUhu
…cal for <=1 event/tick

Lift the one-RawEvent-per-tick constraint. run_scenario's events_by_t was a dict {ev.t: ev}, silently
dropping same-tick collisions; tick() took a single event and every per-tick signal derived from it.

- schema: EffectiveInputVector is now dict[str, list[SemanticInput]] -- a channel may carry SEVERAL inputs
  in one tick (multiple sources firing the same channel). A single-event tick yields one-element lists.
- simulation.run_scenario: group events sharing a tick into a list (scenario order), not a colliding dict.
- simulation.tick(events): accepts a bare RawEvent / list / None (backward-compatible); maps + filters EACH
  event and merges into eff (channel -> list). A PRIMARY event (first; M-MEM.1 will arbitrate the primary
  provoker) drives the per-source reactive signals (provocation/kindness/bystander/refractory/reaction_target/
  bookkeeping) -- byte-identical for <=1 event. history_log appends ALL events.
- update.compute: global gains, event_sources, and relation gains sum over the per-channel input lists.
- simulation provocation/stressor/kindness helpers iterate the lists.
- debug._inputs_dict: a single input on a channel serializes as the bare object (byte-identical with pre-
  M-MEM goldens); several inputs serialize as a list (only appears in multi-source traces).
- tests/test_multi_event.py (3): two same-tick events from different sources BOTH land (today one dropped);
  empty tick is idle; single-event trace keeps the scalar event field.

Full suite 288 passed +4 skipped; goldens byte-identical (test_tick_golden unchanged); ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016rWVvvhWijSuLMEhzjiUhu
When several events land on one tick, the per-source reactive signals (reaction_target,
last_provocation_source, refractory/bystander policy) must key on the RIGHT source. M-MEM.0 used the first
event; M-MEM.1 elects the STRONGEST provoker as `primary`.

- simulation._provocation_score(event, ev_eff): how strongly ONE event provokes from its OWN filtered
  inputs -- the anger/frustration contribution (case 1) or, for a gesture from a resented source, that
  resentment (case 2); 0 for a sourceless stressor or benign non-resented gesture. Mirrors
  _event_is_provocation for a single event.
- tick: during the perception merge, track the max-score provoker (strictly-greater so ties keep the
  earlier scenario-order event) and elect it `primary`; fall back to the first event when nothing provokes
  (benign gesture / stressor / activity). A <=1-event tick is unchanged -> byte-identical.
- tests/test_multi_event.py (2): the strongest of two same-tick insults becomes primary (weak listed
  first); an insult beats a co-occurring benign gesture for primary (gesture listed first).

Full suite 290 passed +4 skipped; goldens byte-identical; ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016rWVvvhWijSuLMEhzjiUhu
….2 to the consumer

The reusable engine seam (several events on one tick, correctly merged + arbitrated) is complete with
M-MEM.0/.1. The fan-out EXPANSION rule (a public accusation => each witness emits suspicion) is domain
logic and belongs to the moral consumer M-J.3.3, not a generic engine helper -- so M-MEM.2 adds no
speculative engine code. Branch is merge-ready: merging to main unblocks M-J.3.3.

- tests/test_multi_event.py: add the witness-fan-out shape test (one accuser + three witnesses on one tick,
  every source's deposit lands) -- a concrete proof the seam suffices without an engine fan-out helper.

Suite 290 passed +4 skipped; goldens byte-identical; ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016rWVvvhWijSuLMEhzjiUhu
…docs

Update the spec + block diagrams + README so the event model matches the implemented engine: a tick may
carry 0/1/MANY events, each mapped+filtered per event and MERGED into the effective input (channel -> list
of inputs, summed by update), with the per-source reactive signals keyed on the strongest provoker
(primary). A <=1-event tick is byte-identical.

- spec_v1: EffectiveInputVector is dict[str, list[SemanticInput]]; tick order loops events + merges + picks
  primary; update equation sums a channel's inputs; section 1/5 notes; stage-2 note records the M-MEM seam.
- diagrams: simulation (invariant + tick steps 3/10), mapper (per-event, merged in tick), update (eff is
  list-valued; gain * sum-of-inputs), relation_filter (per-event filter, merge in tick), wiring (note + seam).
- README: M-MEM bullet under current work.

No HTML architecture doc exists (the only .html is the regenerable burst-regression report). No code change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016rWVvvhWijSuLMEhzjiUhu
Integrate the advanced main (believability/eval work, PRs #9/#10) before merging M-MEM back. Conflict-free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016rWVvvhWijSuLMEhzjiUhu
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016rWVvvhWijSuLMEhzjiUhu
@RobertMalczyk
RobertMalczyk merged commit f748719 into main Jun 24, 2026
3 checks passed
@RobertMalczyk
RobertMalczyk deleted the feature/m-mem-multi-event branch June 24, 2026 15:59
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.

2 participants