lex-cognitive-immune-memory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 012131c8a1758dd81ca39180981eeece28699fdd6e29b9e21c00150fbee3ba2b
4
+ data.tar.gz: 9fbc8c5b7f8d3cdc8e63e21de41b6012fd1e78266d78e214b0f1956f45c0da15
5
+ SHA512:
6
+ metadata.gz: 2f36b3a716fef71808de082745ec4cec13e31147d1e60140d3f18c4b0cedad0ffbb554b8823ab33dd3c2ffa7cf7d3f4b843e5b6ae060a978ca40408b8ba8b984
7
+ data.tar.gz: 9d2318d3428096bc2d882fbd954ffcf4728e8e4bcb8fa9ddf4864b3c1e3f1d62c830f28ad33918ec8f4da1617dde938ee1fd6803e9f672f87226f4e493300112
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ ci:
9
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
10
+
11
+ release:
12
+ needs: ci
13
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
+ uses: LegionIO/.github/.github/workflows/release.yml@main
15
+ secrets:
16
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ pkg
7
+ spec/reports
8
+ tmp
9
+ Gemfile.lock
10
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --order random
data/.rubocop.yml ADDED
@@ -0,0 +1,37 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ TargetRubyVersion: 3.4
4
+
5
+ Style/Documentation:
6
+ Enabled: false
7
+
8
+ Naming/PredicateMethod:
9
+ Enabled: false
10
+
11
+ Naming/PredicatePrefix:
12
+ Enabled: false
13
+
14
+ Metrics/ClassLength:
15
+ Max: 150
16
+
17
+ Metrics/MethodLength:
18
+ Max: 25
19
+
20
+ Metrics/AbcSize:
21
+ Max: 25
22
+
23
+ Metrics/ParameterLists:
24
+ Max: 8
25
+ MaxOptionalParameters: 8
26
+
27
+ Layout/HashAlignment:
28
+ EnforcedColonStyle: table
29
+ EnforcedHashRocketStyle: table
30
+
31
+ Metrics/BlockLength:
32
+ Exclude:
33
+ - spec/**/*
34
+
35
+ Style/OneClassPerFile:
36
+ Exclude:
37
+ - spec/spec_helper.rb
data/CLAUDE.md ADDED
@@ -0,0 +1,116 @@
1
+ # lex-cognitive-immune-memory
2
+
3
+ **Level 3 Leaf Documentation**
4
+ - **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
5
+ - **Gem**: `lex-cognitive-immune-memory`
6
+
7
+ ## Purpose
8
+
9
+ Models long-term immunological memory for cognitive threat recognition. Memory cells are trained records of past threats — each encounter with a known threat type produces a primary response and creates or strengthens a memory cell. Subsequent encounters trigger secondary responses that are faster and stronger than primary. Vaccination artificially installs memory cells without requiring a live encounter. Memory cells decay over time if not reinforced. Tracks threat coverage, neutralization rate, and average response speed across the cell population.
10
+
11
+ ## Gem Info
12
+
13
+ | Field | Value |
14
+ |---|---|
15
+ | Gem name | `lex-cognitive-immune-memory` |
16
+ | Version | `0.1.0` |
17
+ | Namespace | `Legion::Extensions::CognitiveImmuneMemory` |
18
+ | Ruby | `>= 3.4` |
19
+ | License | MIT |
20
+ | GitHub | https://github.com/LegionIO/lex-cognitive-immune-memory |
21
+
22
+ ## File Structure
23
+
24
+ ```
25
+ lib/legion/extensions/cognitive_immune_memory/
26
+ cognitive_immune_memory.rb # Top-level require
27
+ version.rb # VERSION = '0.1.0'
28
+ client.rb # Client class
29
+ helpers/
30
+ constants.rb # Threat types, cell types, activation thresholds, response speeds, labels
31
+ memory_cell.rb # MemoryCell value object
32
+ immune_memory_engine.rb # Engine: cells, vaccination, encounters, decay, coverage
33
+ runners/
34
+ cognitive_immune_memory.rb # Runner module
35
+ ```
36
+
37
+ ## Key Constants
38
+
39
+ | Constant | Value | Meaning |
40
+ |---|---|---|
41
+ | `MAX_MEMORY_CELLS` | 500 | Memory cell cap |
42
+ | `T_CELL_ACTIVATION_THRESHOLD` | 0.5 | Strength threshold for T-cell activation |
43
+ | `B_CELL_ACTIVATION_THRESHOLD` | 0.4 | Strength threshold for B-cell activation |
44
+ | `T_CELL_BOOST` | 0.15 | Strength increase per encounter for T-cells |
45
+ | `B_CELL_BOOST` | 0.12 | Strength increase per encounter for B-cells |
46
+ | `PRIMARY_RESPONSE_SPEED` | 0.3 | Baseline response speed for primary encounter |
47
+ | `SECONDARY_RESPONSE_SPEED` | 0.8 | Elevated response speed for known threats |
48
+ | `VACCINATION_STRENGTH` | 0.5 | Starting strength for vaccinated cells |
49
+ | `THREAT_TYPES` | array | `[:manipulation, :deception, :coercion, :exploitation, :distraction, :flooding, :anchoring, :mirroring]` |
50
+ | `CELL_TYPES` | array | `[:t_cell, :b_cell, :memory_t, :memory_b, :natural_killer]` |
51
+ | `IMMUNITY_LABELS` | hash | `immune` (0.8+) through `naive` |
52
+ | `RESPONSE_SPEED_LABELS` | hash | `instant` (0.9+) through `slow` |
53
+ | `HEALTH_LABELS` | hash | `robust` through `depleted` |
54
+ | `MATURITY_LABELS` | hash | `veteran` through `naive` |
55
+
56
+ ## Helpers
57
+
58
+ ### `MemoryCell`
59
+
60
+ A trained immunological record for a specific threat type.
61
+
62
+ - `initialize(threat_type:, cell_type:, strength: 0.5, cell_id: nil)`
63
+ - `encounter!(boost)` — increases strength by cell-type boost; increments encounter_count; increases response_speed
64
+ - `decay!(rate)` — decreases strength, floor 0.0
65
+ - `active?` — strength >= cell-type activation threshold
66
+ - `veteran?` — encounter_count above veteran threshold
67
+ - `response_speed` — starts at PRIMARY_RESPONSE_SPEED; increases toward SECONDARY_RESPONSE_SPEED with encounters
68
+ - `to_h`
69
+
70
+ ### `ImmuneMemoryEngine`
71
+
72
+ - `create_memory_cell(threat_type:, cell_type: :memory_t, strength: 0.5)` — returns `{ created:, cell_id:, cell: }` or capacity error
73
+ - `vaccinate(threat_type:)` — creates a memory cell at `VACCINATION_STRENGTH` without a live encounter
74
+ - `encounter_threat(threat_type:)` — checks for existing memory cells matching threat_type; if found, triggers secondary response (boosts all matching cells, returns fast response); if not found, triggers primary response and creates new memory cell
75
+ - `decay_all!` — decrements all cell strength values
76
+ - `secondary_response_rate` — proportion of threats that have established memory cells
77
+ - `neutralization_rate` — proportion of active (above-threshold) cells
78
+ - `average_response_speed` — mean response_speed across all cells
79
+ - `threat_coverage` — hash of threat_type -> strongest cell strength
80
+ - `overall_health` — composite score from neutralization, coverage breadth, and mean strength
81
+ - `immune_status_report` — full stats
82
+
83
+ ## Runners
84
+
85
+ **Module**: `Legion::Extensions::CognitiveImmuneMemory::Runners::CognitiveImmuneMemory`
86
+
87
+ | Method | Key Args | Returns |
88
+ |---|---|---|
89
+ | `create_memory_cell` | `threat_type:`, `cell_type: :memory_t`, `strength: 0.5` | `{ success:, cell_id:, cell: }` |
90
+ | `vaccinate` | `threat_type:` | `{ success:, cell_id:, cell: }` |
91
+ | `encounter_threat` | `threat_type:` | `{ success:, response_type:, response_speed:, cells_activated: }` |
92
+ | `decay_all` | — | `{ success:, decayed: N }` |
93
+ | `immunity_for` | `threat_type:` | `{ success:, strength:, cells: }` |
94
+ | `active_cells` | `limit: 20` | `{ success:, cells: }` |
95
+ | `veteran_cells` | `limit: 10` | `{ success:, cells: }` |
96
+ | `threat_coverage` | — | `{ success:, coverage: { threat_type => strength } }` |
97
+ | `immune_status` | — | `{ success:, report: }` |
98
+
99
+ Private: `immune_memory_engine` — memoized `ImmuneMemoryEngine`. Logs via `log_debug` helper.
100
+
101
+ ## Integration Points
102
+
103
+ - **`lex-cognitive-immunology`**: `lex-cognitive-immune-memory` handles long-term recognition and learned threat response. `lex-cognitive-immunology` handles live threat detection, quarantine, and inflammatory response. The two complement each other: immunology for acute response, immune-memory for durable recognition.
104
+ - **`lex-cognitive-immune-response`**: Immune response generates antibodies; immune memory stores the learned patterns. After a threat encounter in `lex-cognitive-immune-response`, a corresponding vaccination in immune-memory can install durable recognition.
105
+ - **`lex-trust`**: Low secondary_response_rate (few threats in memory) corresponds to a naive agent with undeveloped trust discrimination. High memory coverage supports more nuanced trust evaluation.
106
+
107
+ ## Development Notes
108
+
109
+ - `encounter_threat` distinguishes primary vs. secondary response by whether any memory cell for the threat_type exists and is active. New memory cells are created on primary encounter regardless of capacity (up to MAX_MEMORY_CELLS).
110
+ - `vaccinate` creates a new cell at `VACCINATION_STRENGTH` each call — calling vaccinate multiple times for the same threat_type creates multiple cells.
111
+ - `decay_all!` decays all cells unconditionally. Cells that decay to 0.0 remain in the store; callers should check `active?` and prune manually.
112
+ - In-memory only.
113
+
114
+ ---
115
+
116
+ **Maintained By**: Matthew Iverson (@Esity)
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'rspec', '~> 3.13'
8
+ gem 'rubocop', '~> 1.75'
9
+ gem 'rubocop-rspec'
10
+
11
+ gem 'legion-gaia', path: '../../legion-gaia'
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # lex-cognitive-immune-memory
2
+
3
+ Long-term cognitive threat recognition engine for brain-modeled agentic AI in the LegionIO ecosystem.
4
+
5
+ ## What It Does
6
+
7
+ Models immunological memory for cognitive security. Memory cells are trained records of past threat encounters — manipulation, deception, coercion, exploitation, and related adversarial patterns. Encountering a known threat triggers a fast secondary response (response speed 0.8); encountering an unknown threat triggers a slower primary response (0.3) and installs a new memory cell. Vaccination artificially creates memory cells without a live encounter. Memory cells decay over time without reinforcement.
8
+
9
+ ## Usage
10
+
11
+ ```ruby
12
+ require 'legion/extensions/cognitive_immune_memory'
13
+
14
+ client = Legion::Extensions::CognitiveImmuneMemory::Client.new
15
+
16
+ # Vaccinate against a known threat pattern
17
+ client.vaccinate(threat_type: :manipulation)
18
+ # => { success: true, cell_id: "...", cell: { strength: 0.5, response_speed: 0.3, ... } }
19
+
20
+ # Encounter a threat — secondary response since vaccination installed a cell
21
+ client.encounter_threat(threat_type: :manipulation)
22
+ # => { success: true, response_type: :secondary, response_speed: 0.65, cells_activated: 1 }
23
+
24
+ # Encounter an unknown threat — primary response, installs new memory cell
25
+ client.encounter_threat(threat_type: :anchoring)
26
+ # => { success: true, response_type: :primary, response_speed: 0.3, cells_activated: 0 }
27
+
28
+ # Check coverage across all known threats
29
+ client.threat_coverage
30
+ # => { success: true, coverage: { manipulation: 0.62, anchoring: 0.5, ... } }
31
+
32
+ # Overall immune health
33
+ client.immune_status
34
+ # => { success: true, report: { cell_count: 2, health: 0.55, ... } }
35
+ ```
36
+
37
+ ## Development
38
+
39
+ ```bash
40
+ bundle install
41
+ bundle exec rspec
42
+ bundle exec rubocop
43
+ ```
44
+
45
+ ## License
46
+
47
+ MIT
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/cognitive_immune_memory/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-cognitive-immune-memory'
7
+ spec.version = Legion::Extensions::CognitiveImmuneMemory::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+ spec.summary = 'Long-term adaptive immune memory for LegionIO agents'
11
+ spec.description = 'T-cell/B-cell persistent threat recognition with secondary response ' \
12
+ 'amplification for the LegionIO cognitive architecture'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-cognitive-immune-memory'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata = {
18
+ 'homepage_uri' => spec.homepage,
19
+ 'source_code_uri' => spec.homepage,
20
+ 'documentation_uri' => "#{spec.homepage}/blob/origin/README.md",
21
+ 'changelog_uri' => "#{spec.homepage}/blob/origin/CHANGELOG.md",
22
+ 'bug_tracker_uri' => "#{spec.homepage}/issues",
23
+ 'rubygems_mfa_required' => 'true'
24
+ }
25
+
26
+ spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
27
+ spec.require_paths = ['lib']
28
+ spec.add_development_dependency 'legion-gaia'
29
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveImmuneMemory
6
+ class Client
7
+ include Runners::CognitiveImmuneMemory
8
+
9
+ def initialize
10
+ @default_engine = Helpers::ImmuneMemoryEngine.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveImmuneMemory
6
+ module Helpers
7
+ module Constants
8
+ MAX_MEMORY_CELLS = 500
9
+ MAX_ENCOUNTERS = 1000
10
+ MAX_ANTIBODY_LIBRARY = 300
11
+
12
+ # T-cell parameters (cell-mediated: direct threat neutralization)
13
+ T_CELL_ACTIVATION_THRESHOLD = 0.4
14
+ T_CELL_BOOST = 0.12
15
+ T_CELL_DECAY = 0.01
16
+ T_CELL_LIFESPAN = 100 # decay cycles before expiry check
17
+
18
+ # B-cell parameters (humoral: antibody production)
19
+ B_CELL_ACTIVATION_THRESHOLD = 0.3
20
+ B_CELL_BOOST = 0.1
21
+ B_CELL_DECAY = 0.008
22
+ B_CELL_ANTIBODY_PRODUCTION = 0.15
23
+
24
+ # Secondary response amplification
25
+ PRIMARY_RESPONSE_SPEED = 1.0
26
+ SECONDARY_RESPONSE_SPEED = 3.0
27
+ MEMORY_RECOGNITION_THRESHOLD = 0.6
28
+
29
+ # Vaccination (pre-exposure)
30
+ VACCINATION_STRENGTH = 0.5
31
+
32
+ THREAT_TYPES = %i[
33
+ prompt_injection data_poisoning social_engineering
34
+ resource_exhaustion privilege_escalation information_leak
35
+ logic_manipulation identity_spoofing
36
+ ].freeze
37
+
38
+ CELL_TYPES = %i[t_helper t_killer b_memory b_plasma].freeze
39
+
40
+ IMMUNITY_LABELS = {
41
+ (0.8..) => :immune,
42
+ (0.6...0.8) => :resistant,
43
+ (0.4...0.6) => :partial,
44
+ (0.2...0.4) => :vulnerable,
45
+ (..0.2) => :naive
46
+ }.freeze
47
+
48
+ RESPONSE_SPEED_LABELS = {
49
+ (2.5..) => :lightning,
50
+ (1.8...2.5) => :rapid,
51
+ (1.0...1.8) => :normal,
52
+ (0.5...1.0) => :slow,
53
+ (..0.5) => :impaired
54
+ }.freeze
55
+
56
+ HEALTH_LABELS = {
57
+ (0.8..) => :robust,
58
+ (0.6...0.8) => :healthy,
59
+ (0.4...0.6) => :adequate,
60
+ (0.2...0.4) => :weakened,
61
+ (..0.2) => :compromised
62
+ }.freeze
63
+
64
+ MATURITY_LABELS = {
65
+ (0.8..) => :veteran,
66
+ (0.6...0.8) => :experienced,
67
+ (0.4...0.6) => :developing,
68
+ (0.2...0.4) => :immature,
69
+ (..0.2) => :naive
70
+ }.freeze
71
+
72
+ def self.label_for(labels, value)
73
+ labels.each { |range, label| return label if range.cover?(value) }
74
+ :unknown
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module CognitiveImmuneMemory
8
+ module Helpers
9
+ class Encounter
10
+ include Constants
11
+
12
+ attr_reader :id, :threat_type, :threat_signature, :severity,
13
+ :response_type, :response_speed, :outcome, :created_at
14
+
15
+ def initialize(threat_type:, threat_signature:, severity: 0.5, response_type: :primary,
16
+ response_speed: PRIMARY_RESPONSE_SPEED, outcome: :neutralized)
17
+ @id = SecureRandom.uuid
18
+ @threat_type = threat_type.to_sym
19
+ @threat_signature = threat_signature.to_s
20
+ @severity = severity.to_f.clamp(0.0, 1.0)
21
+ @response_type = response_type.to_sym
22
+ @response_speed = response_speed.to_f.round(10)
23
+ @outcome = outcome.to_sym
24
+ @created_at = Time.now
25
+ end
26
+
27
+ def secondary? = @response_type == :secondary
28
+ def primary? = @response_type == :primary
29
+ def neutralized? = @outcome == :neutralized
30
+ def evaded? = @outcome == :evaded
31
+ def critical? = @severity >= 0.8
32
+
33
+ def to_h
34
+ {
35
+ id: @id,
36
+ threat_type: @threat_type,
37
+ threat_signature: @threat_signature,
38
+ severity: @severity,
39
+ response_type: @response_type,
40
+ response_speed: @response_speed,
41
+ outcome: @outcome,
42
+ critical: critical?,
43
+ created_at: @created_at.iso8601
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module CognitiveImmuneMemory
6
+ module Helpers
7
+ class ImmuneMemoryEngine
8
+ include Constants
9
+
10
+ def initialize
11
+ @memory_cells = {}
12
+ @encounters = []
13
+ end
14
+
15
+ def create_memory_cell(threat_type:, signature:, cell_type: :b_memory, strength: VACCINATION_STRENGTH)
16
+ prune_expired
17
+ cell = MemoryCell.new(threat_type: threat_type, signature: signature,
18
+ cell_type: cell_type, strength: strength)
19
+ @memory_cells[cell.id] = cell
20
+ cell
21
+ end
22
+
23
+ def vaccinate(threat_type:, signature:, strength: VACCINATION_STRENGTH)
24
+ existing = find_by_signature(signature)
25
+ return existing.activate! if existing
26
+
27
+ create_memory_cell(threat_type: threat_type, signature: signature,
28
+ cell_type: :b_memory, strength: strength)
29
+ end
30
+
31
+ def encounter_threat(threat_type:, threat_signature:, severity: 0.5)
32
+ matching = find_by_signature(threat_signature)
33
+ response_type = matching ? :secondary : :primary
34
+ speed = matching ? matching.response_speed : PRIMARY_RESPONSE_SPEED
35
+ outcome = determine_outcome(matching, severity)
36
+
37
+ matching&.activate!
38
+
39
+ record = Encounter.new(
40
+ threat_type: threat_type, threat_signature: threat_signature,
41
+ severity: severity, response_type: response_type,
42
+ response_speed: speed, outcome: outcome
43
+ )
44
+ @encounters << record
45
+ prune_encounters
46
+
47
+ unless matching
48
+ create_memory_cell(threat_type: threat_type, signature: threat_signature,
49
+ cell_type: :b_memory, strength: B_CELL_ACTIVATION_THRESHOLD)
50
+ end
51
+
52
+ record
53
+ end
54
+
55
+ def decay_all!
56
+ @memory_cells.each_value(&:decay!)
57
+ prune_expired
58
+ { cells_remaining: @memory_cells.size }
59
+ end
60
+
61
+ def find_by_signature(signature)
62
+ @memory_cells.values.find { |c| c.signature == signature.to_s && !c.expired? }
63
+ end
64
+
65
+ def cells_for_threat(threat_type:)
66
+ @memory_cells.values.select { |c| c.threat_type == threat_type.to_sym && !c.expired? }
67
+ end
68
+
69
+ def immunity_for(threat_type:)
70
+ cells = cells_for_threat(threat_type: threat_type)
71
+ return 0.0 if cells.empty?
72
+
73
+ cells.max_by(&:strength).strength
74
+ end
75
+
76
+ def active_cells = @memory_cells.values.reject(&:expired?)
77
+ def t_cells = @memory_cells.values.select(&:t_cell?)
78
+ def b_cells = @memory_cells.values.select(&:b_cell?)
79
+ def veteran_cells = @memory_cells.values.select(&:veteran?)
80
+ def naive_cells = @memory_cells.values.select(&:naive?)
81
+
82
+ def encounters_for(threat_type:)
83
+ @encounters.select { |e| e.threat_type == threat_type.to_sym }
84
+ end
85
+
86
+ def secondary_response_rate
87
+ return 0.0 if @encounters.empty?
88
+
89
+ secondary = @encounters.count(&:secondary?)
90
+ (secondary.to_f / @encounters.size).round(10)
91
+ end
92
+
93
+ def neutralization_rate
94
+ return 0.0 if @encounters.empty?
95
+
96
+ neutralized = @encounters.count(&:neutralized?)
97
+ (neutralized.to_f / @encounters.size).round(10)
98
+ end
99
+
100
+ def average_response_speed
101
+ return PRIMARY_RESPONSE_SPEED if @encounters.empty?
102
+
103
+ (@encounters.sum(&:response_speed) / @encounters.size).round(10)
104
+ end
105
+
106
+ def threat_coverage
107
+ known_types = @memory_cells.values.map(&:threat_type).uniq
108
+ (known_types.size.to_f / THREAT_TYPES.size).clamp(0.0, 1.0).round(10)
109
+ end
110
+
111
+ def overall_health
112
+ return 0.0 if @memory_cells.empty?
113
+
114
+ avg_strength = (@memory_cells.values.sum(&:strength) / @memory_cells.size).round(10)
115
+ coverage_factor = threat_coverage
116
+ ((avg_strength * 0.6) + (coverage_factor * 0.4)).clamp(0.0, 1.0).round(10)
117
+ end
118
+
119
+ def health_label = Constants.label_for(HEALTH_LABELS, overall_health)
120
+
121
+ def immune_report
122
+ {
123
+ total_cells: @memory_cells.size,
124
+ active_cells: active_cells.size,
125
+ t_cells: t_cells.size,
126
+ b_cells: b_cells.size,
127
+ veteran_cells: veteran_cells.size,
128
+ total_encounters: @encounters.size,
129
+ secondary_response_rate: secondary_response_rate,
130
+ neutralization_rate: neutralization_rate,
131
+ average_response_speed: average_response_speed,
132
+ threat_coverage: threat_coverage,
133
+ overall_health: overall_health,
134
+ health_label: health_label
135
+ }
136
+ end
137
+
138
+ def to_h
139
+ {
140
+ total_cells: @memory_cells.size,
141
+ active: active_cells.size,
142
+ encounters: @encounters.size,
143
+ health: overall_health,
144
+ threat_coverage: threat_coverage
145
+ }
146
+ end
147
+
148
+ private
149
+
150
+ def determine_outcome(matching_cell, severity)
151
+ return :neutralized if matching_cell && matching_cell.strength >= severity
152
+
153
+ severity < 0.5 ? :neutralized : :evaded
154
+ end
155
+
156
+ def prune_expired
157
+ @memory_cells.reject! { |_, c| c.expired? } if @memory_cells.size >= MAX_MEMORY_CELLS
158
+ end
159
+
160
+ def prune_encounters
161
+ @encounters.shift while @encounters.size > MAX_ENCOUNTERS
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end