lex-subliminal 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 +7 -0
- data/.github/workflows/ci.yml +16 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +37 -0
- data/CLAUDE.md +120 -0
- data/Gemfile +11 -0
- data/README.md +66 -0
- data/lex-subliminal.gemspec +29 -0
- data/lib/legion/extensions/subliminal/client.rb +15 -0
- data/lib/legion/extensions/subliminal/helpers/constants.rb +70 -0
- data/lib/legion/extensions/subliminal/helpers/influence_event.rb +40 -0
- data/lib/legion/extensions/subliminal/helpers/subliminal_engine.rb +145 -0
- data/lib/legion/extensions/subliminal/helpers/subliminal_trace.rb +97 -0
- data/lib/legion/extensions/subliminal/runners/subliminal.rb +67 -0
- data/lib/legion/extensions/subliminal/version.rb +9 -0
- data/lib/legion/extensions/subliminal.rb +16 -0
- data/spec/legion/extensions/subliminal/client_spec.rb +21 -0
- data/spec/legion/extensions/subliminal/helpers/influence_event_spec.rb +58 -0
- data/spec/legion/extensions/subliminal/helpers/subliminal_engine_spec.rb +161 -0
- data/spec/legion/extensions/subliminal/helpers/subliminal_trace_spec.rb +168 -0
- data/spec/legion/extensions/subliminal/runners_spec.rb +78 -0
- data/spec/legion/extensions/subliminal_spec.rb +7 -0
- data/spec/spec_helper.rb +28 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 942de14ddf12c8a592ed87370978cd88ed2e6e239c67cdcce276b80d59aaa76f
|
|
4
|
+
data.tar.gz: fead526f92b11f15af80d1d21ce18c6065c2f8c319389e3a684f9e13a2a79363
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ed24c0d2f0e071cfa8e5f27d00f9a4bc0de45fc652309b319c3b9c0dcc11022e30e2689e097a9ccd1129773ed9affceba65a1d3b398962217d13c27a957cbd6c
|
|
7
|
+
data.tar.gz: 948dbd1b4733e98618010563ca944670348dbb300d86a5a24aa60fa5d26c031aad2ed05520795fb6a7694e047b024186ec92eebf0f60780b3c328bdbc5bc76a7
|
|
@@ -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
data/.rspec
ADDED
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,120 @@
|
|
|
1
|
+
# lex-subliminal
|
|
2
|
+
|
|
3
|
+
**Level 3 Leaf Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
|
|
5
|
+
- **Gem**: `lex-subliminal`
|
|
6
|
+
- **Version**: `0.1.0`
|
|
7
|
+
- **Namespace**: `Legion::Extensions::Subliminal`
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Models subliminal (below-threshold) influences on cognition. Subliminal traces carry an activation level that is structurally prevented from reaching the conscious threshold — they cannot be consciously attended to, but they exert a measurable influence on named cognitive targets (attention, memory, decision, emotion, etc.). Traces decay over time; those that fall to near-zero are extinct. Useful for modeling background priming, emotional contagion, implicit learning, and attentional drift.
|
|
12
|
+
|
|
13
|
+
## Gem Info
|
|
14
|
+
|
|
15
|
+
- **Gem name**: `lex-subliminal`
|
|
16
|
+
- **License**: MIT
|
|
17
|
+
- **Ruby**: >= 3.4
|
|
18
|
+
- **No runtime dependencies** beyond the Legion framework
|
|
19
|
+
|
|
20
|
+
## File Structure
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
lib/legion/extensions/subliminal/
|
|
24
|
+
version.rb # VERSION = '0.1.0'
|
|
25
|
+
helpers/
|
|
26
|
+
constants.rb # thresholds, decay rates, influence strength, max caps, type/target lists, labels
|
|
27
|
+
subliminal_trace.rb # SubliminalTrace class — single below-threshold activation trace
|
|
28
|
+
subliminal_engine.rb # SubliminalEngine class — collection with bulk influence processing
|
|
29
|
+
runners/
|
|
30
|
+
subliminal.rb # Runners::Subliminal module — all public runner methods
|
|
31
|
+
client.rb # Client class including Runners::Subliminal
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Key Constants
|
|
35
|
+
|
|
36
|
+
| Constant | Value | Purpose |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `CONSCIOUS_THRESHOLD` | 0.4 | The threshold subliminal traces are prevented from reaching |
|
|
39
|
+
| `SUBLIMINAL_CEILING` | 0.39 | Hard cap on trace activation (just below CONSCIOUS_THRESHOLD) |
|
|
40
|
+
| `SUBLIMINAL_FLOOR` | 0.02 | Minimum activation before approaching extinction |
|
|
41
|
+
| `EXTINCTION_THRESHOLD` | 0.01 | At or below this, trace is considered extinct |
|
|
42
|
+
| `DEFAULT_ACTIVATION` | 0.2 | Starting activation for new traces |
|
|
43
|
+
| `ACTIVATION_BOOST` | 0.08 | Activation increase per `boost!` call |
|
|
44
|
+
| `ACTIVATION_DECAY` | 0.015 | Per-tick activation decrease |
|
|
45
|
+
| `INFLUENCE_STRENGTH` | 0.05 | Magnitude multiplier: `activation * INFLUENCE_STRENGTH` |
|
|
46
|
+
| `MAX_INFLUENCE_PER_DOMAIN` | 0.3 | Maximum total influence on any single domain |
|
|
47
|
+
| `MAX_TOTAL_INFLUENCE` | 0.5 | Maximum combined influence across all targets |
|
|
48
|
+
| `TRACE_TYPES` | 8 symbols | `:priming`, `:emotional`, `:procedural`, `:semantic`, `:inhibitory`, `:motivational`, `:contextual`, `:evaluative` |
|
|
49
|
+
| `INFLUENCE_TARGETS` | 8 symbols | `:attention`, `:memory`, `:decision`, `:emotion`, `:action`, `:perception`, `:language`, `:reasoning` |
|
|
50
|
+
|
|
51
|
+
## Helpers
|
|
52
|
+
|
|
53
|
+
### `Helpers::SubliminalTrace`
|
|
54
|
+
|
|
55
|
+
Single below-threshold activation trace.
|
|
56
|
+
|
|
57
|
+
- `initialize(id:, content:, trace_type:, domain: :general, target: :attention, activation: DEFAULT_ACTIVATION)` — clamps activation to SUBLIMINAL_CEILING on init
|
|
58
|
+
- `boost!(amount = ACTIVATION_BOOST)` — increments activation; hard-clamps to SUBLIMINAL_CEILING (never reaches CONSCIOUS_THRESHOLD)
|
|
59
|
+
- `decay!` — decrements activation by ACTIVATION_DECAY; floors at 0.0
|
|
60
|
+
- `exert_influence!` — decrements activation slightly, returns `influence_magnitude`
|
|
61
|
+
- `influence_magnitude` — `activation * INFLUENCE_STRENGTH`
|
|
62
|
+
- `near_threshold?` — activation between 0.3 and 0.39
|
|
63
|
+
- `active?` — activation > EXTINCTION_THRESHOLD
|
|
64
|
+
- `extinct?` — activation <= EXTINCTION_THRESHOLD
|
|
65
|
+
- `potent?` — activation >= 0.2
|
|
66
|
+
- `breached_threshold?` — activation >= 0.4 (anomaly: should never occur due to ceiling, logged if detected)
|
|
67
|
+
- `activation_label` — `:extinct`, `:faint`, `:weak`, `:moderate`, `:strong` based on activation
|
|
68
|
+
|
|
69
|
+
### `Helpers::SubliminalEngine`
|
|
70
|
+
|
|
71
|
+
Collection of SubliminalTrace objects with bulk influence processing.
|
|
72
|
+
|
|
73
|
+
- `initialize` — traces hash, keyed by trace id
|
|
74
|
+
- `create_trace(content:, trace_type:, domain: :general, target: :attention, activation: DEFAULT_ACTIVATION)` — rejects invalid types/targets; appends trace
|
|
75
|
+
- `boost_trace(trace_id, amount: ACTIVATION_BOOST)` — calls `trace.boost!`
|
|
76
|
+
- `process_influences!` — all active traces call `exert_influence!`; generates InfluenceEvent objects per trace; returns events array
|
|
77
|
+
- `decay_all!` — decays all traces; removes extinct ones
|
|
78
|
+
- `active_traces` — all with `active? == true`
|
|
79
|
+
- `near_threshold_traces` — all with `near_threshold? == true`
|
|
80
|
+
- `potent_traces` — all with `potent? == true`
|
|
81
|
+
- `traces_by_domain(domain)` — filter by domain
|
|
82
|
+
- `traces_by_type(trace_type)` — filter by trace_type
|
|
83
|
+
- `traces_by_target(target)` — filter by influence target
|
|
84
|
+
- `influence_on(target:)` — sum of influence_magnitude across all active traces targeting `target`; capped at MAX_INFLUENCE_PER_DOMAIN
|
|
85
|
+
- `domain_saturation(domain)` — total influence magnitude across all traces in domain
|
|
86
|
+
- `overall_subliminal_load` — sum of all active trace activations; capped at MAX_TOTAL_INFLUENCE
|
|
87
|
+
- `strongest_traces(limit: 5)` — sorted by activation descending
|
|
88
|
+
- `breached_traces` — any traces with `breached_threshold? == true` (anomaly detection)
|
|
89
|
+
- `subliminal_report` — summary: trace counts, overall load, influence per target, near-threshold count
|
|
90
|
+
|
|
91
|
+
## Runners
|
|
92
|
+
|
|
93
|
+
All runners are in `Runners::Subliminal`. The `Client` includes this module. Note: the runner uses `@default_engine` internally (not `@engine`).
|
|
94
|
+
|
|
95
|
+
| Runner | Parameters | Returns |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `create_subliminal_trace` | `content:, trace_type:, domain: :general, target: :attention, activation: DEFAULT_ACTIVATION` | `{ success:, trace_id:, activation:, trace_type: }` |
|
|
98
|
+
| `boost_trace` | `trace_id:, amount: ACTIVATION_BOOST` | `{ success:, trace_id:, activation: }` |
|
|
99
|
+
| `process_influences` | (none) | `{ success:, events:, count: }` — runs `process_influences!` |
|
|
100
|
+
| `decay_all` | (none) | `{ success:, active_count:, removed_count: }` |
|
|
101
|
+
| `active_traces` | (none) | `{ success:, traces:, count: }` |
|
|
102
|
+
| `near_threshold` | (none) | `{ success:, traces:, count: }` — traces approaching conscious threshold |
|
|
103
|
+
| `influence_on` | `target:` | `{ success:, target:, influence: }` |
|
|
104
|
+
| `subliminal_status` | (none) | Full `SubliminalEngine#subliminal_report` hash |
|
|
105
|
+
|
|
106
|
+
## Integration Points
|
|
107
|
+
|
|
108
|
+
- **lex-tick / lex-cortex**: `process_influences` wired as a tick phase handler to apply subliminal influence each cycle; influence events can be forwarded to target extensions (e.g., influence on `:attention` biases the sensory gating thresholds)
|
|
109
|
+
- **lex-sensory-gating**: subliminal influence on `:perception` can modulate gate thresholds without the agent's explicit awareness
|
|
110
|
+
- **lex-emotion**: `:emotional` trace type targeting `:emotion` models emotional contagion and implicit mood induction
|
|
111
|
+
- **lex-dream**: dream consolidation may create subliminal traces from unresolved material; `:contextual` traces targeting `:decision` model incubation effects
|
|
112
|
+
- **lex-semantic-priming**: near-threshold traces can trigger priming events when their target aligns with active concept nodes
|
|
113
|
+
|
|
114
|
+
## Development Notes
|
|
115
|
+
|
|
116
|
+
- `SUBLIMINAL_CEILING = 0.39` is a hard cap enforced in both `initialize` and `boost!` — traces structurally cannot reach CONSCIOUS_THRESHOLD, making the subliminal/conscious boundary a guaranteed architectural constraint
|
|
117
|
+
- `breached_threshold?` >= 0.4 should never trigger in normal operation; it is an anomaly detection flag
|
|
118
|
+
- `process_influences!` slightly decrements activation on each call — sustained influence gradually weakens the trace
|
|
119
|
+
- `@default_engine` naming (vs `@engine`) is specific to this runner module; specs and injection must reference `@default_engine`
|
|
120
|
+
- InfluenceEvent objects from `process_influences!` are plain structs (trace_id, target, magnitude, domain) — callers can route them to downstream handlers by target
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# lex-subliminal
|
|
2
|
+
|
|
3
|
+
Subliminal influence modeling for LegionIO cognitive agents. Traces operate below the conscious threshold but exert measurable influence on attention, memory, decision-making, and emotion.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
`lex-subliminal` maintains a set of activation traces that are structurally capped below the conscious threshold (0.39 max, threshold at 0.4). These traces cannot be consciously attended to but exert influence on named cognitive targets each tick. Traces decay over time; those that reach near-zero are extinct.
|
|
8
|
+
|
|
9
|
+
- **Trace types**: `:priming`, `:emotional`, `:procedural`, `:semantic`, `:inhibitory`, `:motivational`, `:contextual`, `:evaluative`
|
|
10
|
+
- **Influence targets**: `:attention`, `:memory`, `:decision`, `:emotion`, `:action`, `:perception`, `:language`, `:reasoning`
|
|
11
|
+
- **Activation ceiling**: 0.39 — structurally enforced; traces can never breach conscious threshold
|
|
12
|
+
- **Influence magnitude**: `activation * 0.05` per trace per tick
|
|
13
|
+
- **Potent traces**: activation >= 0.2; near-threshold traces: activation 0.3–0.39
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'legion/extensions/subliminal'
|
|
19
|
+
|
|
20
|
+
client = Legion::Extensions::Subliminal::Client.new
|
|
21
|
+
|
|
22
|
+
# Create a subliminal trace
|
|
23
|
+
result = client.create_subliminal_trace(
|
|
24
|
+
content: 'unresolved conflict',
|
|
25
|
+
trace_type: :emotional,
|
|
26
|
+
domain: :relationships,
|
|
27
|
+
target: :decision,
|
|
28
|
+
activation: 0.25
|
|
29
|
+
)
|
|
30
|
+
trace_id = result[:trace_id]
|
|
31
|
+
|
|
32
|
+
# Boost a trace (activation can never exceed 0.39)
|
|
33
|
+
client.boost_trace(trace_id: trace_id, amount: 0.08)
|
|
34
|
+
|
|
35
|
+
# Process influences (called each tick)
|
|
36
|
+
client.process_influences
|
|
37
|
+
# => { events: [{ target: :decision, magnitude: 0.017, ... }], count: 1 }
|
|
38
|
+
|
|
39
|
+
# Check influence on a specific target
|
|
40
|
+
client.influence_on(target: :decision)
|
|
41
|
+
# => { target: :decision, influence: 0.017 }
|
|
42
|
+
|
|
43
|
+
# Near-threshold traces (approaching conscious awareness)
|
|
44
|
+
client.near_threshold
|
|
45
|
+
# => { traces: [...], count: 0 }
|
|
46
|
+
|
|
47
|
+
# Decay all traces
|
|
48
|
+
client.decay_all
|
|
49
|
+
# => { active_count: 1, removed_count: 0 }
|
|
50
|
+
|
|
51
|
+
# Full status report
|
|
52
|
+
client.subliminal_status
|
|
53
|
+
# => { trace_count:, active_count:, overall_load:, influence_per_target: { ... }, ... }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bundle install
|
|
60
|
+
bundle exec rspec
|
|
61
|
+
bundle exec rubocop
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/subliminal/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-subliminal'
|
|
7
|
+
spec.version = Legion::Extensions::Subliminal::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
spec.summary = 'Subliminal activation and below-threshold influence for LegionIO agents'
|
|
11
|
+
spec.description = 'Models below-threshold cognitive traces that influence behavior ' \
|
|
12
|
+
'without reaching conscious awareness in the LegionIO architecture'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-subliminal'
|
|
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,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Subliminal
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_TRACES = 500
|
|
9
|
+
MAX_INFLUENCES = 1000
|
|
10
|
+
|
|
11
|
+
# Threshold boundaries
|
|
12
|
+
CONSCIOUS_THRESHOLD = 0.4
|
|
13
|
+
SUBLIMINAL_CEILING = 0.39
|
|
14
|
+
SUBLIMINAL_FLOOR = 0.02
|
|
15
|
+
EXTINCTION_THRESHOLD = 0.01
|
|
16
|
+
|
|
17
|
+
# Dynamics
|
|
18
|
+
DEFAULT_ACTIVATION = 0.2
|
|
19
|
+
ACTIVATION_BOOST = 0.08
|
|
20
|
+
ACTIVATION_DECAY = 0.015
|
|
21
|
+
INFLUENCE_STRENGTH = 0.05
|
|
22
|
+
ACCUMULATION_RATE = 0.03
|
|
23
|
+
|
|
24
|
+
# Influence caps
|
|
25
|
+
MAX_INFLUENCE_PER_DOMAIN = 0.3
|
|
26
|
+
MAX_TOTAL_INFLUENCE = 0.5
|
|
27
|
+
|
|
28
|
+
TRACE_TYPES = %i[
|
|
29
|
+
perceptual emotional associative procedural
|
|
30
|
+
semantic motivational aversive appetitive
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
INFLUENCE_TARGETS = %i[
|
|
34
|
+
attention emotion decision memory
|
|
35
|
+
preference avoidance approach valence
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
ACTIVATION_LABELS = {
|
|
39
|
+
(0.3...0.4) => :near_threshold,
|
|
40
|
+
(0.2...0.3) => :moderate,
|
|
41
|
+
(0.1...0.2) => :faint,
|
|
42
|
+
(0.02...0.1) => :trace,
|
|
43
|
+
(..0.02) => :extinct
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
INFLUENCE_LABELS = {
|
|
47
|
+
(0.2..) => :strong,
|
|
48
|
+
(0.1...0.2) => :moderate,
|
|
49
|
+
(0.05...0.1) => :subtle,
|
|
50
|
+
(0.01...0.05) => :minimal,
|
|
51
|
+
(..0.01) => :none
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
SATURATION_LABELS = {
|
|
55
|
+
(0.8..) => :saturated,
|
|
56
|
+
(0.6...0.8) => :heavy,
|
|
57
|
+
(0.4...0.6) => :moderate,
|
|
58
|
+
(0.2...0.4) => :light,
|
|
59
|
+
(..0.2) => :clear
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
62
|
+
def self.label_for(labels, value)
|
|
63
|
+
labels.each { |range, label| return label if range.cover?(value) }
|
|
64
|
+
:unknown
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Subliminal
|
|
8
|
+
module Helpers
|
|
9
|
+
class InfluenceEvent
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :trace_id, :target, :magnitude, :domain, :created_at
|
|
13
|
+
|
|
14
|
+
def initialize(trace_id:, target:, magnitude:, domain: :general)
|
|
15
|
+
@id = SecureRandom.uuid
|
|
16
|
+
@trace_id = trace_id
|
|
17
|
+
@target = target.to_sym
|
|
18
|
+
@magnitude = magnitude.to_f.clamp(0.0, MAX_INFLUENCE_PER_DOMAIN).round(10)
|
|
19
|
+
@domain = domain.to_sym
|
|
20
|
+
@created_at = Time.now
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def significant? = @magnitude >= 0.05
|
|
24
|
+
def subtle? = @magnitude < 0.05 && @magnitude > 0.0
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
{
|
|
28
|
+
id: @id,
|
|
29
|
+
trace_id: @trace_id,
|
|
30
|
+
target: @target,
|
|
31
|
+
magnitude: @magnitude,
|
|
32
|
+
domain: @domain,
|
|
33
|
+
created_at: @created_at.iso8601
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Subliminal
|
|
6
|
+
module Helpers
|
|
7
|
+
class SubliminalEngine
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@traces = {}
|
|
12
|
+
@influences = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create_trace(content:, trace_type: :associative, domain: :general, activation: DEFAULT_ACTIVATION,
|
|
16
|
+
influence_target: :attention)
|
|
17
|
+
prune_extinct
|
|
18
|
+
trace = SubliminalTrace.new(content: content, trace_type: trace_type, domain: domain,
|
|
19
|
+
activation: activation, influence_target: influence_target)
|
|
20
|
+
@traces[trace.id] = trace
|
|
21
|
+
trace
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def boost_trace(trace_id:, amount: ACTIVATION_BOOST)
|
|
25
|
+
trace = @traces[trace_id]
|
|
26
|
+
return nil unless trace
|
|
27
|
+
|
|
28
|
+
trace.boost!(amount: amount)
|
|
29
|
+
trace
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def process_influences!
|
|
33
|
+
active = active_traces
|
|
34
|
+
events = []
|
|
35
|
+
active.each do |trace|
|
|
36
|
+
mag = trace.exert_influence!
|
|
37
|
+
next if mag <= 0.0
|
|
38
|
+
|
|
39
|
+
event = InfluenceEvent.new(trace_id: trace.id, target: trace.influence_target,
|
|
40
|
+
magnitude: mag, domain: trace.domain)
|
|
41
|
+
events << event
|
|
42
|
+
@influences << event
|
|
43
|
+
end
|
|
44
|
+
prune_influences
|
|
45
|
+
{ influences_generated: events.size, events: events.map(&:to_h) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def decay_all!
|
|
49
|
+
@traces.each_value(&:decay!)
|
|
50
|
+
prune_extinct
|
|
51
|
+
{ traces_remaining: @traces.size }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def active_traces = @traces.values.select(&:active?)
|
|
55
|
+
def near_threshold_traces = @traces.values.select(&:near_threshold?)
|
|
56
|
+
def potent_traces = @traces.values.select(&:potent?)
|
|
57
|
+
def extinct_traces = @traces.values.select(&:extinct?)
|
|
58
|
+
|
|
59
|
+
def traces_by_domain(domain:)
|
|
60
|
+
@traces.values.select { |t| t.domain == domain.to_sym }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def traces_by_type(trace_type:)
|
|
64
|
+
@traces.values.select { |t| t.trace_type == trace_type.to_sym }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def traces_by_target(target:)
|
|
68
|
+
@traces.values.select { |t| t.influence_target == target.to_sym }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def influence_on(target:)
|
|
72
|
+
recent = @influences.last(100).select { |e| e.target == target.to_sym }
|
|
73
|
+
return 0.0 if recent.empty?
|
|
74
|
+
|
|
75
|
+
recent.sum(&:magnitude).clamp(0.0, MAX_TOTAL_INFLUENCE).round(10)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def domain_saturation(domain:)
|
|
79
|
+
domain_traces = traces_by_domain(domain: domain)
|
|
80
|
+
return 0.0 if domain_traces.empty?
|
|
81
|
+
|
|
82
|
+
total_activation = domain_traces.sum(&:activation)
|
|
83
|
+
(total_activation / (domain_traces.size * SUBLIMINAL_CEILING)).clamp(0.0, 1.0).round(10)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def overall_subliminal_load
|
|
87
|
+
return 0.0 if @traces.empty?
|
|
88
|
+
|
|
89
|
+
active = active_traces
|
|
90
|
+
return 0.0 if active.empty?
|
|
91
|
+
|
|
92
|
+
(active.sum(&:activation) / (active.size * SUBLIMINAL_CEILING)).clamp(0.0, 1.0).round(10)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def saturation_label = Constants.label_for(SATURATION_LABELS, overall_subliminal_load)
|
|
96
|
+
|
|
97
|
+
def strongest_traces(limit: 5) = @traces.values.sort_by { |t| -t.activation }.first(limit)
|
|
98
|
+
|
|
99
|
+
def breached_traces
|
|
100
|
+
@traces.values.select(&:breached_threshold?)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def subliminal_report
|
|
104
|
+
{
|
|
105
|
+
total_traces: @traces.size,
|
|
106
|
+
active_count: active_traces.size,
|
|
107
|
+
near_threshold: near_threshold_traces.size,
|
|
108
|
+
potent_count: potent_traces.size,
|
|
109
|
+
total_influences: @influences.size,
|
|
110
|
+
subliminal_load: overall_subliminal_load,
|
|
111
|
+
saturation_label: saturation_label,
|
|
112
|
+
breached_count: breached_traces.size,
|
|
113
|
+
strongest: strongest_traces(limit: 3).map(&:to_h)
|
|
114
|
+
}
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_h
|
|
118
|
+
{
|
|
119
|
+
total_traces: @traces.size,
|
|
120
|
+
active: active_traces.size,
|
|
121
|
+
subliminal_load: overall_subliminal_load,
|
|
122
|
+
total_influences: @influences.size
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
def prune_extinct
|
|
129
|
+
return if @traces.size < MAX_TRACES
|
|
130
|
+
|
|
131
|
+
@traces.reject! { |_, t| t.extinct? }
|
|
132
|
+
return unless @traces.size >= MAX_TRACES
|
|
133
|
+
|
|
134
|
+
weakest = @traces.values.min_by(&:activation)
|
|
135
|
+
@traces.delete(weakest.id) if weakest
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def prune_influences
|
|
139
|
+
@influences.shift while @influences.size > MAX_INFLUENCES
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Subliminal
|
|
8
|
+
module Helpers
|
|
9
|
+
class SubliminalTrace
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :content, :trace_type, :domain, :original_activation,
|
|
13
|
+
:influence_target, :created_at, :influence_count
|
|
14
|
+
attr_accessor :activation
|
|
15
|
+
|
|
16
|
+
def initialize(content:, trace_type: :associative, domain: :general, activation: DEFAULT_ACTIVATION,
|
|
17
|
+
influence_target: :attention)
|
|
18
|
+
@id = SecureRandom.uuid
|
|
19
|
+
@content = content.to_s
|
|
20
|
+
@trace_type = valid_trace_type(trace_type)
|
|
21
|
+
@domain = domain.to_sym
|
|
22
|
+
@activation = activation.to_f.clamp(SUBLIMINAL_FLOOR, SUBLIMINAL_CEILING).round(10)
|
|
23
|
+
@original_activation = @activation
|
|
24
|
+
@influence_target = valid_influence_target(influence_target)
|
|
25
|
+
@influence_count = 0
|
|
26
|
+
@created_at = Time.now
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def boost!(amount: ACTIVATION_BOOST)
|
|
30
|
+
@activation = (@activation + amount).clamp(0.0, SUBLIMINAL_CEILING).round(10)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def decay!
|
|
35
|
+
@activation = (@activation - ACTIVATION_DECAY).clamp(0.0, 1.0).round(10)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def exert_influence!
|
|
40
|
+
@influence_count += 1
|
|
41
|
+
@activation = (@activation - (INFLUENCE_STRENGTH * 0.5)).clamp(0.0, 1.0).round(10)
|
|
42
|
+
influence_magnitude
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def influence_magnitude
|
|
46
|
+
(@activation * INFLUENCE_STRENGTH).clamp(0.0, MAX_INFLUENCE_PER_DOMAIN).round(10)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def near_threshold? = @activation >= 0.3 && @activation < CONSCIOUS_THRESHOLD
|
|
50
|
+
def active? = @activation >= SUBLIMINAL_FLOOR
|
|
51
|
+
def extinct? = @activation < EXTINCTION_THRESHOLD
|
|
52
|
+
def potent? = @activation >= 0.2
|
|
53
|
+
|
|
54
|
+
def breached_threshold?
|
|
55
|
+
@activation >= CONSCIOUS_THRESHOLD
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def persistence = (@activation / @original_activation).clamp(0.0, 1.0).round(10)
|
|
59
|
+
def activation_label = Constants.label_for(ACTIVATION_LABELS, @activation)
|
|
60
|
+
def influence_label = Constants.label_for(INFLUENCE_LABELS, influence_magnitude)
|
|
61
|
+
|
|
62
|
+
def to_h
|
|
63
|
+
{
|
|
64
|
+
id: @id,
|
|
65
|
+
content: @content,
|
|
66
|
+
trace_type: @trace_type,
|
|
67
|
+
domain: @domain,
|
|
68
|
+
activation: @activation,
|
|
69
|
+
original_activation: @original_activation,
|
|
70
|
+
influence_target: @influence_target,
|
|
71
|
+
influence_count: @influence_count,
|
|
72
|
+
influence_magnitude: influence_magnitude,
|
|
73
|
+
near_threshold: near_threshold?,
|
|
74
|
+
active: active?,
|
|
75
|
+
extinct: extinct?,
|
|
76
|
+
activation_label: activation_label,
|
|
77
|
+
persistence: persistence,
|
|
78
|
+
created_at: @created_at.iso8601
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def valid_trace_type(type)
|
|
85
|
+
sym = type.to_sym
|
|
86
|
+
TRACE_TYPES.include?(sym) ? sym : :associative
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def valid_influence_target(target)
|
|
90
|
+
sym = target.to_sym
|
|
91
|
+
INFLUENCE_TARGETS.include?(sym) ? sym : :attention
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|