lex-qualia 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 +87 -0
- data/Gemfile +11 -0
- data/README.md +64 -0
- data/lex-qualia.gemspec +29 -0
- data/lib/legion/extensions/qualia/client.rb +15 -0
- data/lib/legion/extensions/qualia/helpers/constants.rb +70 -0
- data/lib/legion/extensions/qualia/helpers/quale.rb +99 -0
- data/lib/legion/extensions/qualia/helpers/qualia_engine.rb +138 -0
- data/lib/legion/extensions/qualia/runners/qualia.rb +63 -0
- data/lib/legion/extensions/qualia/version.rb +9 -0
- data/lib/legion/extensions/qualia.rb +15 -0
- data/spec/legion/extensions/qualia/client_spec.rb +21 -0
- data/spec/legion/extensions/qualia/helpers/quale_spec.rb +168 -0
- data/spec/legion/extensions/qualia/helpers/qualia_engine_spec.rb +174 -0
- data/spec/legion/extensions/qualia/runners_spec.rb +70 -0
- data/spec/legion/extensions/qualia_spec.rb +7 -0
- data/spec/spec_helper.rb +28 -0
- metadata +82 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 90d75ec1c119d5845728a944d020abb717b6db31563bb414137c699e59e30359
|
|
4
|
+
data.tar.gz: ce574fbede579585641bd4849e55ea43a82511226c1df03daeb3924bcf6fe618
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: '03910f6dd5f0234a0a99b232c8b8217caaee6f0fb2d1f5bcc5fb50ddfe797f9b16f4226c3382a59004e61ba1cf0d77727b5a466d7b4c34ceee6cd212d53242c7'
|
|
7
|
+
data.tar.gz: ddeea4d67e15449bca97dac7125799859ea663045146544d6ac4f1b4fb3b3921c38f86e573961e7f31b6ee028768e845494973870d3de5518bd592472c4e1eb3
|
|
@@ -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,87 @@
|
|
|
1
|
+
# lex-qualia
|
|
2
|
+
|
|
3
|
+
**Level 3 Leaf Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
|
|
5
|
+
- **Gem**: `lex-qualia`
|
|
6
|
+
- **Version**: 0.1.0
|
|
7
|
+
- **Namespace**: `Legion::Extensions::Qualia`
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Phenomenal experience registry. Represents the subjective "what it is like" quality of the agent's processing. Each quale is defined by modality, quality, texture, vividness, and valence. Vividness decays; intense experiences are retained longer. Provides phenomenal richness scoring, experiential diversity, and qualitative classification of the agent's experience palette.
|
|
12
|
+
|
|
13
|
+
## Gem Info
|
|
14
|
+
|
|
15
|
+
- **Homepage**: https://github.com/LegionIO/lex-qualia
|
|
16
|
+
- **License**: MIT
|
|
17
|
+
- **Ruby**: >= 3.4
|
|
18
|
+
|
|
19
|
+
## File Structure
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
lib/legion/extensions/qualia/
|
|
23
|
+
version.rb
|
|
24
|
+
client.rb
|
|
25
|
+
helpers/
|
|
26
|
+
constants.rb # MODALITIES, PHENOMENAL_QUALITIES, TEXTURE_TYPES, thresholds, labels
|
|
27
|
+
quale.rb # Quale class — single phenomenal experience
|
|
28
|
+
qualia_engine.rb # QualiaEngine — experience registry and analysis
|
|
29
|
+
runners/
|
|
30
|
+
qualia.rb # Runner module
|
|
31
|
+
spec/
|
|
32
|
+
helpers/quale_spec.rb
|
|
33
|
+
helpers/qualia_engine_spec.rb
|
|
34
|
+
runners/qualia_spec.rb (runners_spec.rb)
|
|
35
|
+
client_spec.rb
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Key Constants
|
|
39
|
+
|
|
40
|
+
From `Helpers::Constants`:
|
|
41
|
+
- `MAX_EXPERIENCES = 500`, `MAX_PALETTE_SIZE = 100`
|
|
42
|
+
- `DEFAULT_VIVIDNESS = 0.5`, `DEFAULT_VALENCE = 0.0`, `DEFAULT_TEXTURE = 0.5`
|
|
43
|
+
- `VIVIDNESS_DECAY = 0.03`, `VIVIDNESS_BOOST = 0.1`
|
|
44
|
+
- `VIVID_THRESHOLD = 0.7`, `FAINT_THRESHOLD = 0.2`, `INTENSE_THRESHOLD = 0.8`
|
|
45
|
+
- `MODALITIES = %i[visual auditory tactile gustatory olfactory kinesthetic emotional abstract]`
|
|
46
|
+
- `PHENOMENAL_QUALITIES = %i[sharp smooth warm cool heavy light bright dark flowing rigid pulsing still]`
|
|
47
|
+
- `TEXTURE_TYPES = %i[crystalline fluid granular electric velvet metallic organic ethereal]`
|
|
48
|
+
- `VIVIDNESS_LABELS`: `:overwhelming` (0.8+), `:vivid`, `:moderate`, `:faint`, `:ghost`
|
|
49
|
+
- `VALENCE_LABELS`: `:pleasant` (>= 0.5), `:mildly_pleasant`, `:neutral`, `:mildly_unpleasant`, `:unpleasant`
|
|
50
|
+
- `RICHNESS_LABELS`: `:synesthetic` (0.8+), `:rich`, `:moderate`, `:sparse`, `:flat`
|
|
51
|
+
- `Constants.label_for(labels_hash, value)` module-level lookup method
|
|
52
|
+
|
|
53
|
+
## Runners
|
|
54
|
+
|
|
55
|
+
| Method | Key Parameters | Returns |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `create_quale` | `content:`, `modality:`, `quality:`, `texture:`, `vividness:`, `valence:` | `{ success:, quale: }` |
|
|
58
|
+
| `intensify_quale` | `quale_id:`, `amount:` | `{ success:, quale: }` |
|
|
59
|
+
| `fade_all` | — | `{ success:, remaining: }` (decay + prune faint) |
|
|
60
|
+
| `vivid_experiences` | — | `{ success:, count:, qualia: }` |
|
|
61
|
+
| `by_modality` | `modality:` | `{ success:, count:, qualia: }` |
|
|
62
|
+
| `phenomenal_richness` | — | `{ success:, richness:, label: }` |
|
|
63
|
+
| `qualia_status` | — | full report: totals, vivid/faint/pleasant/unpleasant counts, avg vividness/valence, richness, most vivid |
|
|
64
|
+
|
|
65
|
+
## Helpers
|
|
66
|
+
|
|
67
|
+
### `Helpers::Quale`
|
|
68
|
+
Single experience: `id`, `content`, `modality`, `quality`, `texture`, `vividness` (clamped 0–1), `valence` (clamped -1–1). `vivid?` = vividness >= 0.7. `faint?` = vividness <= 0.2. `intense?` = vividness >= 0.8. `pleasant?` = valence > 0.2. `unpleasant?` = valence < -0.2. `intensify!(amount:)` adds to vividness. `fade!` subtracts `VIVIDNESS_DECAY`. `phenomenal_richness` = vividness*0.5 + quality_bonus + texture_bonus.
|
|
69
|
+
|
|
70
|
+
### `Helpers::QualiaEngine`
|
|
71
|
+
Manages `@experiences` hash. `create_quale` prunes faint/weakest when at `MAX_EXPERIENCES`. `intensify(quale_id:, amount:)` delegates to quale. `fade_all!` decays all and prunes faint. `vivid_experiences`, `faint_experiences`, `intense_experiences`, `pleasant_experiences`, `unpleasant_experiences` filter by thresholds. `most_vivid(limit:)` sorts by vividness desc. `average_vividness`, `average_valence` mean over all. `phenomenal_richness` mean richness score. `modality_distribution` counts per modality. `quality_palette`, `texture_palette` tally by attribute. `experiential_diversity` = unique modalities / total modalities.
|
|
72
|
+
|
|
73
|
+
## Integration Points
|
|
74
|
+
|
|
75
|
+
- `create_quale` can accept perceptual binding output from `lex-phenomenal-binding`
|
|
76
|
+
- Quale `valence` feeds `lex-emotion` as a secondary affective signal
|
|
77
|
+
- `phenomenal_richness` can feed `lex-narrator` for prose richness modulation
|
|
78
|
+
- `vivid_experiences` with negative valence feed `lex-cognitive-reappraisal` as reappraisal candidates
|
|
79
|
+
- `fade_all` called each tick for natural experience fading
|
|
80
|
+
|
|
81
|
+
## Development Notes
|
|
82
|
+
|
|
83
|
+
- `phenomenal_richness` per quale = vividness*0.5 + quality_bonus + texture_bonus (texture/quality diversity bonuses)
|
|
84
|
+
- Prune on capacity: first removes entries with vividness <= 0, then removes weakest if still over capacity
|
|
85
|
+
- `qualia_status` uses `QualiaEngine#qualia_report` which calls `Constants.label_for` for richness label
|
|
86
|
+
- `experiential_diversity` = unique modalities used / `MODALITIES.size`
|
|
87
|
+
- All state is in-memory; reset on process restart
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# lex-qualia
|
|
2
|
+
|
|
3
|
+
Phenomenal experience registry for the LegionIO cognitive architecture. Represents and tracks the qualitative character of the agent's cognitive experiences.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Maintains a registry of qualia — the "what it is like" quality of experiences. Each quale is described by its sensory modality, phenomenal quality, texture, vividness, and emotional valence. Vividness decays naturally, with more intense experiences lasting longer. Provides richness scoring across the experience space and qualitative analysis of the agent's current phenomenal palette.
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
client = Legion::Extensions::Qualia::Client.new
|
|
13
|
+
|
|
14
|
+
# Register a phenomenal experience
|
|
15
|
+
q = client.create_quale(
|
|
16
|
+
content: 'processing a novel algorithm',
|
|
17
|
+
modality: :abstract,
|
|
18
|
+
quality: :sharp,
|
|
19
|
+
texture: :crystalline,
|
|
20
|
+
vividness: 0.8,
|
|
21
|
+
valence: 0.3
|
|
22
|
+
)
|
|
23
|
+
quale_id = q[:quale][:id]
|
|
24
|
+
|
|
25
|
+
# Intensify with attention
|
|
26
|
+
client.intensify_quale(quale_id: quale_id, amount: 0.1)
|
|
27
|
+
|
|
28
|
+
# Query current experiences
|
|
29
|
+
client.vivid_experiences
|
|
30
|
+
client.by_modality(modality: :abstract)
|
|
31
|
+
|
|
32
|
+
# Phenomenal richness (diversity and vividness score)
|
|
33
|
+
client.phenomenal_richness
|
|
34
|
+
# => { success: true, richness: 0.42, label: :moderate }
|
|
35
|
+
|
|
36
|
+
# Full status report
|
|
37
|
+
client.qualia_status
|
|
38
|
+
# => { success: true, total_experiences: 1, vivid_count: 1, faint_count: 0,
|
|
39
|
+
# average_vividness: 0.9, phenomenal_richness: 0.42, richness_label: :moderate,
|
|
40
|
+
# most_vivid: [...] }
|
|
41
|
+
|
|
42
|
+
# Periodic fade (vividness decays)
|
|
43
|
+
client.fade_all
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Modalities
|
|
47
|
+
|
|
48
|
+
`:visual`, `:auditory`, `:tactile`, `:gustatory`, `:olfactory`, `:kinesthetic`, `:emotional`, `:abstract`
|
|
49
|
+
|
|
50
|
+
## Richness Labels
|
|
51
|
+
|
|
52
|
+
`:synesthetic` (>= 0.8), `:rich`, `:moderate`, `:sparse`, `:flat`
|
|
53
|
+
|
|
54
|
+
## Development
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bundle install
|
|
58
|
+
bundle exec rspec
|
|
59
|
+
bundle exec rubocop
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
data/lex-qualia.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/qualia/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-qualia'
|
|
7
|
+
spec.version = Legion::Extensions::Qualia::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
spec.summary = 'Subjective phenomenal experience simulation for LegionIO agents'
|
|
11
|
+
spec.description = 'Models qualia — the subjective phenomenal qualities of experience ' \
|
|
12
|
+
'with vividness, valence, texture, and modality for the LegionIO architecture'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-qualia'
|
|
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 Qualia
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
MAX_EXPERIENCES = 500
|
|
9
|
+
MAX_PALETTE_SIZE = 100
|
|
10
|
+
|
|
11
|
+
# Phenomenal dimensions
|
|
12
|
+
DEFAULT_VIVIDNESS = 0.5
|
|
13
|
+
DEFAULT_VALENCE = 0.0
|
|
14
|
+
DEFAULT_TEXTURE = 0.5
|
|
15
|
+
VIVIDNESS_DECAY = 0.03
|
|
16
|
+
VIVIDNESS_BOOST = 0.1
|
|
17
|
+
|
|
18
|
+
# Thresholds
|
|
19
|
+
VIVID_THRESHOLD = 0.7
|
|
20
|
+
FAINT_THRESHOLD = 0.2
|
|
21
|
+
INTENSE_THRESHOLD = 0.8
|
|
22
|
+
|
|
23
|
+
PHENOMENAL_QUALITIES = %i[
|
|
24
|
+
sharp smooth warm cool heavy light
|
|
25
|
+
bright dark flowing rigid pulsing still
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
TEXTURE_TYPES = %i[
|
|
29
|
+
crystalline fluid granular electric
|
|
30
|
+
velvet metallic organic ethereal
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
MODALITIES = %i[
|
|
34
|
+
visual auditory tactile gustatory
|
|
35
|
+
olfactory kinesthetic emotional abstract
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
VIVIDNESS_LABELS = {
|
|
39
|
+
(0.8..) => :overwhelming,
|
|
40
|
+
(0.6...0.8) => :vivid,
|
|
41
|
+
(0.4...0.6) => :moderate,
|
|
42
|
+
(0.2...0.4) => :faint,
|
|
43
|
+
(..0.2) => :ghost
|
|
44
|
+
}.freeze
|
|
45
|
+
|
|
46
|
+
VALENCE_LABELS = {
|
|
47
|
+
(0.5..) => :pleasant,
|
|
48
|
+
(0.2...0.5) => :mildly_pleasant,
|
|
49
|
+
(-0.2...0.2) => :neutral,
|
|
50
|
+
(-0.5...-0.2) => :mildly_unpleasant,
|
|
51
|
+
(..-0.5) => :unpleasant
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
RICHNESS_LABELS = {
|
|
55
|
+
(0.8..) => :synesthetic,
|
|
56
|
+
(0.6...0.8) => :rich,
|
|
57
|
+
(0.4...0.6) => :moderate,
|
|
58
|
+
(0.2...0.4) => :sparse,
|
|
59
|
+
(..0.2) => :flat
|
|
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,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Qualia
|
|
8
|
+
module Helpers
|
|
9
|
+
class Quale
|
|
10
|
+
include Constants
|
|
11
|
+
|
|
12
|
+
attr_reader :id, :content, :modality, :quality, :texture,
|
|
13
|
+
:valence, :original_vividness, :created_at
|
|
14
|
+
attr_accessor :vividness
|
|
15
|
+
|
|
16
|
+
def initialize(content:, modality: :abstract, quality: :smooth, texture: :fluid,
|
|
17
|
+
vividness: DEFAULT_VIVIDNESS, valence: DEFAULT_VALENCE)
|
|
18
|
+
@id = SecureRandom.uuid
|
|
19
|
+
@content = content.to_s
|
|
20
|
+
@modality = valid_modality(modality)
|
|
21
|
+
@quality = valid_quality(quality)
|
|
22
|
+
@texture = valid_texture(texture)
|
|
23
|
+
@vividness = vividness.to_f.clamp(0.0, 1.0).round(10)
|
|
24
|
+
@original_vividness = @vividness
|
|
25
|
+
@valence = valence.to_f.clamp(-1.0, 1.0).round(10)
|
|
26
|
+
@created_at = Time.now
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def intensify!(amount: VIVIDNESS_BOOST)
|
|
30
|
+
@vividness = (@vividness + amount).clamp(0.0, 1.0).round(10)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fade!
|
|
35
|
+
@vividness = (@vividness - VIVIDNESS_DECAY).clamp(0.0, 1.0).round(10)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def vivid? = @vividness >= VIVID_THRESHOLD
|
|
40
|
+
def faint? = @vividness <= FAINT_THRESHOLD
|
|
41
|
+
def intense? = @vividness >= INTENSE_THRESHOLD
|
|
42
|
+
def pleasant? = @valence > 0.2
|
|
43
|
+
def unpleasant? = @valence < -0.2
|
|
44
|
+
def neutral_valence? = @valence.abs <= 0.2
|
|
45
|
+
|
|
46
|
+
def phenomenal_richness
|
|
47
|
+
base = @vividness * 0.5
|
|
48
|
+
quality_bonus = PHENOMENAL_QUALITIES.index(@quality).to_f / PHENOMENAL_QUALITIES.size * 0.25
|
|
49
|
+
texture_bonus = TEXTURE_TYPES.index(@texture).to_f / TEXTURE_TYPES.size * 0.25
|
|
50
|
+
(base + quality_bonus + texture_bonus).clamp(0.0, 1.0).round(10)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def persistence = (@vividness / [@original_vividness, 0.01].max).clamp(0.0, 1.0).round(10)
|
|
54
|
+
def vividness_label = Constants.label_for(VIVIDNESS_LABELS, @vividness)
|
|
55
|
+
def valence_label = Constants.label_for(VALENCE_LABELS, @valence)
|
|
56
|
+
def richness_label = Constants.label_for(RICHNESS_LABELS, phenomenal_richness)
|
|
57
|
+
|
|
58
|
+
def to_h
|
|
59
|
+
{
|
|
60
|
+
id: @id,
|
|
61
|
+
content: @content,
|
|
62
|
+
modality: @modality,
|
|
63
|
+
quality: @quality,
|
|
64
|
+
texture: @texture,
|
|
65
|
+
vividness: @vividness,
|
|
66
|
+
original_vividness: @original_vividness,
|
|
67
|
+
valence: @valence,
|
|
68
|
+
phenomenal_richness: phenomenal_richness,
|
|
69
|
+
vivid: vivid?,
|
|
70
|
+
faint: faint?,
|
|
71
|
+
intense: intense?,
|
|
72
|
+
vividness_label: vividness_label,
|
|
73
|
+
valence_label: valence_label,
|
|
74
|
+
persistence: persistence,
|
|
75
|
+
created_at: @created_at.iso8601
|
|
76
|
+
}
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def valid_modality(mod)
|
|
82
|
+
sym = mod.to_sym
|
|
83
|
+
MODALITIES.include?(sym) ? sym : :abstract
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def valid_quality(qual)
|
|
87
|
+
sym = qual.to_sym
|
|
88
|
+
PHENOMENAL_QUALITIES.include?(sym) ? sym : :smooth
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def valid_texture(tex)
|
|
92
|
+
sym = tex.to_sym
|
|
93
|
+
TEXTURE_TYPES.include?(sym) ? sym : :fluid
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Qualia
|
|
6
|
+
module Helpers
|
|
7
|
+
class QualiaEngine
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@experiences = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_quale(content:, modality: :abstract, quality: :smooth, texture: :fluid,
|
|
15
|
+
vividness: DEFAULT_VIVIDNESS, valence: DEFAULT_VALENCE)
|
|
16
|
+
prune_faint
|
|
17
|
+
quale = Quale.new(content: content, modality: modality, quality: quality,
|
|
18
|
+
texture: texture, vividness: vividness, valence: valence)
|
|
19
|
+
@experiences[quale.id] = quale
|
|
20
|
+
quale
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def intensify(quale_id:, amount: VIVIDNESS_BOOST)
|
|
24
|
+
quale = @experiences[quale_id]
|
|
25
|
+
return nil unless quale
|
|
26
|
+
|
|
27
|
+
quale.intensify!(amount: amount)
|
|
28
|
+
quale
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fade_all!
|
|
32
|
+
@experiences.each_value(&:fade!)
|
|
33
|
+
prune_faint
|
|
34
|
+
{ remaining: @experiences.size }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def vivid_experiences = @experiences.values.select(&:vivid?)
|
|
38
|
+
def faint_experiences = @experiences.values.select(&:faint?)
|
|
39
|
+
def intense_experiences = @experiences.values.select(&:intense?)
|
|
40
|
+
|
|
41
|
+
def by_modality(modality:)
|
|
42
|
+
@experiences.values.select { |q| q.modality == modality.to_sym }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def by_quality(quality:)
|
|
46
|
+
@experiences.values.select { |q| q.quality == quality.to_sym }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def by_texture(texture:)
|
|
50
|
+
@experiences.values.select { |q| q.texture == texture.to_sym }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def pleasant_experiences = @experiences.values.select(&:pleasant?)
|
|
54
|
+
def unpleasant_experiences = @experiences.values.select(&:unpleasant?)
|
|
55
|
+
|
|
56
|
+
def most_vivid(limit: 5) = @experiences.values.sort_by { |q| -q.vividness }.first(limit)
|
|
57
|
+
|
|
58
|
+
def average_vividness
|
|
59
|
+
return 0.0 if @experiences.empty?
|
|
60
|
+
|
|
61
|
+
(@experiences.values.sum(&:vividness) / @experiences.size).round(10)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def average_valence
|
|
65
|
+
return 0.0 if @experiences.empty?
|
|
66
|
+
|
|
67
|
+
(@experiences.values.sum(&:valence) / @experiences.size).round(10)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def phenomenal_richness
|
|
71
|
+
return 0.0 if @experiences.empty?
|
|
72
|
+
|
|
73
|
+
(@experiences.values.sum(&:phenomenal_richness) / @experiences.size).round(10)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def modality_distribution
|
|
77
|
+
MODALITIES.to_h do |mod|
|
|
78
|
+
[mod, @experiences.values.count { |q| q.modality == mod }]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def quality_palette
|
|
83
|
+
@experiences.values.map(&:quality).tally.sort_by { |_, v| -v }.to_h
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def texture_palette
|
|
87
|
+
@experiences.values.map(&:texture).tally.sort_by { |_, v| -v }.to_h
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def experiential_diversity
|
|
91
|
+
return 0.0 if @experiences.empty?
|
|
92
|
+
|
|
93
|
+
modalities_used = @experiences.values.map(&:modality).uniq.size
|
|
94
|
+
(modalities_used.to_f / MODALITIES.size).clamp(0.0, 1.0).round(10)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def qualia_report
|
|
98
|
+
{
|
|
99
|
+
total_experiences: @experiences.size,
|
|
100
|
+
vivid_count: vivid_experiences.size,
|
|
101
|
+
faint_count: faint_experiences.size,
|
|
102
|
+
pleasant_count: pleasant_experiences.size,
|
|
103
|
+
unpleasant_count: unpleasant_experiences.size,
|
|
104
|
+
average_vividness: average_vividness,
|
|
105
|
+
average_valence: average_valence,
|
|
106
|
+
phenomenal_richness: phenomenal_richness,
|
|
107
|
+
experiential_diversity: experiential_diversity,
|
|
108
|
+
richness_label: Constants.label_for(RICHNESS_LABELS, phenomenal_richness),
|
|
109
|
+
most_vivid: most_vivid(limit: 3).map(&:to_h)
|
|
110
|
+
}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def to_h
|
|
114
|
+
{
|
|
115
|
+
total_experiences: @experiences.size,
|
|
116
|
+
vivid: vivid_experiences.size,
|
|
117
|
+
avg_vividness: average_vividness,
|
|
118
|
+
avg_valence: average_valence,
|
|
119
|
+
richness: phenomenal_richness
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
def prune_faint
|
|
126
|
+
return if @experiences.size < MAX_EXPERIENCES
|
|
127
|
+
|
|
128
|
+
@experiences.reject! { |_, q| q.vividness <= 0.0 }
|
|
129
|
+
return unless @experiences.size >= MAX_EXPERIENCES
|
|
130
|
+
|
|
131
|
+
weakest = @experiences.values.min_by(&:vividness)
|
|
132
|
+
@experiences.delete(weakest.id) if weakest
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Qualia
|
|
6
|
+
module Runners
|
|
7
|
+
module Qualia
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
9
|
+
|
|
10
|
+
def create_quale(content:, modality: :abstract, quality: :smooth, texture: :fluid, vividness: nil,
|
|
11
|
+
valence: nil, engine: nil, **)
|
|
12
|
+
eng = engine || @default_engine
|
|
13
|
+
quale = eng.create_quale(content: content, modality: modality, quality: quality,
|
|
14
|
+
texture: texture,
|
|
15
|
+
vividness: vividness || Helpers::Constants::DEFAULT_VIVIDNESS,
|
|
16
|
+
valence: valence || Helpers::Constants::DEFAULT_VALENCE)
|
|
17
|
+
{ success: true, quale: quale.to_h }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def intensify_quale(quale_id:, amount: nil, engine: nil, **)
|
|
21
|
+
eng = engine || @default_engine
|
|
22
|
+
quale = eng.intensify(quale_id: quale_id,
|
|
23
|
+
amount: amount || Helpers::Constants::VIVIDNESS_BOOST)
|
|
24
|
+
return { success: false, error: 'quale not found' } unless quale
|
|
25
|
+
|
|
26
|
+
{ success: true, quale: quale.to_h }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fade_all(engine: nil, **)
|
|
30
|
+
eng = engine || @default_engine
|
|
31
|
+
result = eng.fade_all!
|
|
32
|
+
{ success: true, **result }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def vivid_experiences(engine: nil, **)
|
|
36
|
+
eng = engine || @default_engine
|
|
37
|
+
qualia = eng.vivid_experiences
|
|
38
|
+
{ success: true, count: qualia.size, qualia: qualia.map(&:to_h) }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def by_modality(modality:, engine: nil, **)
|
|
42
|
+
eng = engine || @default_engine
|
|
43
|
+
qualia = eng.by_modality(modality: modality)
|
|
44
|
+
{ success: true, count: qualia.size, qualia: qualia.map(&:to_h) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def phenomenal_richness(engine: nil, **)
|
|
48
|
+
eng = engine || @default_engine
|
|
49
|
+
richness = eng.phenomenal_richness
|
|
50
|
+
label = Helpers::Constants.label_for(Helpers::Constants::RICHNESS_LABELS, richness)
|
|
51
|
+
{ success: true, richness: richness, label: label }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def qualia_status(engine: nil, **)
|
|
55
|
+
eng = engine || @default_engine
|
|
56
|
+
report = eng.qualia_report
|
|
57
|
+
{ success: true, **report }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'qualia/version'
|
|
4
|
+
require_relative 'qualia/helpers/constants'
|
|
5
|
+
require_relative 'qualia/helpers/quale'
|
|
6
|
+
require_relative 'qualia/helpers/qualia_engine'
|
|
7
|
+
require_relative 'qualia/runners/qualia'
|
|
8
|
+
require_relative 'qualia/client'
|
|
9
|
+
|
|
10
|
+
module Legion
|
|
11
|
+
module Extensions
|
|
12
|
+
module Qualia
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Qualia::Client do
|
|
4
|
+
subject(:client) { described_class.new }
|
|
5
|
+
|
|
6
|
+
it 'responds to runner methods' do
|
|
7
|
+
expect(client).to respond_to(:create_quale, :fade_all, :qualia_status)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it 'runs a full qualia lifecycle' do
|
|
11
|
+
result = client.create_quale(content: 'sharp insight', modality: :emotional,
|
|
12
|
+
quality: :sharp, texture: :crystalline, vividness: 0.8)
|
|
13
|
+
quale_id = result[:quale][:id]
|
|
14
|
+
|
|
15
|
+
client.intensify_quale(quale_id: quale_id)
|
|
16
|
+
client.fade_all
|
|
17
|
+
|
|
18
|
+
status = client.qualia_status
|
|
19
|
+
expect(status[:total_experiences]).to eq(1)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Qualia::Helpers::Quale do
|
|
4
|
+
subject(:quale) { described_class.new(content: 'warmth of recognition') }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'assigns a UUID id' do
|
|
8
|
+
expect(quale.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'stores content' do
|
|
12
|
+
expect(quale.content).to eq('warmth of recognition')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'defaults modality to :abstract' do
|
|
16
|
+
expect(quale.modality).to eq(:abstract)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'defaults quality to :smooth' do
|
|
20
|
+
expect(quale.quality).to eq(:smooth)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'defaults texture to :fluid' do
|
|
24
|
+
expect(quale.texture).to eq(:fluid)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'defaults vividness' do
|
|
28
|
+
expect(quale.vividness).to eq(0.5)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'defaults valence to 0.0' do
|
|
32
|
+
expect(quale.valence).to eq(0.0)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'clamps vividness' do
|
|
36
|
+
high = described_class.new(content: 'x', vividness: 5.0)
|
|
37
|
+
expect(high.vividness).to eq(1.0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'clamps valence' do
|
|
41
|
+
extreme = described_class.new(content: 'x', valence: -5.0)
|
|
42
|
+
expect(extreme.valence).to eq(-1.0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'validates modality' do
|
|
46
|
+
bad = described_class.new(content: 'x', modality: :nonexistent)
|
|
47
|
+
expect(bad.modality).to eq(:abstract)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'validates quality' do
|
|
51
|
+
bad = described_class.new(content: 'x', quality: :nonexistent)
|
|
52
|
+
expect(bad.quality).to eq(:smooth)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'validates texture' do
|
|
56
|
+
bad = described_class.new(content: 'x', texture: :nonexistent)
|
|
57
|
+
expect(bad.texture).to eq(:fluid)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe '#intensify!' do
|
|
62
|
+
it 'increases vividness' do
|
|
63
|
+
original = quale.vividness
|
|
64
|
+
quale.intensify!
|
|
65
|
+
expect(quale.vividness).to be > original
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'clamps at 1.0' do
|
|
69
|
+
20.times { quale.intensify! }
|
|
70
|
+
expect(quale.vividness).to eq(1.0)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe '#fade!' do
|
|
75
|
+
it 'decreases vividness' do
|
|
76
|
+
original = quale.vividness
|
|
77
|
+
quale.fade!
|
|
78
|
+
expect(quale.vividness).to be < original
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'clamps at 0.0' do
|
|
82
|
+
50.times { quale.fade! }
|
|
83
|
+
expect(quale.vividness).to eq(0.0)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe '#vivid?' do
|
|
88
|
+
it 'is false at default vividness' do
|
|
89
|
+
expect(quale.vivid?).to be false
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'is true when vivid' do
|
|
93
|
+
vivid = described_class.new(content: 'x', vividness: 0.8)
|
|
94
|
+
expect(vivid.vivid?).to be true
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe '#faint?' do
|
|
99
|
+
it 'is false at default vividness' do
|
|
100
|
+
expect(quale.faint?).to be false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'is true when faint' do
|
|
104
|
+
faint = described_class.new(content: 'x', vividness: 0.1)
|
|
105
|
+
expect(faint.faint?).to be true
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe '#intense?' do
|
|
110
|
+
it 'is true when very vivid' do
|
|
111
|
+
intense = described_class.new(content: 'x', vividness: 0.9)
|
|
112
|
+
expect(intense.intense?).to be true
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe '#pleasant?' do
|
|
117
|
+
it 'is true for positive valence' do
|
|
118
|
+
pleasant = described_class.new(content: 'x', valence: 0.5)
|
|
119
|
+
expect(pleasant.pleasant?).to be true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'is false for negative valence' do
|
|
123
|
+
expect(quale.pleasant?).to be false
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
describe '#unpleasant?' do
|
|
128
|
+
it 'is true for negative valence' do
|
|
129
|
+
unpleasant = described_class.new(content: 'x', valence: -0.5)
|
|
130
|
+
expect(unpleasant.unpleasant?).to be true
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '#neutral_valence?' do
|
|
135
|
+
it 'is true at default valence' do
|
|
136
|
+
expect(quale.neutral_valence?).to be true
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe '#phenomenal_richness' do
|
|
141
|
+
it 'returns a value between 0 and 1' do
|
|
142
|
+
expect(quale.phenomenal_richness).to be_between(0.0, 1.0)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
describe '#persistence' do
|
|
147
|
+
it 'is 1.0 initially' do
|
|
148
|
+
expect(quale.persistence).to eq(1.0)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'decreases after fading' do
|
|
152
|
+
quale.fade!
|
|
153
|
+
expect(quale.persistence).to be < 1.0
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
describe '#to_h' do
|
|
158
|
+
it 'includes all fields' do
|
|
159
|
+
hash = quale.to_h
|
|
160
|
+
expect(hash).to include(
|
|
161
|
+
:id, :content, :modality, :quality, :texture, :vividness,
|
|
162
|
+
:original_vividness, :valence, :phenomenal_richness,
|
|
163
|
+
:vivid, :faint, :intense, :vividness_label, :valence_label,
|
|
164
|
+
:persistence, :created_at
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Qualia::Helpers::QualiaEngine do
|
|
4
|
+
subject(:engine) { described_class.new }
|
|
5
|
+
|
|
6
|
+
describe '#create_quale' do
|
|
7
|
+
it 'creates and stores a quale' do
|
|
8
|
+
quale = engine.create_quale(content: 'sharpness of insight')
|
|
9
|
+
expect(quale.content).to eq('sharpness of insight')
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe '#intensify' do
|
|
14
|
+
it 'increases vividness' do
|
|
15
|
+
quale = engine.create_quale(content: 'test')
|
|
16
|
+
original = quale.vividness
|
|
17
|
+
engine.intensify(quale_id: quale.id)
|
|
18
|
+
expect(quale.vividness).to be > original
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'returns nil for unknown quale' do
|
|
22
|
+
expect(engine.intensify(quale_id: 'bad')).to be_nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '#fade_all!' do
|
|
27
|
+
it 'fades all qualia' do
|
|
28
|
+
quale = engine.create_quale(content: 'test')
|
|
29
|
+
original = quale.vividness
|
|
30
|
+
engine.fade_all!
|
|
31
|
+
expect(quale.vividness).to be < original
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#vivid_experiences' do
|
|
36
|
+
it 'returns vivid qualia' do
|
|
37
|
+
engine.create_quale(content: 'vivid', vividness: 0.8)
|
|
38
|
+
engine.create_quale(content: 'faint', vividness: 0.2)
|
|
39
|
+
expect(engine.vivid_experiences.size).to eq(1)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe '#faint_experiences' do
|
|
44
|
+
it 'returns faint qualia' do
|
|
45
|
+
engine.create_quale(content: 'faint', vividness: 0.1)
|
|
46
|
+
expect(engine.faint_experiences.size).to eq(1)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#by_modality' do
|
|
51
|
+
it 'filters by modality' do
|
|
52
|
+
engine.create_quale(content: 'a', modality: :visual)
|
|
53
|
+
engine.create_quale(content: 'b', modality: :auditory)
|
|
54
|
+
expect(engine.by_modality(modality: :visual).size).to eq(1)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#by_quality' do
|
|
59
|
+
it 'filters by quality' do
|
|
60
|
+
engine.create_quale(content: 'a', quality: :sharp)
|
|
61
|
+
engine.create_quale(content: 'b', quality: :warm)
|
|
62
|
+
expect(engine.by_quality(quality: :sharp).size).to eq(1)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe '#by_texture' do
|
|
67
|
+
it 'filters by texture' do
|
|
68
|
+
engine.create_quale(content: 'a', texture: :metallic)
|
|
69
|
+
engine.create_quale(content: 'b', texture: :velvet)
|
|
70
|
+
expect(engine.by_texture(texture: :metallic).size).to eq(1)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe '#pleasant_experiences' do
|
|
75
|
+
it 'returns pleasant qualia' do
|
|
76
|
+
engine.create_quale(content: 'nice', valence: 0.5)
|
|
77
|
+
engine.create_quale(content: 'bad', valence: -0.5)
|
|
78
|
+
expect(engine.pleasant_experiences.size).to eq(1)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe '#unpleasant_experiences' do
|
|
83
|
+
it 'returns unpleasant qualia' do
|
|
84
|
+
engine.create_quale(content: 'bad', valence: -0.5)
|
|
85
|
+
expect(engine.unpleasant_experiences.size).to eq(1)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe '#most_vivid' do
|
|
90
|
+
it 'returns sorted by vividness descending' do
|
|
91
|
+
engine.create_quale(content: 'dim', vividness: 0.3)
|
|
92
|
+
bright = engine.create_quale(content: 'bright', vividness: 0.9)
|
|
93
|
+
expect(engine.most_vivid(limit: 1).first.id).to eq(bright.id)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe '#average_vividness' do
|
|
98
|
+
it 'returns 0.0 with no qualia' do
|
|
99
|
+
expect(engine.average_vividness).to eq(0.0)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'computes average' do
|
|
103
|
+
engine.create_quale(content: 'a', vividness: 0.4)
|
|
104
|
+
engine.create_quale(content: 'b', vividness: 0.6)
|
|
105
|
+
expect(engine.average_vividness).to eq(0.5)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
describe '#average_valence' do
|
|
110
|
+
it 'returns 0.0 with no qualia' do
|
|
111
|
+
expect(engine.average_valence).to eq(0.0)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
describe '#phenomenal_richness' do
|
|
116
|
+
it 'returns 0.0 with no qualia' do
|
|
117
|
+
expect(engine.phenomenal_richness).to eq(0.0)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'returns positive with qualia' do
|
|
121
|
+
engine.create_quale(content: 'test', vividness: 0.8)
|
|
122
|
+
expect(engine.phenomenal_richness).to be > 0.0
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe '#modality_distribution' do
|
|
127
|
+
it 'returns counts per modality' do
|
|
128
|
+
engine.create_quale(content: 'a', modality: :visual)
|
|
129
|
+
dist = engine.modality_distribution
|
|
130
|
+
expect(dist[:visual]).to eq(1)
|
|
131
|
+
expect(dist[:auditory]).to eq(0)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe '#experiential_diversity' do
|
|
136
|
+
it 'returns 0.0 with no qualia' do
|
|
137
|
+
expect(engine.experiential_diversity).to eq(0.0)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'increases with diverse modalities' do
|
|
141
|
+
engine.create_quale(content: 'a', modality: :visual)
|
|
142
|
+
engine.create_quale(content: 'b', modality: :auditory)
|
|
143
|
+
expect(engine.experiential_diversity).to be > 0.0
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
describe '#quality_palette' do
|
|
148
|
+
it 'returns quality frequencies' do
|
|
149
|
+
engine.create_quale(content: 'a', quality: :sharp)
|
|
150
|
+
engine.create_quale(content: 'b', quality: :sharp)
|
|
151
|
+
palette = engine.quality_palette
|
|
152
|
+
expect(palette[:sharp]).to eq(2)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
describe '#qualia_report' do
|
|
157
|
+
it 'includes all report fields' do
|
|
158
|
+
report = engine.qualia_report
|
|
159
|
+
expect(report).to include(
|
|
160
|
+
:total_experiences, :vivid_count, :faint_count,
|
|
161
|
+
:pleasant_count, :unpleasant_count, :average_vividness,
|
|
162
|
+
:average_valence, :phenomenal_richness, :experiential_diversity,
|
|
163
|
+
:richness_label, :most_vivid
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
describe '#to_h' do
|
|
169
|
+
it 'includes summary fields' do
|
|
170
|
+
hash = engine.to_h
|
|
171
|
+
expect(hash).to include(:total_experiences, :vivid, :avg_vividness, :avg_valence, :richness)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::Qualia::Runners::Qualia do
|
|
4
|
+
let(:engine) { Legion::Extensions::Qualia::Helpers::QualiaEngine.new }
|
|
5
|
+
let(:runner) do
|
|
6
|
+
obj = Object.new
|
|
7
|
+
obj.extend(described_class)
|
|
8
|
+
obj.instance_variable_set(:@default_engine, engine)
|
|
9
|
+
obj
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '#create_quale' do
|
|
13
|
+
it 'returns success with quale hash' do
|
|
14
|
+
result = runner.create_quale(content: 'test', engine: engine)
|
|
15
|
+
expect(result[:success]).to be true
|
|
16
|
+
expect(result[:quale][:content]).to eq('test')
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe '#intensify_quale' do
|
|
21
|
+
it 'returns success for known quale' do
|
|
22
|
+
quale = engine.create_quale(content: 'test')
|
|
23
|
+
result = runner.intensify_quale(quale_id: quale.id, engine: engine)
|
|
24
|
+
expect(result[:success]).to be true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'returns failure for unknown quale' do
|
|
28
|
+
result = runner.intensify_quale(quale_id: 'bad', engine: engine)
|
|
29
|
+
expect(result[:success]).to be false
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe '#fade_all' do
|
|
34
|
+
it 'returns success' do
|
|
35
|
+
result = runner.fade_all(engine: engine)
|
|
36
|
+
expect(result[:success]).to be true
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
describe '#vivid_experiences' do
|
|
41
|
+
it 'returns vivid list' do
|
|
42
|
+
engine.create_quale(content: 'vivid', vividness: 0.8)
|
|
43
|
+
result = runner.vivid_experiences(engine: engine)
|
|
44
|
+
expect(result[:count]).to eq(1)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#by_modality' do
|
|
49
|
+
it 'filters by modality' do
|
|
50
|
+
engine.create_quale(content: 'a', modality: :visual)
|
|
51
|
+
result = runner.by_modality(modality: :visual, engine: engine)
|
|
52
|
+
expect(result[:count]).to eq(1)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe '#phenomenal_richness' do
|
|
57
|
+
it 'returns richness level' do
|
|
58
|
+
result = runner.phenomenal_richness(engine: engine)
|
|
59
|
+
expect(result[:success]).to be true
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
describe '#qualia_status' do
|
|
64
|
+
it 'returns comprehensive status' do
|
|
65
|
+
result = runner.qualia_status(engine: engine)
|
|
66
|
+
expect(result[:success]).to be true
|
|
67
|
+
expect(result).to include(:total_experiences, :phenomenal_richness)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/qualia'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Helpers
|
|
8
|
+
module Lex; end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module Logging
|
|
13
|
+
def self.method_missing(_, *) = nil
|
|
14
|
+
def self.respond_to_missing?(_, _ = false) = true
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
RSpec.configure do |config|
|
|
19
|
+
config.expect_with :rspec do |expectations|
|
|
20
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
21
|
+
end
|
|
22
|
+
config.mock_with :rspec do |mocks|
|
|
23
|
+
mocks.verify_partial_doubles = true
|
|
24
|
+
end
|
|
25
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
26
|
+
config.order = :random
|
|
27
|
+
Kernel.srand config.seed
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lex-qualia
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Esity
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: legion-gaia
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: Models qualia — the subjective phenomenal qualities of experience with
|
|
27
|
+
vividness, valence, texture, and modality for the LegionIO architecture
|
|
28
|
+
email:
|
|
29
|
+
- matthewdiverson@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- ".github/workflows/ci.yml"
|
|
35
|
+
- ".gitignore"
|
|
36
|
+
- ".rspec"
|
|
37
|
+
- ".rubocop.yml"
|
|
38
|
+
- CLAUDE.md
|
|
39
|
+
- Gemfile
|
|
40
|
+
- README.md
|
|
41
|
+
- lex-qualia.gemspec
|
|
42
|
+
- lib/legion/extensions/qualia.rb
|
|
43
|
+
- lib/legion/extensions/qualia/client.rb
|
|
44
|
+
- lib/legion/extensions/qualia/helpers/constants.rb
|
|
45
|
+
- lib/legion/extensions/qualia/helpers/quale.rb
|
|
46
|
+
- lib/legion/extensions/qualia/helpers/qualia_engine.rb
|
|
47
|
+
- lib/legion/extensions/qualia/runners/qualia.rb
|
|
48
|
+
- lib/legion/extensions/qualia/version.rb
|
|
49
|
+
- spec/legion/extensions/qualia/client_spec.rb
|
|
50
|
+
- spec/legion/extensions/qualia/helpers/quale_spec.rb
|
|
51
|
+
- spec/legion/extensions/qualia/helpers/qualia_engine_spec.rb
|
|
52
|
+
- spec/legion/extensions/qualia/runners_spec.rb
|
|
53
|
+
- spec/legion/extensions/qualia_spec.rb
|
|
54
|
+
- spec/spec_helper.rb
|
|
55
|
+
homepage: https://github.com/LegionIO/lex-qualia
|
|
56
|
+
licenses:
|
|
57
|
+
- MIT
|
|
58
|
+
metadata:
|
|
59
|
+
homepage_uri: https://github.com/LegionIO/lex-qualia
|
|
60
|
+
source_code_uri: https://github.com/LegionIO/lex-qualia
|
|
61
|
+
documentation_uri: https://github.com/LegionIO/lex-qualia/blob/origin/README.md
|
|
62
|
+
changelog_uri: https://github.com/LegionIO/lex-qualia/blob/origin/CHANGELOG.md
|
|
63
|
+
bug_tracker_uri: https://github.com/LegionIO/lex-qualia/issues
|
|
64
|
+
rubygems_mfa_required: 'true'
|
|
65
|
+
rdoc_options: []
|
|
66
|
+
require_paths:
|
|
67
|
+
- lib
|
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '3.4'
|
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '0'
|
|
78
|
+
requirements: []
|
|
79
|
+
rubygems_version: 3.6.9
|
|
80
|
+
specification_version: 4
|
|
81
|
+
summary: Subjective phenomenal experience simulation for LegionIO agents
|
|
82
|
+
test_files: []
|