lex-cognitive-quicksilver 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 +11 -0
- data/.rspec +4 -0
- data/.rubocop.yml +32 -0
- data/CLAUDE.md +123 -0
- data/Gemfile +11 -0
- data/README.md +67 -0
- data/lex-cognitive-quicksilver.gemspec +27 -0
- data/lib/legion/extensions/cognitive_quicksilver/client.rb +25 -0
- data/lib/legion/extensions/cognitive_quicksilver/helpers/constants.rb +46 -0
- data/lib/legion/extensions/cognitive_quicksilver/helpers/droplet.rb +122 -0
- data/lib/legion/extensions/cognitive_quicksilver/helpers/pool.rb +79 -0
- data/lib/legion/extensions/cognitive_quicksilver/helpers/quicksilver_engine.rb +120 -0
- data/lib/legion/extensions/cognitive_quicksilver/runners/cognitive_quicksilver.rb +126 -0
- data/lib/legion/extensions/cognitive_quicksilver/version.rb +9 -0
- data/lib/legion/extensions/cognitive_quicksilver.rb +18 -0
- data/spec/legion/extensions/cognitive_quicksilver/client_spec.rb +72 -0
- data/spec/legion/extensions/cognitive_quicksilver/helpers/constants_spec.rb +105 -0
- data/spec/legion/extensions/cognitive_quicksilver/helpers/droplet_spec.rb +310 -0
- data/spec/legion/extensions/cognitive_quicksilver/helpers/pool_spec.rb +174 -0
- data/spec/legion/extensions/cognitive_quicksilver/helpers/quicksilver_engine_spec.rb +226 -0
- data/spec/legion/extensions/cognitive_quicksilver/runners/cognitive_quicksilver_spec.rb +227 -0
- data/spec/spec_helper.rb +30 -0
- metadata +83 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 571d9e7b2d96df5d5efaa0bc37a1fe7427d7059a128f5b1b6326074447179de2
|
|
4
|
+
data.tar.gz: eb78fdceccf7682d16a9466280b47fd2868a49c0c481f1c318551d9e46df02ac
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d95e318ddc47a7402d10108c37b246538cdf5eeba22ae591e006186921089a457f2499dcbfe9bd484bea6633cc7d2d70a242243e5c7f2215383a018eb8f59d01
|
|
7
|
+
data.tar.gz: 6601e6ddc468ef7ae7241411d0d54a55dbb09b9e74244fa092a59f8d23d7e4e1869ad821b67defbf52697dfa57dbdc060bcb53d858a7438f252ea51c32e5969d
|
|
@@ -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,32 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 3.4
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Layout/LineLength:
|
|
6
|
+
Max: 160
|
|
7
|
+
Style/Documentation:
|
|
8
|
+
Enabled: false
|
|
9
|
+
Naming/PredicateMethod:
|
|
10
|
+
Enabled: false
|
|
11
|
+
Naming/PredicatePrefix:
|
|
12
|
+
Enabled: false
|
|
13
|
+
Metrics/ClassLength:
|
|
14
|
+
Max: 150
|
|
15
|
+
Metrics/MethodLength:
|
|
16
|
+
Max: 25
|
|
17
|
+
Metrics/AbcSize:
|
|
18
|
+
Max: 25
|
|
19
|
+
Metrics/ParameterLists:
|
|
20
|
+
Max: 8
|
|
21
|
+
MaxOptionalParameters: 8
|
|
22
|
+
Layout/HashAlignment:
|
|
23
|
+
EnforcedColonStyle: table
|
|
24
|
+
EnforcedHashRocketStyle: table
|
|
25
|
+
Metrics/BlockLength:
|
|
26
|
+
Exclude:
|
|
27
|
+
- 'spec/**/*'
|
|
28
|
+
Style/FrozenStringLiteralComment:
|
|
29
|
+
Enabled: true
|
|
30
|
+
Style/OneClassPerFile:
|
|
31
|
+
Exclude:
|
|
32
|
+
- 'spec/spec_helper.rb'
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# lex-cognitive-quicksilver
|
|
2
|
+
|
|
3
|
+
**Level 3 Leaf Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-agentic/CLAUDE.md`
|
|
5
|
+
- **Gem**: `lex-cognitive-quicksilver`
|
|
6
|
+
- **Version**: 0.1.0
|
|
7
|
+
- **Namespace**: `Legion::Extensions::CognitiveQuicksilver`
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
Models cognitive fluidity as a mercury-like substance. Thoughts, impulses, and mental contents are represented as **droplets** that can merge, split, evaporate, be captured, or pool together. The metaphor reflects how cognitive content flows, coalesces, and transforms in states of high versus low mental fluidity.
|
|
12
|
+
|
|
13
|
+
## Gem Info
|
|
14
|
+
|
|
15
|
+
- **Gemspec**: `lex-cognitive-quicksilver.gemspec`
|
|
16
|
+
- **Require**: `lex-cognitive-quicksilver`
|
|
17
|
+
- **Ruby**: >= 3.4
|
|
18
|
+
- **License**: MIT
|
|
19
|
+
- **Homepage**: https://github.com/LegionIO/lex-cognitive-quicksilver
|
|
20
|
+
|
|
21
|
+
## File Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
lib/legion/extensions/cognitive_quicksilver/
|
|
25
|
+
version.rb # VERSION = '0.1.0'
|
|
26
|
+
helpers/
|
|
27
|
+
constants.rb # FORM_TYPES, SURFACE_TYPES, label tables, fluidity constants
|
|
28
|
+
droplet.rb # Droplet class — individual unit of cognitive content
|
|
29
|
+
pool.rb # Pool class — collection of droplets on a surface
|
|
30
|
+
quicksilver_engine.rb # QuicksilverEngine — manages droplets and pools
|
|
31
|
+
runners/
|
|
32
|
+
cognitive_quicksilver.rb # Runner module — public API methods
|
|
33
|
+
client.rb # Client class — instantiates engine, includes runner
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Key Constants
|
|
37
|
+
|
|
38
|
+
| Constant | Value | Meaning |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `FORM_TYPES` | `[:liquid, :droplet, :bead, :stream, :pool]` | Valid droplet forms |
|
|
41
|
+
| `SURFACE_TYPES` | `[:glass, :metal, :wood, :stone, :fabric]` | Valid pool/droplet surfaces |
|
|
42
|
+
| `MAX_DROPLETS` | 500 | Hard cap on tracked droplets |
|
|
43
|
+
| `MAX_POOLS` | 50 | Hard cap on tracked pools |
|
|
44
|
+
| `FLUIDITY_BASE` | 0.8 | Default fluidity for new droplets |
|
|
45
|
+
| `SURFACE_TENSION` | 0.3 | Default pool surface tension |
|
|
46
|
+
| `EVAPORATION_RATE` | 0.02 | Mass reduction per `evaporate!` call |
|
|
47
|
+
| `COALESCENCE_BONUS` | 0.1 | Extra mass added when two droplets merge |
|
|
48
|
+
|
|
49
|
+
## Key Classes
|
|
50
|
+
|
|
51
|
+
### `Helpers::Droplet`
|
|
52
|
+
|
|
53
|
+
An individual unit of cognitive content with form, mass, fluidity, and surface properties.
|
|
54
|
+
|
|
55
|
+
- `shift_form!(new_form)` — changes form and adjusts fluidity to `FORM_FLUIDITY[form]`
|
|
56
|
+
- `merge!(other_droplet)` — absorbs another droplet; mass sums + coalescence bonus; fluidity averages
|
|
57
|
+
- `split!` — halves mass and spawns twin; returns nil if mass <= 0.2
|
|
58
|
+
- `capture!` / `release!` — halves/doubles fluidity; toggles `@captured` flag
|
|
59
|
+
- `evaporate!` — reduces mass by `EVAPORATION_RATE`
|
|
60
|
+
- `elusive?` — fluidity >= 0.7 and not captured
|
|
61
|
+
- `stable?` — fluidity < 0.4 or captured
|
|
62
|
+
- `vanishing?` — mass < 0.1
|
|
63
|
+
|
|
64
|
+
### `Helpers::Pool`
|
|
65
|
+
|
|
66
|
+
A surface that holds droplets.
|
|
67
|
+
|
|
68
|
+
- `add_droplet(id)` / `remove_droplet(id)` — adjusts depth by `DEPTH_CHANGE`
|
|
69
|
+
- `agitate!` — lowers surface tension, probabilistically releases droplets
|
|
70
|
+
- `settle!` — raises surface tension
|
|
71
|
+
- `reflective?` — depth >= 0.7 and surface_tension >= 0.5
|
|
72
|
+
- `shallow?` — depth < 0.2
|
|
73
|
+
|
|
74
|
+
### `Helpers::QuicksilverEngine`
|
|
75
|
+
|
|
76
|
+
Registry and coordinator for droplets and pools.
|
|
77
|
+
|
|
78
|
+
- `create_droplet(form:, content:, **)` — enforces MAX_DROPLETS
|
|
79
|
+
- `create_pool(surface_type:, **)` — enforces MAX_POOLS
|
|
80
|
+
- `shift_form / merge_droplets / split_droplet / capture_droplet / release_droplet`
|
|
81
|
+
- `add_to_pool(droplet_id:, pool_id:)`
|
|
82
|
+
- `agitate_pool(pool_id:)`
|
|
83
|
+
- `evaporate_all!` — evaporates all droplets, removes vanishing ones, returns removed IDs
|
|
84
|
+
- `quicksilver_report` — aggregate counts and averages
|
|
85
|
+
|
|
86
|
+
## Runners
|
|
87
|
+
|
|
88
|
+
Module: `Legion::Extensions::CognitiveQuicksilver::Runners::CognitiveQuicksilver`
|
|
89
|
+
|
|
90
|
+
| Runner | Key Args | Returns |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `create_droplet` | `form:`, `content:` | `{ success:, droplet: }` |
|
|
93
|
+
| `create_pool` | `surface_type:` | `{ success:, pool: }` |
|
|
94
|
+
| `shift_form` | `droplet_id:`, `new_form:` | `{ success:, droplet: }` |
|
|
95
|
+
| `merge` | `droplet_a_id:`, `droplet_b_id:` | `{ success:, droplet: }` |
|
|
96
|
+
| `split` | `droplet_id:` | `{ success:, original:, twin: }` or `{ success: false, error: }` |
|
|
97
|
+
| `capture` | `droplet_id:` | `{ success:, droplet: }` |
|
|
98
|
+
| `release` | `droplet_id:` | `{ success:, droplet: }` |
|
|
99
|
+
| `add_to_pool` | `droplet_id:`, `pool_id:` | `{ success:, pool: }` |
|
|
100
|
+
| `list_droplets` | — | `{ success:, droplets:, count: }` |
|
|
101
|
+
| `quicksilver_status` | — | aggregate report hash |
|
|
102
|
+
|
|
103
|
+
All runners accept optional `engine:` keyword to inject a test engine instance.
|
|
104
|
+
|
|
105
|
+
## Helpers
|
|
106
|
+
|
|
107
|
+
- `Constants#label_for(table, value)` — range-table lookup for `COHESION_LABELS` and `FLUIDITY_LABELS`
|
|
108
|
+
- `Droplet#cohesion_label` / `#fluidity_label` — delegates to `Constants.label_for`
|
|
109
|
+
|
|
110
|
+
## Integration Points
|
|
111
|
+
|
|
112
|
+
- No direct dependencies on other LegionIO extensions
|
|
113
|
+
- Designed as a standalone cognitive metaphor subsystem
|
|
114
|
+
- Can be wired into `lex-tick` phase handlers via `lex-cortex` if a fluidity signal is needed
|
|
115
|
+
- All state is in-memory per `QuicksilverEngine` instance; no persistence
|
|
116
|
+
|
|
117
|
+
## Development Notes
|
|
118
|
+
|
|
119
|
+
- The `engine:` keyword in each runner method allows test injection without mocking global state
|
|
120
|
+
- `split!` returns `nil` (not an error) when mass <= 0.2; runner handles this as `{ success: false, error: }`
|
|
121
|
+
- `merge!` deletes the absorbed droplet from the engine registry; only one object remains
|
|
122
|
+
- `evaporate_all!` is a batch operation; individual evaporation is only through direct Droplet calls
|
|
123
|
+
- No actors defined; this extension is driven entirely by external calls or task triggers
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# lex-cognitive-quicksilver
|
|
2
|
+
|
|
3
|
+
A LegionIO cognitive architecture extension that models mental fluidity using a mercury metaphor. Cognitive content flows, coalesces, splits, and evaporates as droplets of varying form and mass.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
Represents thought-units as **droplets** moving across cognitive surfaces. Droplets have:
|
|
8
|
+
|
|
9
|
+
- **Form**: liquid, droplet, bead, stream, or pool — each with different fluidity characteristics
|
|
10
|
+
- **Mass**: accumulates through merging, depletes through evaporation
|
|
11
|
+
- **Fluidity**: determines how easily the droplet moves; capture reduces fluidity, release restores it
|
|
12
|
+
|
|
13
|
+
Droplets can collect into **pools** on surfaces (glass, metal, wood, stone, fabric). Pools track depth and surface tension; agitating a pool releases some droplets.
|
|
14
|
+
|
|
15
|
+
This extension provides a low-level building block for modeling states of mental flow, dissociation, and cognitive cohesion.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
require 'lex-cognitive-quicksilver'
|
|
21
|
+
|
|
22
|
+
client = Legion::Extensions::CognitiveQuicksilver::Client.new
|
|
23
|
+
|
|
24
|
+
# Create a droplet
|
|
25
|
+
result = client.create_droplet(form: :droplet, content: 'working memory fragment')
|
|
26
|
+
droplet_id = result[:droplet][:id]
|
|
27
|
+
|
|
28
|
+
# Create a pool and add the droplet
|
|
29
|
+
pool_result = client.create_pool(surface_type: :glass)
|
|
30
|
+
client.add_to_pool(droplet_id: droplet_id, pool_id: pool_result[:pool][:id])
|
|
31
|
+
|
|
32
|
+
# Shift form and check state
|
|
33
|
+
client.shift_form(droplet_id: droplet_id, new_form: :stream)
|
|
34
|
+
client.quicksilver_status
|
|
35
|
+
# => { total_droplets: 1, total_pools: 1, elusive_count: 0, ... }
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Key Operations
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# Merge two droplets
|
|
42
|
+
client.merge(droplet_a_id: id1, droplet_b_id: id2)
|
|
43
|
+
|
|
44
|
+
# Split a droplet in half (requires mass > 0.2)
|
|
45
|
+
client.split(droplet_id: id)
|
|
46
|
+
|
|
47
|
+
# Capture a droplet (reduces fluidity)
|
|
48
|
+
client.capture(droplet_id: id)
|
|
49
|
+
|
|
50
|
+
# Release it again
|
|
51
|
+
client.release(droplet_id: id)
|
|
52
|
+
|
|
53
|
+
# List all droplets
|
|
54
|
+
client.list_droplets
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Development
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bundle install
|
|
61
|
+
bundle exec rspec
|
|
62
|
+
bundle exec rubocop
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
MIT
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/cognitive_quicksilver/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-cognitive-quicksilver'
|
|
7
|
+
spec.version = Legion::Extensions::CognitiveQuicksilver::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Cognitive Quicksilver'
|
|
12
|
+
spec.description = 'Cognitive quicksilver fluidity for LegionIO agentic architecture'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-cognitive-quicksilver'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 3.4'
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-cognitive-quicksilver'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-cognitive-quicksilver'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-cognitive-quicksilver'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-cognitive-quicksilver/issues'
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
23
|
+
|
|
24
|
+
spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") }
|
|
25
|
+
spec.require_paths = ['lib']
|
|
26
|
+
spec.add_development_dependency 'legion-gaia'
|
|
27
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/cognitive_quicksilver/helpers/constants'
|
|
4
|
+
require 'legion/extensions/cognitive_quicksilver/helpers/droplet'
|
|
5
|
+
require 'legion/extensions/cognitive_quicksilver/helpers/pool'
|
|
6
|
+
require 'legion/extensions/cognitive_quicksilver/helpers/quicksilver_engine'
|
|
7
|
+
require 'legion/extensions/cognitive_quicksilver/runners/cognitive_quicksilver'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module CognitiveQuicksilver
|
|
12
|
+
class Client
|
|
13
|
+
include Runners::CognitiveQuicksilver
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@quicksilver_engine = Helpers::QuicksilverEngine.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :quicksilver_engine
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveQuicksilver
|
|
6
|
+
module Helpers
|
|
7
|
+
module Constants
|
|
8
|
+
FORM_TYPES = %i[liquid droplet bead stream pool].freeze
|
|
9
|
+
SURFACE_TYPES = %i[glass metal wood stone fabric].freeze
|
|
10
|
+
|
|
11
|
+
MAX_DROPLETS = 500
|
|
12
|
+
MAX_POOLS = 50
|
|
13
|
+
|
|
14
|
+
FLUIDITY_BASE = 0.8
|
|
15
|
+
SURFACE_TENSION = 0.3
|
|
16
|
+
EVAPORATION_RATE = 0.02
|
|
17
|
+
COALESCENCE_BONUS = 0.1
|
|
18
|
+
|
|
19
|
+
COHESION_LABELS = [
|
|
20
|
+
{ range: (0.8..1.0), label: :unified },
|
|
21
|
+
{ range: (0.6...0.8), label: :cohesive },
|
|
22
|
+
{ range: (0.4...0.6), label: :scattered },
|
|
23
|
+
{ range: (0.2...0.4), label: :dispersed },
|
|
24
|
+
{ range: (0.0...0.2), label: :atomized }
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
FLUIDITY_LABELS = [
|
|
28
|
+
{ range: (0.8..1.0), label: :liquid },
|
|
29
|
+
{ range: (0.6...0.8), label: :flowing },
|
|
30
|
+
{ range: (0.4...0.6), label: :viscous },
|
|
31
|
+
{ range: (0.2...0.4), label: :sluggish },
|
|
32
|
+
{ range: (0.0...0.2), label: :solid }
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
module_function
|
|
36
|
+
|
|
37
|
+
def label_for(table, value)
|
|
38
|
+
clamped = value.clamp(0.0, 1.0)
|
|
39
|
+
entry = table.find { |row| row[:range].include?(clamped) }
|
|
40
|
+
entry ? entry[:label] : table.last[:label]
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CognitiveQuicksilver
|
|
8
|
+
module Helpers
|
|
9
|
+
class Droplet
|
|
10
|
+
attr_reader :id, :form, :content, :mass, :fluidity, :surface, :captured, :created_at
|
|
11
|
+
|
|
12
|
+
FORM_FLUIDITY = {
|
|
13
|
+
liquid: 0.9,
|
|
14
|
+
droplet: 0.8,
|
|
15
|
+
bead: 0.7,
|
|
16
|
+
stream: 0.6,
|
|
17
|
+
pool: 0.3
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
def initialize(form:, content:, mass: 0.3, fluidity: Constants::FLUIDITY_BASE, surface: :glass)
|
|
21
|
+
raise ArgumentError, "invalid form: #{form}" unless Constants::FORM_TYPES.include?(form)
|
|
22
|
+
raise ArgumentError, "invalid surface: #{surface}" unless Constants::SURFACE_TYPES.include?(surface)
|
|
23
|
+
|
|
24
|
+
@id = SecureRandom.uuid
|
|
25
|
+
@form = form
|
|
26
|
+
@content = content
|
|
27
|
+
@mass = mass.clamp(0.0, 1.0)
|
|
28
|
+
@fluidity = fluidity.clamp(0.0, 1.0)
|
|
29
|
+
@surface = surface
|
|
30
|
+
@captured = false
|
|
31
|
+
@created_at = Time.now.utc
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def shift_form!(new_form)
|
|
35
|
+
raise ArgumentError, "invalid form: #{new_form}" unless Constants::FORM_TYPES.include?(new_form)
|
|
36
|
+
|
|
37
|
+
@form = new_form
|
|
38
|
+
@fluidity = FORM_FLUIDITY.fetch(new_form, Constants::FLUIDITY_BASE).clamp(0.0, 1.0)
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def merge!(other_droplet)
|
|
43
|
+
@mass = (@mass + other_droplet.mass + Constants::COALESCENCE_BONUS).clamp(0.0, 1.0)
|
|
44
|
+
@form = @mass >= other_droplet.mass ? @form : other_droplet.form
|
|
45
|
+
@fluidity = ((@fluidity + other_droplet.fluidity) / 2.0).round(10)
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def split!
|
|
50
|
+
return nil if @mass <= 0.2
|
|
51
|
+
|
|
52
|
+
half_mass = (@mass / 2.0).round(10)
|
|
53
|
+
@mass = half_mass
|
|
54
|
+
|
|
55
|
+
self.class.new(
|
|
56
|
+
form: @form,
|
|
57
|
+
content: @content,
|
|
58
|
+
mass: half_mass,
|
|
59
|
+
fluidity: @fluidity,
|
|
60
|
+
surface: @surface
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def capture!
|
|
65
|
+
@captured = true
|
|
66
|
+
@fluidity = (@fluidity / 2.0).round(10)
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def release!
|
|
71
|
+
@captured = false
|
|
72
|
+
@fluidity = (@fluidity * 2.0).clamp(0.0, 1.0)
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def evaporate!
|
|
77
|
+
@mass = (@mass - Constants::EVAPORATION_RATE).clamp(0.0, 1.0).round(10)
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def elusive?
|
|
82
|
+
@fluidity >= 0.7 && !@captured
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def stable?
|
|
86
|
+
@fluidity < 0.4 || @captured
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def vanishing?
|
|
90
|
+
@mass < 0.1
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def cohesion_label
|
|
94
|
+
Constants.label_for(Constants::COHESION_LABELS, @mass)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def fluidity_label
|
|
98
|
+
Constants.label_for(Constants::FLUIDITY_LABELS, @fluidity)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def to_h
|
|
102
|
+
{
|
|
103
|
+
id: @id,
|
|
104
|
+
form: @form,
|
|
105
|
+
content: @content,
|
|
106
|
+
mass: @mass.round(10),
|
|
107
|
+
fluidity: @fluidity.round(10),
|
|
108
|
+
surface: @surface,
|
|
109
|
+
captured: @captured,
|
|
110
|
+
elusive: elusive?,
|
|
111
|
+
stable: stable?,
|
|
112
|
+
vanishing: vanishing?,
|
|
113
|
+
cohesion: cohesion_label,
|
|
114
|
+
fluidity_lbl: fluidity_label,
|
|
115
|
+
created_at: @created_at
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module CognitiveQuicksilver
|
|
8
|
+
module Helpers
|
|
9
|
+
class Pool
|
|
10
|
+
attr_reader :id, :surface_type, :depth, :droplet_ids, :surface_tension, :created_at
|
|
11
|
+
|
|
12
|
+
DEPTH_CHANGE = 0.05
|
|
13
|
+
TENSION_DECREASE = 0.1
|
|
14
|
+
TENSION_INCREASE = 0.05
|
|
15
|
+
|
|
16
|
+
def initialize(surface_type:, depth: 0.5, surface_tension: Constants::SURFACE_TENSION)
|
|
17
|
+
raise ArgumentError, "invalid surface_type: #{surface_type}" unless Constants::SURFACE_TYPES.include?(surface_type)
|
|
18
|
+
|
|
19
|
+
@id = SecureRandom.uuid
|
|
20
|
+
@surface_type = surface_type
|
|
21
|
+
@depth = depth.clamp(0.0, 1.0)
|
|
22
|
+
@droplet_ids = []
|
|
23
|
+
@surface_tension = surface_tension.clamp(0.0, 1.0)
|
|
24
|
+
@created_at = Time.now.utc
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_droplet(droplet_id)
|
|
28
|
+
@droplet_ids << droplet_id unless @droplet_ids.include?(droplet_id)
|
|
29
|
+
@depth = (@depth + DEPTH_CHANGE).clamp(0.0, 1.0).round(10)
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def remove_droplet(droplet_id)
|
|
34
|
+
@droplet_ids.delete(droplet_id)
|
|
35
|
+
@depth = (@depth - DEPTH_CHANGE).clamp(0.0, 1.0).round(10)
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def agitate!
|
|
40
|
+
@surface_tension = (@surface_tension - TENSION_DECREASE).clamp(0.0, 1.0).round(10)
|
|
41
|
+
released = []
|
|
42
|
+
@droplet_ids.each do |did|
|
|
43
|
+
released << did if rand > @surface_tension
|
|
44
|
+
end
|
|
45
|
+
released.each { |did| remove_droplet(did) }
|
|
46
|
+
released
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def settle!
|
|
50
|
+
@surface_tension = (@surface_tension + TENSION_INCREASE).clamp(0.0, 1.0).round(10)
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def reflective?
|
|
55
|
+
@depth >= 0.7 && @surface_tension >= 0.5
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def shallow?
|
|
59
|
+
@depth < 0.2
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_h
|
|
63
|
+
{
|
|
64
|
+
id: @id,
|
|
65
|
+
surface_type: @surface_type,
|
|
66
|
+
depth: @depth.round(10),
|
|
67
|
+
droplet_ids: @droplet_ids.dup,
|
|
68
|
+
droplet_count: @droplet_ids.size,
|
|
69
|
+
surface_tension: @surface_tension.round(10),
|
|
70
|
+
reflective: reflective?,
|
|
71
|
+
shallow: shallow?,
|
|
72
|
+
created_at: @created_at
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|