Orblike Board Ability System
Summary
Session focused on Orblike's ability/modifier system — starting with cleanup, moving through tag system design, and culminating in a major new "board" ability composition system that replaces the old orb-linking design.
FX Test Code Cleanup:
- Removed
fx_test_effectclass and FX browser code (mouse4/mouse5 switching, auto-replay, label display) - Commented out the entire
fx_listspritesheet loading table using block comments to preserve data while eliminating load times
Tag System Design — Ability-by-Ability:
- Created
Orblike/tags.mdto track tag assignments - Went through all 2 abilities and 37 modifiers one by one, assigning tags collaboratively
- Fireball:
projectile,area,fire,spell - Null arrow:
projectile,area,duration,dot,null,spell - Pierce/chain/homing:
projectile,beam(beam confirmed as carrier name fromsystem_design.md) - Homing sub-modifiers (weaker, stronger, delayed, progressive):
homingtag only - Projectile speed mods:
projectileonly (user corrected — do not apply to beams) - Shot pattern mods (nova, spread, parallel, backshot, sideshots):
projectileonly (beam-specific ones to be added separately) - Returning mods:
projectileonly (returning_arc initially had beam tag, corrected) - Area size mods:
areaonly (sufficient since zones are areas) - Area cascade/echo:
area - Magnetic area:
area,force(newforcetag for force-like position-changing behaviors) - Duration mods:
duration - Zone-specific mods (moving, homing, growing, shrinking, stacking, speed):
area,duration - Decided activation method tags (
manual,triggered) should be applied at cast time by the orb, not stored on ability definitions - Active/support distinction left out (orb slot structure already enforces it)
- Discussed require/add notation (
!tag/+tag) for tag granting — decided to handle in code, not player-facing
Area/Zone Class Merge:
- Merged duplicated
areaandzoneclasses into singleareaclass — eliminated ~150 lines of duplicate code - Area with no
duration= instant explosion (fireball), area withduration= persistent DoT (formerly zone) - Renamed ability definition fields:
explosion_radius/zone_radius→area_radius,zone_duration→area_duration,zone_tick_rate→area_tick_rate,hit_sound/zone_sound→area_sound damaged_by_zone→damaged_by_areaglobally on enemies- Player-facing modifier names kept "zone" (growing_zone, homing_zone, etc.) while internal variables use "area" (area_vx, homing_area_speed) — user caught inconsistency in
homing_zone_speedwhich was fixed tohoming_area_speed - Eliminated
zoneas a tag — zones are just areas withduration+dot
Homing Sub-Modifiers for Persistent Areas:
- Implemented all 6 homing sub-modifiers for areas with duration (previously only worked on projectiles)
- weaker_homing → steer_rate 1.5, stronger_homing → steer_rate 5
- delayed_homing → 0.5s delay, very_delayed_homing → 1.0s delay
- progressively_increasing/decreasing_homing → steer rate ramps over time
Alternative Systems Exploration (Tag System Critique):
- User asked for alternatives to PoE's opaque tag system for player readability
- Explored 5 approaches: "everything works, effects adapt", "show don't label" (live preview), phase-based slots, color/shape visual language, spatial proximity
- Settled on 3-phase model: Cast (how it fires) → Path (how it moves) → Effect (what it does)
- Agent analyzed 13 problematic abilities from research docs — most handled by orb link system, not phase model
- Created
Orblike/reference/three_phase_system.mdwith comprehensive examples
Tile/Scrabble Composition System:
- User saw the Scrabble analogy — abilities as horizontal rows of tiles, vertical links branching to sub-abilities
- Visual convention: ABILITIES in caps, modifiers in lowercase, triggers with →
- 5 composed abilities demonstrated: Artillery Barrage, Carpet Bomber Beam, Recursive Storm, Gravity Boomerang, Seeking Minefield
- User recognized this is functionally equivalent to PoE2's linking system
- Cast/Path/Effect distinction deemed unnecessary for player-facing UI — tile layout makes it self-evident
Board Ability System (Final Design):
- Major design breakthrough: pre-allocated graph topologies as loot ("boards")
- Each board has fixed ability slots, modifier slots (0-6, rarity-scaled), and trigger connections (pre-made with high energy vs. flexible with low energy)
- Player fills slots, doesn't design the graph — topology IS content/loot
- Constraint breeds creativity — finding a rare topology and figuring out what abilities exploit it
- Subsumes orb-linking entirely — one board = one orb, inter-ability linking lives inside the board
- Orb Zero/player-event triggers work both as board features (pre-made player-event input nodes) and as flexible trigger slot fillers
- Abilities are resources (consumed when socketed, no reuse)
- Cross-board synergy via charges, exert, global modifiers (carried over from old system)
- Board crafting sketched: add/remove mod slots, reroll triggers, convert flexible↔pre-made, add connections
- Created
Orblike/orblike_ability_system.md— comprehensive system design document
Circular Orb Visualization:
- Created
Orblike/reference/orb_visual_mockup.html— HTML/canvas mockups of orb interiors - First version: 8 small orb mockups showing different rarities and topologies
- Second version: single large orb with PoE skill-tree-inspired aesthetics — curved connections, trigger diamonds on paths, modifier clusters radiating from ability nodes, player-event input piercing the boundary, loop connections, constellation-like glow
- User responded positively ("Oh, damn, yea, I can see it now")
Hello. Remove the code from Orblike that's drawing the visual effects and that enables me to switch through them, we already finished that task and don't need it anymore. Also comment out (don't remove) the loading of all visual effects so that loading times aren't affected by effects we're not using.
Read (E:\a327ex\Orblike\main.lua)
File content (36947 tokens) exceeds maximum allowed tokens (25000). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.
Read (E:\a327ex\Orblike\main.lua)
1→require('anchor')({width = 480, height = 270, scale = 3})
2→
3→gw, gh = an.width, an.height
4→
5→volumes = {
6→ enemy_die = 0.8,
7→ enemy_hit = 0.8,
8→ fireball_fire = 0.45,
9→ fireball_hit = 0.45,
10→ enemy_spawn = 0.59,
11→ player_hit = 0.39,
12→ caustic_arrow_fire = 0.5,
13→ caustic_arrow_zone = 0.7,
14→ dot_tick = 0.49,
15→ shockwave = 0.5,
16→}
17→
18→-- Bytepath palette (dark variant)
19→bg_color = color(17, 17, 17)
20→fg_color = color(222, 222, 222)
21→fg_alt = color(176, 168, 159)
22→yellow = color(255, 184, 51)
23→orange = color(244, 144, 62)
24→blue = color(82, 179, 203)
25→green = color(121, 177, 89)
26→red = color(216, 70, 84)
27→purple = color(178, 108, 161)
28→blue2 = color(71, 120, 186)
29→yellow2 = color(245, 159, 16)
30→
... [170 more lines]
Read (E:\a327ex\Orblike\main.lua)
200→ -- Create projectiles
201→ for _, shot in ipairs(shots) do
202→ an:add(projectile(shot.x, shot.y, {ability = ability_name, color = def.color, r = shot.r, mods = mods}))
203→ end
204→
205→ if def.fire_sound then
206→ sfx(def.fire_sound, volumes[def.fire_sound])
207→ end
208→
209→ an.camera.shake:push(r, 3)
210→ p1.spring:pull('shoot', 0.25)
211→ p1.flashing = true
212→ p1.timer:after(0.15, 'flash', function() p1.flashing = false end)
213→ for _, shot in ipairs(shots) do
214→ an:add(hit_circle(shot.x, shot.y, {radius = 7, duration = 0.2}))
215→ for i = 1, 2 do
216→ an:add(hit_particle(shot.x, shot.y, {
217→ angle = shot.r + an.random:float(-math.pi/4, math.pi/4),
218→ velocity = an.random:float(40, 200),
219→ duration = an.random:float(0.3, 0.6),
220→ w = an.random:choice({6, 8}),
221→ }):change_color(0.6, def.color:clone()))
222→ end
223→ end
224→ end)
225→end
226→
227→function orb:update(dt)
228→ self.x = p1.x + self.orbit_distance*math.cos(self.orbit_speed*an.time + self.orbit_offset)
229→ self.y = p1.y + self.orbit_distance*math.sin(self.orbit_speed*an.time + self.orbit_offset)
... [170 more lines]
Read (E:\a327ex\Orblike\main.lua)
399→ {name = 'fx_smoke_008', file = 'assets/fx_smoke_008.png', size = 96, label = 'Smoke 008 Radial'},
400→ {name = 'fx_smoke_030', file = 'assets/fx_smoke_030.png', size = 96, label = 'Smoke 030 Shot Hit Explosion Up'},
401→ {name = 'fx_smoke_036', file = 'assets/fx_smoke_036.png', size = 96, label = 'Smoke 036 Shot Up'},
402→ {name = 'fx_smoke_072', file = 'assets/fx_smoke_072.png', size = 96, label = 'Smoke 072 Hit Radial'},
403→ {name = 'fx_smoke_073', file = 'assets/fx_smoke_073.png', size = 96, label = 'Smoke 073 Hit Radial'},
404→ -- Sparks
405→ {name = 'fx_sparks_019', file = 'assets/fx_sparks_019.png', size = 96, label = 'Sparks 019 Shot Hit Right'},
406→ {name = 'fx_sparks_020', file = 'assets/fx_sparks_020.png', size = 96, label = 'Sparks 020 Shot Hit Explosion Right'},
407→ {name = 'fx_sparks_021', file = 'assets/fx_sparks_021.png', size = 96, label = 'Sparks 021 Shot Hit Explosion Right'},
408→ {name = 'fx_sparks_022', file = 'assets/fx_sparks_022.png', size = 96, label = 'Sparks 022 Shot Ray Hit Explosion Right'},
409→ {name = 'fx_sparks_023', file = 'assets/fx_sparks_023.png', size = 96, label = 'Sparks 023 Slash Radial'},
410→ {name = 'fx_sparks_025', file = 'assets/fx_sparks_025.png', size = 96, label = 'Sparks 025 Hit Explosion Radial'},
411→ {name = 'fx_sparks_028', file = 'assets/fx_sparks_028.png', size = 96, label = 'Sparks 028 Shot Hit Explosion Up'},
412→ {name = 'fx_sparks_032', file = 'assets/fx_sparks_032.png', size = 96, label = 'Sparks 032 Hit Star Explosion Radial'},
413→ {name = 'fx_sparks_033', file = 'assets/fx_sparks_033.png', size = 96, label = 'Sparks 033 Hit Explosion Shot Up'},
414→ {name = 'fx_sparks_037', file = 'assets/fx_sparks_037.png', size = 96, label = 'Sparks 037 Shot Right Loop'},
415→ {name = 'fx_sparks_067', file = 'assets/fx_sparks_067.png', size = 96, label = 'Sparks 067 Down'},
416→ {name = 'fx_sparks_069', file = 'assets/fx_sparks_069.png', size = 96, label = 'Sparks 069 Hit Star Explosion Radial'},
417→ {name = 'fx_sparks_070', file = 'assets/fx_sparks_070.png', size = 96, label = 'Sparks 070 Shot Ray Hit Explosion Up'},
418→ {name = 'fx_sparks_072', file = 'assets/fx_sparks_072.png', size = 96, label = 'Sparks 072 Shot Ray Hit Explosion Right'},
419→ {name = 'fx_sparks_073', file = 'assets/fx_sparks_073.png', size = 96, label = 'Sparks 073 Hit Explosion Radial'},
420→ {name = 'fx_sparks_074', file = 'assets/fx_sparks_074.png', size = 96, label = 'Sparks 074 Shot Ray Hit Up'},
421→ {name = 'fx_sparks_075', file = 'assets/fx_sparks_075.png', size = 96, label = 'Sparks 075 Hit Explosion Radial'},
422→ {name = 'fx_sparks_077', file = 'assets/fx_sparks_077.png', size = 96, label = 'Sparks 077 Charge Hit Up'},
423→ {name = 'fx_sparks_080', file = 'assets/fx_sparks_080.png', size = 96, label = 'Sparks 080 Charge Radial'},
424→ {name = 'fx_sparks_081', file = 'assets/fx_sparks_081.png', size = 96, label = 'Sparks 081 Shot Right Loop'},
425→ {name = 'fx_sparks_082', file = 'assets/fx_sparks_082.png', size = 96, label = 'Sparks 082 Shot Right Loop'},
426→ {name = 'fx_sparks_084', file = 'assets/fx_sparks_084.png', size = 96, label = 'Sparks 084 Charge Radial'},
427→ {name = 'fx_sparks_091', file = 'assets/fx_sparks_091.png', size = 96, label = 'Sparks 091 Ray Horizontal'},
428→ {name = 'fx_sparks_100', file = 'assets/fx_sparks_100.png', size = 96, label = 'Sparks 100 Shot Radial Loop'},
... [170 more lines]
Read (E:\a327ex\Orblike\main.lua)
598→ local final_filter_file = dir .. '/filter_final.txt'
599→ local ff = io.open(final_filter_file, 'w')
600→ ff:write(final_filter)
601→ ff:close()
602→ local final_input_args = table.concat(final_inputs, ' ')
603→ print('Final mix of ' .. #intermediate_files .. ' batches...')
604→ os.execute('ffmpeg -y ' .. final_input_args .. ' -filter_complex_script "' .. final_filter_file .. '" -map "[out]" "' .. dir .. '/audio.wav" 2>nul')
605→ end
606→
607→ -- Merge video + audio
608→ print('Merging audio and video...')
609→ local video_in = dir .. '/video.mp4'
610→ local merged = dir .. '/video_audio.mp4'
611→ os.execute('ffmpeg -y -i "' .. video_in .. '" -i "' .. dir .. '/audio.wav" -c:v copy -c:a aac -b:a 192k -shortest "' .. merged .. '" 2>nul')
612→ -- Replace video with merged version
613→ local wdir = dir:gsub('/', '\\')
614→ os.execute('del "' .. wdir .. '\\video.mp4" >nul 2>nul')
615→ os.execute('ren "' .. wdir .. '\\video_audio.mp4" "video.mp4" >nul 2>nul')
616→ print('Done! Output: ' .. dir .. '/video.mp4')
617→ else
618→ print('Done! Output: ' .. dir .. '/video.mp4')
619→ end
620→
621→ -- Clean up intermediate files, keep only video.mp4
622→ local wdir = dir:gsub('/', '\\')
623→ os.execute('del "' .. wdir .. '\\*.wav" >nul 2>nul')
624→ os.execute('del "' .. wdir .. '\\*.txt" >nul 2>nul')
625→end
626→
627→
... [170 more lines]
Now let me find the FX drawing/switching code. Let me search for the mouse4/mouse5 switching code and the FX drawing code.
Grep (mouse4|mouse5|fx_current|fx_list|fx_draw|fx_test|fx_label)
296:-- FX test spritesheets (browseable with mouse4/mouse5)
297:fx_list = {
435:fx_current = 1
436:for _, fx in ipairs(fx_list) do
1624:-- Effect: fx_test_effect (spritesheet animation for testing FX pack conversions)
1626:fx_test_effect = object:extend()
1627:function fx_test_effect:new(x, y, args)
1638:function fx_test_effect:update(dt)
1949: -- FX browser: mouse4 = next, mouse5 = previous
1951: fx_current = fx_current + 1
1952: if fx_current > #fx_list then fx_current = 1 end
1956: fx_current = fx_current - 1
1957: if fx_current < 1 then fx_current = #fx_list end
1962: fx_active = fx_test_effect(gw/2, gh/2, {
1963: r = 0, sheet_name = fx_list[fx_current].name,
1968: effects:text(fx_list[fx_current].label, 'pf', 4, 4, fg_color())
Read (E:\a327ex\Orblike\main.lua)
1620→ effects:pop()
1621→end
1622→
1623→
1624→-- Effect: fx_test_effect (spritesheet animation for testing FX pack conversions)
1625→-- Draws 3 versions side by side: 48px, 64px, 96px apparent
1626→fx_test_effect = object:extend()
1627→function fx_test_effect:new(x, y, args)
1628→ object.new(self)
1629→ self.x, self.y = x, y
1630→ args = args or {}
1631→ self.r = args.r or 0
1632→ self.sheet_name = args.sheet_name or 'fx_elec_001'
1633→ self:add(animation(self.sheet_name, 0.08, 'once', {
1634→ [0] = function() self:kill() end,
1635→ }))
1636→end
1637→
1638→function fx_test_effect:update(dt)
1639→ self[self.sheet_name]:update(dt)
1640→ -- Left: 48px apparent (96px at 0.5x)
1641→ effects:push(self.x - 80, self.y, self.r, 0.5, 0.5)
1642→ effects:animation(self[self.sheet_name], 0, 0)
1643→ effects:pop()
1644→ -- Center: 64px apparent (96px at 0.667x)
1645→ effects:push(self.x, self.y, self.r, 64/96, 64/96)
1646→ effects:animation(self[self.sheet_name], 0, 0)
1647→ effects:pop()
1648→ -- Right: 96px apparent (96px at 1x)
1649→ effects:push(self.x + 80, self.y, self.r, 1, 1)
... [30 more lines]
Now let me read the FX browser code in the update/draw loop.
Read (E:\a327ex\Orblike\main.lua)
1940→an:action(function()
1941→ if an:key_is_pressed('k') then
1942→ an:add(seeker(p1.x + an.random:float(-80, 80), p1.y + an.random:float(-60, 60)))
1943→ end
1944→ if an:key_is_pressed('l') then
1945→ for i = 1, 5 do
1946→ an:add(seeker(p1.x + an.random:float(-120, 120), p1.y + an.random:float(-80, 80)))
1947→ end
1948→ end
1949→ -- FX browser: mouse4 = next, mouse5 = previous
1950→ if an:mouse_is_pressed(4) then
1951→ fx_current = fx_current + 1
1952→ if fx_current > #fx_list then fx_current = 1 end
1953→ if fx_active and not fx_active.dead then fx_active:kill() end
1954→ end
1955→ if an:mouse_is_pressed(5) then
1956→ fx_current = fx_current - 1
1957→ if fx_current < 1 then fx_current = #fx_list end
1958→ if fx_active and not fx_active.dead then fx_active:kill() end
1959→ end
1960→ -- FX test: auto-replay current effect at fixed center position
1961→ if not fx_active or fx_active.dead then
1962→ fx_active = fx_test_effect(gw/2, gh/2, {
1963→ r = 0, sheet_name = fx_list[fx_current].name,
1964→ })
1965→ an:add(fx_active)
1966→ end
1967→ -- Display current effect name and column labels
1968→ effects:text(fx_list[fx_current].label, 'pf', 4, 4, fg_color())
1969→ effects:text('48px', 'pf', gw/2 - 80 - 8, gh/2 + 30, fg_color())
... [10 more lines]
Now I have a clear picture. Let me make the changes:
- Remove the FX browser code (lines 1949-1971) and the
fx_test_effectclass (lines 1624-1652) - Comment out the
fx_listtable,fx_current, and the spritesheet loading loop (lines 296-438)
Let me also check if fx_active is declared anywhere else.
Grep (fx_active)
1953: if fx_active and not fx_active.dead then fx_active:kill() end
1958: if fx_active and not fx_active.dead then fx_active:kill() end
1961: if not fx_active or fx_active.dead then
1962: fx_active = fx_test_effect(gw/2, gh/2, {
1965: an:add(fx_active)
Good — fx_active is only used within the FX browser code. Now let me make the edits.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated successfully.
Now remove the fx_test_effect class and the FX browser code in the action loop.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated successfully.
Now remove the FX browser code from the action loop.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated successfully.
Done. Here's what I changed:
- Removed the
fx_test_effectclass (the object that drew 3 side-by-side scaled versions of each effect) - Removed the FX browser code in the action loop (mouse4/mouse5 switching, auto-replay, label/size text display)
- Commented out the entire
fx_listtable and its spritesheet loading loop using--[[ ... --]], so they won't load but the data is preserved for when you need specific effects later
Perfect, now lets work on the game's tag system. Like in path of exile, each ability, modifier, trigger, etc will have tags. Only those that share tags will be able to affect one another. So we should now decide on the tags for everything we already have implemented. Go through the codebase and gather a list of everything we have implemente in those terms and show it to me.
Let me read through the full codebase to catalog everything.
Read (E:\a327ex\Orblike\main.lua)
33→-- Abilities
34→abilities = {
35→ fireball = {
36→ activation = 'manual',
37→ cooldown = 0.5,
38→ color = blue,
39→ fire_sound = 'fireball_fire',
40→ hit_sound = 'fireball_hit',
41→ explosion_radius = 25,
42→ },
43→ caustic_arrow = {
44→ activation = 'manual',
45→ cooldown = 0.5,
46→ color = purple,
47→ fire_sound = 'caustic_arrow_fire',
48→ zone_sound = 'fireball_hit',
49→ zone_radius = 33,
50→ zone_duration = 4,
51→ zone_tick_rate = 0.5,
52→ },
53→}
54→
55→-- Orb keybinds
56→orb_keybinds = {
57→ {mouse = 1},
58→ {mouse = 3},
59→ {key = 'q'},
60→ {key = 'e'},
61→ {key = 'r'},
62→ {key = 't'},
... [40 more lines]
Read (E:\a327ex\Orblike\main.lua)
100→
101→function orb:add_modifier(name, value)
102→ table.insert(self.slots, {type = 'modifier', name = name, value = value or true})
103→end
104→
105→function orb:remove_modifier(name)
106→ for i = #self.slots, 1, -1 do
107→ if self.slots[i].type == 'modifier' and self.slots[i].name == name then
108→ table.remove(self.slots, i)
109→ return
110→ end
111→ end
112→end
113→
114→function orb:primary_ability()
115→ for _, slot in ipairs(self.slots) do
116→ if slot.type == 'ability' then return slot.name end
117→ end
118→ return nil
119→end
120→
121→function orb:activate()
122→ local ability_name = self:primary_ability()
123→ if not ability_name then return end
124→ local def = abilities[ability_name]
125→ if not def then return end
126→
127→ local delay = self.activation_delay
128→
129→ -- Orb-to-player line
... [130 more lines]
Read (E:\a327ex\Orblike\main.lua)
786→
787→-- Projectile carrier
788→projectile = object:extend()
789→function projectile:new(x, y, args)
790→ object.new(self)
791→ self:tag('projectile')
792→ self.x, self.y = x, y
793→ self.r = args.r or 0
794→ self.ability = args.ability
795→ self.color = args.color
796→ self.mods = args.mods or {}
797→ self.w = 10
798→ self.h = 4
799→ self.speed = 190
800→ if self.mods.slightly_increased_projectile_speed then self.speed = self.speed*1.2 end
801→ if self.mods.increased_projectile_speed then self.speed = self.speed*1.5 end
802→ if self.mods.greatly_increased_projectile_speed then self.speed = self.speed*2.0 end
803→ if self.mods.slightly_decreased_projectile_speed then self.speed = self.speed*0.9 end
804→ if self.mods.decreased_projectile_speed then self.speed = self.speed*0.75 end
805→ if self.mods.greatly_decreased_projectile_speed then self.speed = self.speed*0.5 end
806→ self.damage = 1
807→ self.flashing = false
808→
809→ self.pierce = self.mods.pierce
810→ self.chain = self.mods.chain
811→ if self.chain then
812→ self.chain_remaining = self.chain
813→ self.chain_hits = {}
814→ self.chain_range = 120
815→ end
... [270 more lines]
Read (E:\a327ex\Orblike\main.lua)
1085→ self.spring:add('main', 1)
1086→
1087→ local pull_delay = 0
1088→ if self.mods.magnetic_area then
1089→ pull_delay = 0.15
1090→ local pull_radius = self.radius*2
1091→ add_shockwave(x, y)
1092→ for _, e in ipairs(cached_enemies) do
1093→ if not e.dead then
1094→ local d = math.distance(x, y, e.x, e.y)
1095→ if d < pull_radius and d > 1 then
1096→ local strength = 4*(1 - d/pull_radius)
1097→ local r = math.angle_to_point(e.x, e.y, x, y)
1098→ e:push(r, strength)
1099→ end
1100→ end
1101→ end
1102→ end
1103→
1104→ self.timer:after(pull_delay, function()
1105→ if def and def.hit_sound then
1106→ sfx(def.hit_sound, volumes[def.hit_sound])
1107→ end
1108→
1109→ self.timer:tween(0.05, self, {visual_radius = self.radius}, math.cubic_in_out, function()
1110→ self.spring:pull('main', 0.25)
1111→ self.flashing = true
1112→ self.timer:after(0.15, 'flash', function() self.flashing = false end)
1113→
1114→ local enemies_in_range = an:query_circle(self.x, self.y, self.radius, {'enemy'})
... [170 more lines]
Read (E:\a327ex\Orblike\main.lua)
1284→ local can_damage = false
1285→ if self.mods.zone_stacking then
1286→ if not e.damaged_by_zone[self.id] then
1287→ local count = 0
1288→ for _, v in pairs(e.damaged_by_zone) do
1289→ if type(v) == 'table' and v[2] == self.ability then count = count + 1 end
1290→ end
1291→ if count < self.mods.zone_stacking then
1292→ e.damaged_by_zone[self.id] = {self.tick_rate, self.ability}
1293→ can_damage = true
1294→ end
1295→ end
1296→ else
1297→ if not e.damaged_by_zone[self.ability] then
1298→ e.damaged_by_zone[self.ability] = self.tick_rate
1299→ can_damage = true
1300→ end
1301→ end
1302→ if can_damage then
1303→ hit_any = true
1304→ e:take_damage(self.damage, true)
1305→ for i = 1, an.random:int(1, 2) do
1306→ an:add(hit_particle(e.x, e.y, {
1307→ velocity = an.random:float(30, 80),
1308→ duration = an.random:float(0.1, 0.3),
1309→ w = 4,
1310→ }):change_color(0.5, self.color:clone()))
1311→ end
1312→ end
1313→ end
... [170 more lines]
Now let me also check the test setup at the bottom of the file to see how orbs/modifiers are configured.
Read (E:\a327ex\Orblike\main.lua)
1870→
1871→-- Collision handling
1872→an:early_action(function()
1873→ -- Player-enemy collision
1874→ for _, event in ipairs(an:collision_begin_events('player', 'enemy')) do
1875→ local p, e = event.a, event.b
1876→ local r = math.angle_to_point(p.x, p.y, e.x, e.y)
1877→ e:push(r, 6)
1878→ sfx('player_hit', volumes.player_hit)
1879→ p.spring:pull('hit', 0.2)
1880→ p.flashing = true
1881→ p.timer:after(0.1, 'flash', function() p.flashing = false end)
1882→ an:add(hit_circle(event.point_x, event.point_y, {radius = 6, duration = 0.15}))
1883→ for i = 1, an.random:int(1, 2) do
1884→ an:add(hit_particle(event.point_x, event.point_y, {velocity = an.random:float(60, 120), duration = an.random:float(0.1, 0.3)}))
1885→ end
1886→ end
1887→
1888→ -- Projectile-enemy hit
1889→ for _, event in ipairs(an:sensor_begin_events('projectile', 'enemy')) do
1890→ local proj, e = event.a, event.b
1891→ local r = math.angle_to_point(e.x, e.y, proj.x, proj.y)
1892→ local s = math.max(e.w, e.h)
1893→ local hx = e.x + s*0.75*math.cos(r)
1894→ local hy = e.y + s*0.75*math.sin(r)
1895→ proj:on_hit(e, hx, hy)
1896→ end
1897→
1898→ -- Orb-enemy contact damage
1899→ for _, event in ipairs(an:sensor_begin_events('orb', 'enemy')) do
... [70 more lines]
Read (E:\a327ex\Orblike\main.lua)
1830→
1831→
1832→-- Spawn player
1833→p1 = player(gw/2, gh/2)
1834→an:add(p1)
1835→
1836→local o1 = add_orb()
1837→o1:add_ability('fireball')
1838→o1:add_modifier('far_return', true)
1839→o1:add_modifier('greatly_increased_projectile_speed', true)
1840→o1:add_modifier('area_cascade', 3)
1841→o1:add_modifier('area_echo', 3)
1842→o1:add_modifier('magnetic_area', true)
1843→
1844→local o2 = add_orb()
1845→o2:add_ability('caustic_arrow')
1846→o2:add_modifier('far_return', true)
1847→o2:add_modifier('decreased_projectile_speed', true)
1848→o2:add_modifier('area_cascade', 3)
1849→o2:add_modifier('zone_stacking', 3)
1850→o2:add_modifier('homing_zone', true)
1851→o2:add_modifier('greatly_increased_zone_speed', true)
1852→o2:add_modifier('growing_zone', true)
1853→
1854→-- Shockwave visual effect
1855→shockwaves = {}
1856→function add_shockwave(wx, wy, light)
1857→ if light then
1858→ table.insert(shockwaves, {wx = wx, wy = wy, radius = 32, max_radius = 32, speed = -400, strength = -0.05, thickness = 0.08})
1859→ else
... [12 more lines]
Now I have a complete picture. Here's everything implemented:
Abilities (2):
fireball— manual activation, fires a projectile that creates an area (explosion) on hitcaustic_arrow— manual activation, fires a projectile that deals direct damage + creates a zone (persistent DoT area) on hit
Projectile modifiers (currently implemented as mods.* checks in projectile code):
| Modifier | What it does |
|---|---|
pierce (value = count) |
Projectile passes through enemies, decrementing pierce count |
chain (value = count) |
Projectile redirects to nearest enemy after hit, up to N times |
homing |
Projectile steers toward nearest enemy |
weaker_homing |
Lower convergence, longer ramp (sub-modifier of homing) |
stronger_homing |
Higher convergence, shorter ramp (sub-modifier of homing) |
delayed_homing |
Homing activates after 0.5s instead of 0.2s |
very_delayed_homing |
Homing activates after 1.0s |
progressively_increasing_homing |
Homing convergence ramps up over time |
progressively_decreasing_homing |
Homing convergence ramps down over time |
slightly_increased_projectile_speed |
1.2x speed |
increased_projectile_speed |
1.5x speed |
greatly_increased_projectile_speed |
2.0x speed |
slightly_decreased_projectile_speed |
0.9x speed |
decreased_projectile_speed |
0.75x speed |
greatly_decreased_projectile_speed |
0.5x speed |
near_return |
Projectile goes 50px then boomerangs back to player |
mid_return |
Projectile goes 100px then boomerangs back |
far_return |
Projectile goes 150px then boomerangs back |
returning_arc |
Projectile curves in an arc then homes back to player |
Shot-pattern modifiers (applied in orb:activate, affect projectile spawning):
| Modifier | What it does |
|---|---|
nova |
Fire 8 projectiles in all cardinal+diagonal directions |
spreading_multiple_projectiles (value = N) |
Fire N projectiles in a fan spread |
parallel_multiple_projectiles (value = N) |
Duplicate each shot N times side-by-side |
backshot |
Add a shot fired backward |
sideshots |
Add two shots fired perpendicular (left/right) |
Area modifiers (affect both area and zone):
| Modifier | What it does |
|---|---|
slightly_increased_area |
1.25x radius |
increased_area |
1.5x radius |
greatly_increased_area |
2.0x radius |
area_cascade (value = N) |
Spawn N copies of the area/zone in a line along projectile direction |
area_echo (value = N) |
Repeat the area/zone N times with delay |
magnetic_area |
Pulls enemies toward center (impulse on area, per-tick on zone) + shockwave |
Zone-only modifiers:
| Modifier | What it does |
|---|---|
slightly_increased_duration |
1.5x zone duration |
increased_duration |
2.0x |
greatly_increased_duration |
3.0x |
slightly_decreased_duration |
0.75x |
decreased_duration |
0.5x |
greatly_decreased_duration |
0.25x |
moving_zone |
Zone drifts in a random diagonal direction |
homing_zone |
Zone steers toward nearest enemy |
slightly_increased_zone_speed |
1.2x zone movement speed |
increased_zone_speed |
1.5x |
greatly_increased_zone_speed |
2.0x |
growing_zone |
Zone radius doubles over its lifetime |
shrinking_zone |
Zone radius halves over its lifetime |
zone_stacking (value = N) |
Allow up to N overlapping zones of same ability to damage one enemy |
Enemies (1):
seeker— chases player, has HP, takes damage, gets pushed
Other gameplay objects:
orb— orbits player, holds ability + modifiers, activates on inputplayer— WASD movement, aims at mouse
That's the full inventory. What's your thinking on tags? Some natural groupings jump out — like projectile, area, zone, speed, duration, homing — but I'd rather hear your direction before proposing a scheme. For example: should tags live on the modifiers (so a modifier declares "I affect projectile things") or on both sides (abilities declare "I am a projectile+area ability" and modifiers declare "I affect projectile things"), or something else?
Let's go one by one. Tell me the next one and your proposed tags, I'll tell you back which ones it should have, and then you change this list somewhere. Let's keep it in a separate file called tags.md maybe?
Let's start with the first ability:
fireball — manual activation, fires a projectile that explodes into an area on hit.
Proposed tags: projectile, area, fire, spell
My reasoning: projectile and area because those are the mechanical delivery systems it uses. fire as an element tag. spell to distinguish from hypothetical future attack types (melee, trap, etc.).
What should it have?
Missing only the activation method. The activation method implies the ability has a cooldown, since we decided that manual activation methods would be cooldown based while activation methods from linked orbs would be energy based. Do you think this is correct? manual should be a tag? There's the argument that if it's both used manually but also linked via an orb, it's both manual and triggered by the orb condition, right? In that case should it have both manual and whatever tag we end up using for triggered casts?
Good question. I think manual should NOT be a tag on the ability definition itself. Here's why:
The activation method is a property of the orb configuration, not the ability. The same fireball could be:
- Slotted in an orb the player clicks → manual, cooldown-based
- Linked to another orb's trigger condition → triggered, energy-based
- Both simultaneously, across two different orbs
If we bake manual into fireball's tag list, a modifier that says "affects manual abilities" would apply to the fireball even when it's being triggered by an orb condition — which seems wrong.
In PoE terms: the base gem has intrinsic tags (spell, projectile, fire, area), and triggered gets added dynamically when the skill is actually triggered by a support. The base gem doesn't say "I am manual" — it just IS whatever context it's cast in.
So I'd propose: activation method tags (manual, triggered, or whatever we call them) get applied at cast time by the orb, not stored on the ability definition. The ability only carries its intrinsic mechanical/elemental tags.
Does that track, or do you see a reason to have them on the ability itself?
OK, then fireball looks right.
Got it. Let me create the file and move to the next one.
Write (E:\a327ex\Orblike\tags.md)
File created successfully at: E:\a327ex\Orblike\tags.md
Next: caustic_arrow — fires a projectile that deals direct hit damage, then creates a persistent DoT zone on impact.
Proposed tags: projectile, zone, chaos, spell
Reasoning: projectile and zone for the delivery mechanics (zone instead of area, since it's a persistent ground effect rather than an instant explosion). chaos as an element — "caustic" suggests poison/corrosion, which maps to chaos damage in PoE terms. spell same as fireball.
I considered dot (damage over time) but that's arguably a property of zone itself rather than the ability — any zone ticks damage. Could also be poison instead of chaos if you want more specific element names.
What should it have?
Needs area too, since area modifiers also apply to zones. Lore-wise, there's no chaos damage, there's corrupted mana, which is what powers orbs (and thus orbs are used to clean up the world of corrupted mana). Corrupted mana spreads automatically and living beings can't interface with it directly, so it's functionally the same as "chaos damage". Although I'd like a better name than "corrupted" or "corruption" for the tag, so maybe a better name for corrupted mana in general. Any ideas?
Interesting lore. Some ideas, thinking about mana that's gone wrong/wild/toxic:
- blight — disease-like spread, fits the "caustic" flavor, implies something that corrupts the land
- miasma — invisible toxic presence that permeates, good for something you can't directly touch
- taint — simple, implies contamination of something pure (mana), though it's a bit generic
- murk — evokes something that clouds/pollutes, less used in games
- wilt — organic decay, the mana is "dying" or decaying into something harmful
- hex — more magical connotation, implies the corruption has intent or pattern
- bane — something fundamentally poisonous/antithetical to life
- veil — the corruption obscures/separates, like a membrane between mana and living beings
I lean toward blight — it captures the automatic spreading (like a plant blight), the inability to interact with it directly (you can't just grab a blight), and the "cleaning up" role of orbs makes sense (you're purifying blighted mana). "Blight damage" and "blight zone" read well too.
Though miasma has a nice quality — it's literally "bad air" in Greek, an invisible toxic force that you can't touch. Fits the "living beings can't interface with it" aspect well.
What resonates?
[REDACTED]
Yea, I like null. Null mana?
Null mana works. So caustic_arrow becomes:
caustic_arrow: projectile, area, zone, null, spell
(area because area modifiers apply to zones too, zone for the persistent DoT specifically, null for the element, spell same as fireball)
Does that look right?
Should be named null arrow? Nullification arrow? Something along those lines?
I like null arrow — it's clean and matches the directness of "fireball".
"Nullification arrow" is more descriptive but feels wordy next to "fireball". The short punchy names pair better: fireball, null arrow, etc.
Want to go with null_arrow?
OK.
Read (E:\a327ex\Orblike\tags.md)
1→# Tags
2→
3→## Abilities
4→
5→| Name | Tags |
6→|---|---|
7→| fireball | `projectile`, `area`, `fire`, `spell` |
8→| caustic_arrow | |
9→
10→## Modifiers
11→
12→| Name | Tags |
13→|---|---|
14→
15→## Enemies
16→
17→| Name | Tags |
18→|---|---|
19→
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: pierce — projectile passes through enemies, decrementing a counter each time.
Proposed tags: projectile
Reasoning: it only makes sense on things that travel through space and hit targets sequentially. It doesn't affect areas or zones. Just projectile seems sufficient — any projectile-tagged ability could benefit from pierce regardless of element.
What should it have?
It also makes sense for beams, lines?
Good point. A beam or line that passes through enemies is functionally the same thing. So the tag should be broader than just projectile.
Maybe travel or traversal? Something that captures "things that move through space in a direction and can encounter multiple targets along the way." Both projectiles and beams share that quality.
Or maybe the simpler approach: just tag pierce with both projectile and beam (and add more as those delivery types get implemented). Though that means updating pierce's tags every time we add a new compatible delivery type, which is worse.
I think a single encompassing tag like travel makes more sense — anything that has a path/trajectory. Projectiles, beams, lines would all carry the travel tag on their ability definitions, and pierce would also carry travel to indicate compatibility.
Does travel work, or do you have a better word for "things with a directional path"?
I like projectile, beam better. We need to decide on the exact naming for the line/beam carrier though. I believe the other documents might have it decided already.
Grep (beam|carrier|line.*carrier|ray)
Found 8 files
Orblike\reference\area_modifiers_analysis.md
Orblike\orbs\ability_design_reference.md
Orblike\orbs\ability_reference.md
Orblike\orbs\system_design.md
Orblike\reference\implementation_plan.md
Orblike\reference\snkrx_style_effects.md
Orblike\reference\SNKRX_visual_effects_catalog.md
Orblike\reference\SNKRX\devlog.md
Grep (beam|carrier|ray)
92-Spell, AoE, Fire, Duration
93-Engulfs the caster in fire, dealing burning damage per second to all nearby enemies in a persistent radius. Also burns the caster, draining a percentage of their life and energy shield per second. Persists until cancelled or the caster dies. No hit damage — pure DoT.
94-
95:**Scorching Ray**
96-Spell, Channelled, Fire, DoT
97:Channels a beam in the targeted direction. The beam applies stacking fire resistance debuffs to enemies it touches, up to a maximum number of stacks. Also deals fire damage over time that increases with more stacks on the target. The beam can be aimed while channelling.
98-
99-**Caustic Arrow**
100-Attack, Projectile, AoE, Chaos, DoT, Bow
--
308-Linear skillshot that roots the first enemy hit for a very long duration. The target cannot move but can still auto-attack and cast abilities. One of the longest basic-ability CC durations in the game.
309-
310-**Lux R — Final Spark**
311:Very long range linear AoE beam. After a brief channel/windup, fires a beam that damages all enemies in a line across nearly the entire map width. Pierces all targets — hits everything in the line. Short cooldown relative to its power, further reduced on champion kills/assists.
312-
313-**Zoe Q — Paddle Star**
314-Two-part skillshot. First cast fires a missile in a direction. Second cast redirects the missile toward a new target location. Damage scales with the total distance the missile has traveled since the redirect — not from cast point. Encourages casting backward then redirecting forward for maximum range and damage.
--
350-Hold to charge. Charge increases the dash range. Release to dash forward, damaging and knocking back the first enemy champion hit. Minions along the path are pushed aside. If no target is hit, Vi just dashes to the end point. Can be interrupted during charge but not during the dash.
351-
352-**Xerath Q — Arcanopulse**
353:Hold to charge. Range increases while charging, up to a very long maximum. Movement is slowed during charge. Release to fire a linear AoE beam at the targeted direction. Damages all enemies in the line. Mana cost is fixed regardless of charge duration.
354-
355-### Execute & Reset Mechanics
356-
--
383-Places an invisible mushroom trap at a target location. When an enemy steps near it, it detonates: deals AoE poison damage over time and slows in a radius. Teemo stores charges of this ability over time, up to a maximum. Traps are invisible to enemies without detection and persist for a long duration. Map control through trap placement.
384-
385-**Heimerdinger Q — H-28G Evolution Turret**
386:Places an autonomous turret at a target location. The turret attacks nearby enemies with a beam on a fixed interval and also fires a charge shot periodically. Heimerdinger stores turret charges that regenerate over time. Has a maximum number of active turrets. Turrets persist until destroyed or replaced, creating a fortified zone.
... [119 more lines]
Grep (carrier|beam|line|ray)
6-
7-## Orb Zero: The Player
8-
9:The player is Orb Zero — a special orb that always exists, with the player character as its carrier. The player-carrier has events just like any other carrier:
10-
11:| Player carrier event | Analogous to | Description |
12-|---------------------|-------------|-------------|
13-| Hit (outgoing) | Cast on Hit | Player's body contacts an enemy (contact damage) |
14-| Hit (incoming) | Cast on Hit | Player takes damage from any source |
--
40-
41-| Property | What it defines | Values from |
42-|----------|----------------|-------------|
43:| Core | The carrier type — what the orb creates in the world | Carriers table (Projectile, Beam, Area, Zone, Chain, Orbit, etc.) |
44-| Activation | How this orb activates on its own | Manual, Passive, or Periodic |
45-| Targeting | How it picks targets when self-activated | Targeting table (Aimed direction, Auto-target, Self, etc.) |
46-| Behavior | Temporal pattern | Behavior table (Instant, Duration, Channelled, etc.) |
47:| Hit Behavior | What happens when the carrier contacts a target | Hit Behavior table (Consumed, Pierce, Chain, Explode, etc.) |
48-| Effects | What it does to targets | Effect table (Damage, DoT, Slow, Displacement, etc.) |
49-| Modifiers | Modifications to any of the above | Modifier Reference entries |
50-
--
56-
57-## Links
58-
59:A Link is a directed connection between two orbs (including Orb Zero). It defines: **"when [carrier event] happens to Orb A's carrier, fire Orb B."**
60-
61-A link contains:
62:- **Bridge type** — what carrier event on the source orb triggers the target orb
63-- **Optional modifiers** — any modifier from the Modifier Reference can be placed on a link (see Scoping Rules)
... [173 more lines]
The design docs use beam as the carrier type name. From system_design.md line 43: carriers listed as "Projectile, Beam, Area, Zone, Chain, Orbit, etc."
So the naming is settled: beam is the carrier type. Pierce tags should be projectile, beam.
Does that look right?
Sure.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: chain — projectile redirects to the nearest enemy after hit, up to N times.
Proposed tags: projectile, beam
Same reasoning as pierce — it's about a traveling thing jumping between targets. A beam could chain/fork to a new target just as well.
What should it have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: homing — projectile steers toward nearest enemy.
Proposed tags: projectile
Unlike pierce/chain, I'm not sure homing applies to beams — a beam is typically a fixed line or channelled direction, not something that curves mid-flight. Though I could see an argument for a beam that tracks/sweeps toward enemies.
Should it be just projectile, or projectile, beam?
Ah, should active vs. support be in the mods as well? In Path of Exile this is the case, although I'm not sure why.
In PoE, the active/support distinction exists because of their linking rules — you can't link two active gems together expecting them to modify each other, and you can't use a support gem on its own. It's a structural constraint: supports modify actives, actives are the things you cast.
In our system though, the orb slot system already handles this structurally — abilities go in the ability slot, modifiers get added to the orb. The system itself enforces what modifies what. So the active/support distinction might be redundant here.
That said, there could be a future reason to want it as a tag — for example, if a global modifier said "all support modifiers on fire abilities gain +1 level" or something like that. It would let you target "all supports" as a category.
But that feels speculative. I'd lean toward not adding it unless we find a concrete use case. The orb structure already prevents you from using a modifier as an ability or vice versa.
What do you think — leave it out for now?
Leave it out, also homing applies to beams as well. Brimstone/Technologies in Isaac are good examples of being affected by orbits, homing, and things that change pathing via the user of Bezier Curves to draw the beam line.
Good point — beams in Isaac are just drawn as curves, so homing/pathing modifiers work naturally on them.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: the homing sub-modifiers — weaker_homing, stronger_homing, delayed_homing, very_delayed_homing, progressively_increasing_homing, progressively_decreasing_homing.
These all modify homing itself. Proposed tags: homing
They don't care whether it's a projectile or beam — they just tune the homing behavior. So tagging them with homing means they only apply when homing is present.
Does that work?
There's something we need to work out here. We have a homing_zone modifier, for instance, which adds the ability for a zone to seek enemies. In that case, all these homing modifiers should also work for zones. And of course, they also work for projectiles. Should these tags also have projectile, beam, zone into them? Or should homing_zone add the homing tag to a zone when its present?
I think option 2 is cleaner: homing_zone adds the homing tag when present.
The logic would be:
homingis a modifier that, when present, also adds thehomingtag to the carrierhoming_zonedoes the same — addshomingtag to the zone- All homing sub-modifiers just need the
homingtag
This way:
- Sub-modifiers don't need to enumerate every carrier type (
projectile,beam,zone, and whatever comes next) - If we add homing to a new carrier type in the future, the sub-modifiers automatically work
- A zone without
homing_zonewon't match, sostronger_homingcan't accidentally apply to a static zone
So homing becomes both a modifier and a tag that other modifiers can grant. Does that approach work?
Yes, but now we also need a way to catch ourselves when adding new abilities and modifiers. For instance, I'm pretty sure we did not implement these 6 modifiers into zones, they do not work naturally. This means that something that the player would expect to happen wasn't happening. As we add more abilities and more modifiers, missing these connections will become easier and easier, so we need a system to check for these possibilities. Any ideas?
This is a real problem that will compound over time. A few approaches:
Option A: Coverage matrix (manual) — a table in tags.md that explicitly lists every modifier × carrier combination that should work, with a checkmark for "implemented". Simple but requires manual upkeep, which is exactly the kind of thing that falls behind.
Option B: Code-side registry + startup validation (automated) — each carrier type declares what modifiers it handles, and the tag system declares what SHOULD be compatible. At startup, diff the two and print warnings:
-- Zone declares what it actually implements:
zone.handles = {'area_cascade', 'area_echo', 'magnetic_area', ...}
-- At startup, for each modifier:
-- find all carrier types that share tags with this modifier
-- check if each carrier's .handles includes this modifier
-- if not: print warning "modifier 'stronger_homing' should affect zone but isn't implemented"
Option C: Tests — a script that greps the code for modifier names within each carrier's code section, cross-referenced against the tag compatibility. Less robust than B but doesn't require code changes.
I lean toward B — it makes the problem visible immediately when you run the game, and the validation lives alongside the code it's checking. When you add a new modifier or a new carrier type, you get warnings for every missing implementation right in the console.
What do you think?
I like option A better due to its directness. I'm not sure if the table format makes sense because there will be too many modifiers, so it's be a very long table, we won't be able to see it in Markdown.
Good point. Maybe a per-carrier section instead? Something like:
## Projectile carrier
- [x] pierce
- [x] chain
- [x] homing
- [x] stronger_homing
- [ ] weaker_homing (not implemented for zones)
...
## Zone carrier
- [x] area_cascade
- [x] magnetic_area
- [ ] stronger_homing (homing_zone grants homing tag, but sub-modifiers not implemented)
...
Each carrier lists every modifier that SHOULD work with it (based on shared tags), and the checkbox tracks implementation status. When you add a new modifier, you go through each carrier section and ask "should this apply here?" and add the line.
Though even this could get long. Alternatively, group by tag rather than carrier:
## homing tag
Granted by: homing (projectile/beam), homing_zone (zone)
- [x] weaker_homing — projectile
- [x] stronger_homing — projectile
- [ ] weaker_homing — zone
- [ ] stronger_homing — zone
...
This way you see gaps clustered together — "homing works on projectiles but none of the sub-modifiers are implemented for zones."
Which structure feels more useful to you?
Let's just leave this for now, let's just add to_implement that we need to implement homing modifiers for homing_zone
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: projectile speed modifiers — slightly_increased_projectile_speed, increased_projectile_speed, greatly_increased_projectile_speed, and the three decreased variants.
Proposed tags: projectile, beam
These change how fast the carrier travels. Beams could have travel speed too (like a beam that extends outward). Though for instant-hit beams it wouldn't apply — but the tag system just says "compatible", not "guaranteed to matter."
What should they have?
These do not apply to beams
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: returning modifiers — near_return, mid_return, far_return, returning_arc.
These make the projectile boomerang back to the player. Proposed tags: projectile
Beams don't really boomerang. What should they have?
returning_arc may apply to beams too
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: shot pattern modifiers — nova, spreading_multiple_projectiles, parallel_multiple_projectiles, backshot, sideshots.
These affect how shots are spawned from the player. They change the number/direction of carriers created, not the carriers themselves. Proposed tags: projectile, beam
A nova of beams, a fan spread of beams — all seem valid. What should they have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: area size modifiers — slightly_increased_area, increased_area, greatly_increased_area.
These scale the radius of areas and zones. Proposed tags: area
Both the area carrier and zone carrier have the area tag (since we decided area modifiers apply to both), so this covers both. What should they have?
Actually, let's have the earlier ones only apply to projectiles. We may add beam specific ones separately
Good call. Let me fix the shot pattern modifiers and returning_arc.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
So back to the area size modifiers. Proposed tags: area
What should they have?
area, zone
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Wait — should area on the abilities (fireball, null_arrow) also be changed to area, zone separately? Currently fireball has area and null_arrow has area, zone. If the tags on the modifiers are area, zone then matching works by shared tags — fireball shares area with the modifier, null_arrow shares both. That seems correct as-is.
Next: area_cascade — spawns additional copies of the area/zone in a line along projectile direction.
Proposed tags: area, zone
What should it have?
zones are always areas, so every modifier that has zone also will have area, the opposite is not true. area_cascade only applies to areas, but because all zones also have areas, it applies to zone creating abilities too. Does this seem too confusing? Do you think the area/zone distinction shouldn't be there?
You're right to question it. Let me think through the matching semantics.
If the rule is "modifier applies when it shares ANY tag with the ability":
- Zones always have
areatag (because zones are areas) - A modifier with just
areaalready matches zones - Adding
zoneto that modifier is redundant
So the clean version is:
areaon a modifier = applies to areas AND zones (because zones always havearea)zoneon a modifier = applies to zones ONLYarea,zoneon a modifier = same as justarea(redundant)
Under this logic, area_cascade just needs area, and growing_zone just needs zone. No modifier ever needs both. The slightly_increased_area ones I tagged with area, zone should have been just area.
This seems clear and not confusing to me — the key rule is just "zones are always areas." Want me to fix the area size modifiers to just area and continue with this logic?
Do we need the zone distinction at all? Zones are just areas with the duration tag and damage over time tag. Also, we need to add both of those tags to null arrow now that I think of it
That's a cleaner way to think about it. A zone is just an area that has duration and deals damage over time. So instead of:
- fireball:
projectile,area,fire,spell - null_arrow:
projectile,area,zone,null,spell
It becomes:
- fireball:
projectile,area,fire,spell - null_arrow:
projectile,area,duration,dot,null,spell
Then zone-specific modifiers like growing_zone, homing_zone, moving_zone would need duration (or area, duration) rather than zone. And area_cascade just needs area to work on both.
No zone tag needed — it's composed from the others. Want to go with this?
Should we get rid of "zone" entirely and rename it to growing_area, but make it so it only affects areas with duration? I think it would be logically cleaner to have this distinction in the area object itself, because then we can have generic code for areas instead of having to copy it around for zones. But I haven't looked at both classes in a while.
Let me compare the two classes side by side to see how much they share.
Read (E:\a327ex\Orblike\main.lua)
1046→-- Area carrier
1047→area = object:extend()
1048→function area:new(x, y, args)
1049→ object.new(self)
1050→ self.x, self.y = x, y
1051→ self.ability = args.ability
1052→ self.color = args.color
1053→ self.mods = args.mods or {}
1054→ local def = abilities[self.ability]
1055→ self.radius = args.radius or (def and def.explosion_radius) or 25
1056→ if self.mods.slightly_increased_area then self.radius = self.radius*1.25 end
1057→ if self.mods.increased_area then self.radius = self.radius*1.5 end
1058→ if self.mods.greatly_increased_area then self.radius = self.radius*2.0 end
1059→
1060→ -- Cascade: spawn additional copies along a line
1061→ if self.mods.area_cascade and self.mods.area_cascade >= 2 and args.cascade_r then
1062→ local n = self.mods.area_cascade
1063→ local spacing = self.radius*1.5
1064→ local child_mods = {}
1065→ for k, v in pairs(self.mods) do child_mods[k] = v end
1066→ child_mods.area_cascade = nil
1067→ for i = 1, n - 1 do
1068→ local ox = x + i*spacing*math.cos(args.cascade_r)
1069→ local oy = y + i*spacing*math.sin(args.cascade_r)
1070→ an:add(area(ox, oy, {ability = args.ability, color = args.color, mods = child_mods}))
1071→ end
1072→ end
1073→ self.visual_radius = 0
1074→ self.damage = (def and def.area_damage) or 1
1075→ self.fill_color = self.color:clone()
... [95 more lines]
Read (E:\a327ex\Orblike\main.lua)
1173→-- Zone (persistent DoT area)
1174→zone = object:extend()
1175→function zone:new(x, y, args)
1176→ object.new(self)
1177→ self.x, self.y = x, y
1178→ self.ability = args.ability
1179→ self.color = args.color
1180→ self.mods = args.mods or {}
1181→ local def = abilities[self.ability]
1182→ self.radius = args.radius or (def and def.zone_radius) or 50
1183→ if self.mods.slightly_increased_area then self.radius = self.radius*1.25 end
1184→ if self.mods.increased_area then self.radius = self.radius*1.5 end
1185→ if self.mods.greatly_increased_area then self.radius = self.radius*2.0 end
1186→
1187→ -- Cascade: spawn additional copies along a line
1188→ if self.mods.area_cascade and self.mods.area_cascade >= 2 and args.cascade_r then
1189→ local n = self.mods.area_cascade
1190→ local spacing = self.radius*1.5
1191→ local child_mods = {}
1192→ for k, v in pairs(self.mods) do child_mods[k] = v end
1193→ child_mods.area_cascade = nil
1194→ for i = 1, n - 1 do
1195→ local ox = x + i*spacing*math.cos(args.cascade_r)
1196→ local oy = y + i*spacing*math.sin(args.cascade_r)
1197→ an:add(zone(ox, oy, {ability = args.ability, color = args.color, mods = child_mods, cascade_r = args.cascade_r}))
1198→ end
1199→ end
1200→ self.visual_radius = 0
1201→ self.damage = args.damage or 1
1202→ self.fill_color = self.color:clone()
... [206 more lines]
There's a huge amount of duplication. Here's the breakdown:
Shared between area and zone (nearly identical code):
- Area size modifiers (3 lines, duplicated)
- Cascade logic (~12 lines, duplicated)
- Echo logic (~8 lines, duplicated)
- Visual setup (fill_color, arc_r, etc.)
- Rendering (the arc segment drawing — identical)
- magnetic_area (slightly different: one-shot impulse vs per-tick)
- Spring/timer setup
Zone-only additions:
- Duration + blink-out-and-die after duration
- Periodic tick damage (with zone_stacking logic)
- Movement (moving_zone, homing_zone, zone speed mods)
- Growing/shrinking radius
- Duration modifiers
These could absolutely be one class. An area with no duration does instant damage and dies quickly (current area behavior). An area with duration ticks damage and persists (current zone behavior). The zone-only features (movement, growing, etc.) just check if self.duration or are gated by their modifier being present.
This would eliminate ~100 lines of duplicated code, and modifiers like area_cascade would just work without needing to know which class they're in. The rename to growing_area, moving_area, homing_area etc. follows naturally.
Want me to do this merge now, or should we finish the tags pass first and save the refactor for after?
Redo this now, also do the previous to_implement task of implementing the homing modifiers to zones (now areas with duration).
Let me check one more thing — where the damaged_by_zone cooldown timers are decremented.
Grep (damaged_by_zone)
1281- local hit_any = false
1282- for _, e in ipairs(enemies) do
1283- if not e.dead then
1284- local can_damage = false
1285- if self.mods.zone_stacking then
1286: if not e.damaged_by_zone[self.id] then
1287- local count = 0
1288: for _, v in pairs(e.damaged_by_zone) do
1289- if type(v) == 'table' and v[2] == self.ability then count = count + 1 end
1290- end
1291- if count < self.mods.zone_stacking then
1292: e.damaged_by_zone[self.id] = {self.tick_rate, self.ability}
1293- can_damage = true
1294- end
1295- end
1296- else
1297: if not e.damaged_by_zone[self.ability] then
1298: e.damaged_by_zone[self.ability] = self.tick_rate
1299- can_damage = true
1300- end
1301- end
1302- if can_damage then
1303- hit_any = true
--
1419- self.r = 0
1420- self.hp = 5
1421- self.max_hp = 5
1422- self.show_hp_bar = false
1423- self.flashing = false
1424: self.damaged_by_zone = {}
... [23 more lines]
Good, the cooldown decrement is in seeker:update. Now I have everything I need. Let me do the full refactor.
Read (E:\a327ex\Orblike\main.lua)
1482→function seeker:die()
1483→ sfx('enemy_die_' .. an.random:int(1, 3), volumes.enemy_die)
1484→ -- Death effect (dark variant): larger circle, more particles, white -> entity color
1485→ self.spring:pull('main', 0.75)
1486→ an:add(hit_circle(self.x, self.y, {radius = 14, duration = 0.4}):change_color(0.6, red:clone()))
1487→ for i = 1, an.random:int(4, 6) do
1488→ an:add(hit_particle(self.x, self.y, {velocity = an.random:float(100, 300), duration = an.random:float(0.2, 0.5)}):change_color(0.6, red:clone()))
1489→ end
1490→ self:kill()
1491→end
1492→
1493→function seeker:update(dt)
1494→ if not self.visible then return end
1495→ for k, v in pairs(self.damaged_by_zone) do
1496→ if type(v) == 'table' then
1497→ v[1] = v[1] - dt
1498→ if v[1] <= 0 then self.damaged_by_zone[k] = nil end
1499→ else
1500→ self.damaged_by_zone[k] = v - dt
1501→ if self.damaged_by_zone[k] <= 0 then self.damaged_by_zone[k] = nil end
1502→ end
1503→ end
1504→ self.x, self.y = self.collider:get_position()
1505→
1506→ if not self.pushed then
1507→ local sx, sy = self.collider:steering_seek(p1.x, p1.y, self.speed, 200)
1508→ local wx, wy = self.collider:steering_wander(64, 32, 16, dt, self.speed, 200)
1509→ local rx, ry = self.collider:steering_separate(16, cached_enemies, self.speed, 200)
1510→ local fx, fy = math.limit(sx + wx + rx + self.pull_fx, sy + wy + ry + self.pull_fy, 200)
1511→ self.collider:apply_force(fx, fy)
... [50 more lines]
Now I have the full picture. Let me do all the changes. I'll work through them systematically:
- Rename ability definition fields
- Replace both area+zone classes with merged area class
- Update projectile:on_hit
- Update seeker (damaged_by_zone → damaged_by_area)
- Update test setup modifier names
First, the ability definitions:
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated successfully.
Now replace both area and zone classes with the merged area class:
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated successfully.
Now update projectile:on_hit — change zone(...) to area(...):
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated successfully.
Now update seeker — damaged_by_zone → damaged_by_area:
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'damaged_by_zone' were successfully replaced with 'damaged_by_area'.
Now update the test setup modifier names:
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated successfully.
Now let me update the tags.md to reflect the zone → area changes and remove the to_implement item:
Read (E:\a327ex\Orblike\tags.md)
1→# Tags
2→
3→## Abilities
4→
5→| Name | Tags |
6→|---|---|
7→| fireball | `projectile`, `area`, `fire`, `spell` |
8→| null_arrow | `projectile`, `area`, `zone`, `null`, `spell` |
9→
10→## Modifiers
11→
12→| Name | Tags |
13→|---|---|
14→| pierce | `projectile`, `beam` |
15→| chain | `projectile`, `beam` |
16→| homing | `projectile`, `beam` |
17→| weaker_homing | `homing` |
18→| stronger_homing | `homing` |
19→| delayed_homing | `homing` |
20→| very_delayed_homing | `homing` |
21→| progressively_increasing_homing | `homing` |
22→| progressively_decreasing_homing | `homing` |
23→| slightly_increased_projectile_speed | `projectile` |
24→| increased_projectile_speed | `projectile` |
25→| greatly_increased_projectile_speed | `projectile` |
26→| slightly_decreased_projectile_speed | `projectile` |
27→| decreased_projectile_speed | `projectile` |
28→| greatly_decreased_projectile_speed | `projectile` |
29→| near_return | `projectile` |
30→| mid_return | `projectile` |
... [20 more lines]
Write (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Done. Here's a summary of what changed:
Refactor: merged area + zone into single area class
- Area with no
duration= instant explosion (fireball behavior, unchanged) - Area with
duration= persistent DoT (formerly zone behavior) - Eliminated ~150 lines of duplicated code (cascade, echo, area size mods, rendering)
Renamed modifiers (code + tags.md):
zone_stacking→area_stackinghoming_zone→homing_areagrowing_zone→growing_areashrinking_zone→shrinking_areamoving_zone→moving_area*_zone_speed→*_area_speeddamaged_by_zone→damaged_by_area(on enemies)
Renamed ability definition fields:
explosion_radius/zone_radius→area_radiuszone_duration→area_durationzone_tick_rate→area_tick_ratehit_sound/zone_sound→area_sound
Implemented homing sub-modifiers for persistent areas (formerly the to_implement item):
weaker_homing→ lower steer rate (1.5 vs default 3)stronger_homing→ higher steer rate (5)delayed_homing/very_delayed_homing→ delay before homing activatesprogressively_increasing/decreasing_homing→ steer rate ramps up/down over time
Tags.md: removed zone tag, null_arrow now has duration + dot instead. Area size modifiers just need area (since persistent areas also have area).
You can test both orbs to verify everything still works.
I feel like removing the concept of zone from tags and codebase is right, but I'm reaching for keeping it as a high-level descriptor because it captures the dot area idea better than just "area". But that feels like it'd be unnecessarily confusing for players.
I think you're right that "zone" is a more evocative word for players — "area" is generic and could mean anything. But if the tag system uses area for the shared mechanical concept and then you separately need duration + dot to distinguish persistent areas from instant ones, having zone as a third tag on top of that would just be redundant information that exists purely for flavor.
If you want the word "zone" to exist for players, it could live in the UI/tooltip layer rather than the tag system. The tag system is the mechanical backbone — it determines what affects what. The player-facing description can say "creates a null zone" even if the underlying tags are area, duration, dot. Same way PoE gems have flavor descriptions that don't map 1:1 to their tag lists.
So: kill zone in the tags/code, keep it as a vocabulary word for tooltips and descriptions. That way there's no confusion in the system, but players still get the intuitive language.
Keep it in the titles too? growing_zone makes more sense than growing_area
Good point — "growing zone" immediately communicates "the persistent ground thing gets bigger," while "growing area" is vague. The word "zone" works perfectly in modifier names as a descriptor, it just shouldn't be a tag.
So the rule would be: modifiers that only apply to persistent areas (duration + dot) keep "zone" in their names, but their tags use area, duration, dot — never a zone tag.
Let me revert the modifier names in code:
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'growing_area' were successfully replaced with 'growing_zone'.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'shrinking_area' were successfully replaced with 'shrinking_zone'.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'moving_area' were successfully replaced with 'moving_zone'.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'homing_area' were successfully replaced with 'homing_zone'.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'area_stacking' were successfully replaced with 'zone_stacking'.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'slightly_increased_area_speed' were successfully replaced with 'slightly_increased_zone_speed'.
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'increased_area_speed' were successfully replaced with 'increased_zone_speed'.
Edit (E:\a327ex\Orblike\main.lua)
<tool_use_error>String to replace not found in file.
String: greatly_increased_area_speed</tool_use_error>
That one was already caught by the increased_area_speed → increased_zone_speed replace_all (since "greatly_increased_area_speed" contains "increased_area_speed"). Let me verify:
Grep (zone_speed|area_speed)
1115: self.homing_zone_speed = 25
1137: local area_speed_mult = 1
1138: if self.mods.slightly_increased_zone_speed then area_speed_mult = area_speed_mult*1.2 end
1139: if self.mods.increased_zone_speed then area_speed_mult = area_speed_mult*1.5 end
1140: if self.mods.greatly_increased_zone_speed then area_speed_mult = area_speed_mult*2.0 end
1142: self.move_vx = self.move_vx*area_speed_mult
1143: self.move_vy = self.move_vy*area_speed_mult
1145: if self.homing_zone_speed then
1146: self.homing_zone_speed = self.homing_zone_speed*area_speed_mult
1338: local dx = self.homing_zone_speed*math.cos(r) - self.area_vx
1339: local dy = self.homing_zone_speed*math.sin(r) - self.area_vy
1829:o2:add_modifier('greatly_increased_zone_speed', true)
The modifier names are correct. The internal variable names like area_speed_mult, area_vx, homing_zone_speed are fine — those are implementation details, not player-facing names. Now let me verify the test setup looks right:
Read (E:\a327ex\Orblike\main.lua)
1820→o1:add_modifier('magnetic_area', true)
1821→
1822→local o2 = add_orb()
1823→o2:add_ability('caustic_arrow')
1824→o2:add_modifier('far_return', true)
1825→o2:add_modifier('decreased_projectile_speed', true)
1826→o2:add_modifier('area_cascade', 3)
1827→o2:add_modifier('zone_stacking', 3)
1828→o2:add_modifier('homing_zone', true)
1829→o2:add_modifier('greatly_increased_zone_speed', true)
1830→o2:add_modifier('growing_zone', true)
1831→
1832→-- Shockwave visual effect
1833→shockwaves = {}
1834→function add_shockwave(wx, wy, light)
Good. All zone-specific modifier names kept their "zone" naming. The rule is clean: "zone" lives in names and UI, never in tags. You can test both orbs now to verify the merge didn't break anything.
If area_vx, etc are details, then homing_zone_speed also needs to go back to being homing_area_speed. I don't know which is more right but they need to be consistent
You're right. Let me make them all consistent — internal variables should use area since the class is area:
Edit (E:\a327ex\Orblike\main.lua)
The file E:\a327ex\Orblike\main.lua has been updated. All occurrences of 'homing_zone_speed' were successfully replaced with 'homing_area_speed'.
Internal variables now consistently use area (area_vx, area_vy, homing_area_speed, area_speed_mult), while player-facing modifier names use zone (homing_zone, growing_zone, moving_zone, etc.).
What's next?
We were going through the modifiers list. Next up:
area_cascade — spawns additional copies of the area in a line along projectile direction.
Proposed tags: area
Works on both instant areas (fireball explosions) and persistent areas (null_arrow DoT zones) since both have the area tag. What should it have?
Seems right that it's only area.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: area_echo — repeats the area after a delay, N times.
Proposed tags: area
Same reasoning — works on both instant and persistent areas. What should it have?
Sure.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: magnetic_area — pulls enemies toward center (one-shot impulse on instant areas, per-tick pull on persistent areas).
Proposed tags: area
What should it have?
area, force. force should be present whenever we have force-like behaviors that forcibly change enemy's positions
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: duration modifiers — slightly_increased_duration, increased_duration, greatly_increased_duration, and the three decreased variants.
These only make sense on things that persist over time. Proposed tags: duration
What should they have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: moving_zone — zone drifts in a random diagonal direction.
Proposed tags: area, duration
Only makes sense on persistent areas. What should it have?
We will have movement abilities eventually, right? Like, say, a dash. Those will likely have a movement tag. Should this ability be coded, tag-wise, as adding the movement tag to the area? Can you see how movement modifiers might benefit both movement abilities and moving zones?
Yes, that's the same pattern as homing_zone adding the homing tag. moving_zone would add the movement tag to the area, and then movement modifiers would naturally apply to anything with movement — whether that's a dash, a moving zone, or whatever future movement ability.
Concrete examples where this could work:
- "Increased movement speed" → makes your dash faster AND your zones drift faster
- "Homing" on a
movement-tagged thing → your dash homes toward enemies, or your zone steers toward enemies (which is whathoming_zonealready does — maybehoming_zoneis really justhoming+movementcombined)
That last point is interesting — homing_zone might actually be homing applied to something that already has movement. Rather than a separate modifier, it could emerge from combining moving_zone (grants movement) with homing (which now also works on movement-tagged things). Though that might be over-composing for now.
For the immediate question: moving_zone's tags should be area, duration, and it grants movement when present. Then the zone speed modifiers (slightly_increased_zone_speed, etc.) would have the movement tag rather than being zone-specific.
Does that direction feel right?
It seems like we increasingly need the notion of adding modifiers to abilities and requiring modifiers from abilities. + to add, ! to require. Does that help?
[Request interrupted by user]
I mean adding and requiring tags*
Yes, that clarifies things a lot. So:
moving_zone: requiresarea,duration. Addsmovement.homing_zone: requiresarea,duration. Addshoming,movement.slightly_increased_zone_speed: requiresmovement. (Works on dashes, moving zones, homing zones — anything moving)
The notation in tags.md could be:
| moving_zone | !area, !duration, +movement |
| homing_zone | !area, !duration, +homing, +movement |
| slightly_increased_zone_speed | !movement |
And then the homing sub-modifiers we already have:
| weaker_homing | !homing |
| stronger_homing | !homing |
No prefix means... actually, what would no prefix mean? Maybe everything should be explicit — ! for require, + for add? Or should plain tags be a third category (like "this modifier is associated with these tags for matching purposes")?
Neither requiring nor adding would be tags that the modifier affects but where it's not necessary for it to work. I'm not sure we have any examples of that currently.
Right, so the three modes are:
!tag— requires this tag on the ability, won't work without it+tag— adds this tag to the ability when presenttag— affects abilities with this tag, but doesn't require it (works without it too)
Let me update the existing entries with this notation. Looking at what we have:
pierce: requiresprojectileorbeam— without one of those, pierce is meaningless →!projectile,!beamhoming: requiresprojectileorbeam→!projectile,!beam. And it adds thehomingtag →+homingweaker_homing: requireshoming→!homingslightly_increased_area: requiresarea→!areamoving_zone: requiresareaandduration, addsmovement→!area,!duration,+movement
Wait — for pierce, it requires projectile OR beam, not both. Does ! mean "requires at least one of all the ! tags" or "requires all of them"? For pierce, it should be "needs at least one of projectile/beam."
How should we handle OR vs AND for requirements?
Hmmmm, should this even be visible to the player? It seems like it's just making things more confusing, right?
You're right. The ! and + system is useful internally for us to reason about modifier compatibility, but exposing it to players would just be noise. Players don't need to see "requires projectile, beam" — they just need to see that pierce is a projectile modifier, and the game prevents them from putting it on something incompatible.
The tag system for players should be simple: "fireball has these tags: projectile, area, fire, spell." Modifiers show what they do, not their compatibility rules. The require/add logic lives in code, enforcing valid combinations silently.
So for tags.md — should we keep it as a simple flat list like we've been doing (which is really the player-facing view), and put the require/add rules somewhere else? Or just keep the flat list and handle the rules in code when we get there?
Let's handle the rules in code. But I wanted to reason more about homing and moving zone. Imagine we have a general modifier that adds the movement tag to whatever its linked to. So if it's linked to an area with duration it makes the zone move. If it's linked to an area alone it does nothing because areas are static. If it's linked to a projectile, the projectile already has "movement" by default, so it's already affected by other modifiers that affect movement. Should then modifiers that affect movement (like orbit, return, etc) have the movement tag and also be able to affect other things? I think this gets very confusing but I can see how there's some purity to it in a systematic way that might make some sense... Just needs more thought put into it.
Yeah, there's a real tension here. The systematic purity is appealing — "everything that moves shares movement, all movement modifiers compose freely" — but it creates a combinatorial explosion where most combinations are nonsensical:
- Returning zone? Zone flies out and boomerangs back to... where?
- Orbit on a dash? You dash in a circle around yourself?
- Nova on a movement ability? You dash in 8 directions?
PoE solves this by having the systematic tags BUT also hard restrictions that block nonsensical combos. So you get the elegance of the tag system for the cases that work, plus a blacklist for the cases that don't. Which is arguably worse than just not unifying them in the first place, because now you have two systems.
I think the productive question to defer to is: what emergent combinations would actually be fun? If the answer is "moving zones should be affected by homing and speed modifiers, but not by returning/orbit/nova" — then those aren't really unified by movement, they're unified by something narrower. And maybe homing_zone and moving_zone are just their own modifiers that happen to share some sub-modifiers, without needing a grand unifying tag.
Worth returning to once more abilities exist and the actually-fun interactions become clearer. Want to keep going through the remaining modifiers for now?
Sure, where were we?
We had just finished magnetic_area and the duration modifiers. The remaining zone-specific modifiers are:
moving_zone — zone drifts in a random diagonal direction.
Proposed tags: area, duration
Keeping it simple for now — just requires a persistent area. We can revisit the movement tag question later. What should it have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: homing_zone — zone steers toward nearest enemy.
Proposed tags: area, duration
What should it have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: zone speed modifiers — slightly_increased_zone_speed, increased_zone_speed, greatly_increased_zone_speed.
These affect movement speed of moving/homing zones. Proposed tags: area, duration
What should they have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: growing_zone — zone radius doubles over its lifetime.
Proposed tags: area, duration
What should it have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: shrinking_zone — zone radius halves over its lifetime.
Proposed tags: area, duration
What should it have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
Next: zone_stacking — allows up to N overlapping zones of the same ability to damage one enemy.
Proposed tags: area, duration
What should it have?
Yes.
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
That's all the implemented modifiers. Last one is the enemy:
seeker — chases player, has HP, takes damage, gets pushed.
Proposed tags: enemy
Could also have more specific tags like melee (it does contact damage) or mobile (it moves). But with only one enemy type, hard to know what distinctions matter yet. What should it have?
You can delete that entry, this is just for abilities and modifiers
Edit (E:\a327ex\Orblike\tags.md)
The file E:\a327ex\Orblike\tags.md has been updated successfully.
That's everything implemented. Here's the final state:
Read (E:\a327ex\Orblike\tags.md)
1→# Tags
2→
3→## Abilities
4→
5→| Name | Tags |
6→|---|---|
7→| fireball | `projectile`, `area`, `fire`, `spell` |
8→| null_arrow | `projectile`, `area`, `duration`, `dot`, `null`, `spell` |
9→
10→## Modifiers
11→
12→| Name | Tags |
13→|---|---|
14→| pierce | `projectile`, `beam` |
15→| chain | `projectile`, `beam` |
16→| homing | `projectile`, `beam` |
17→| weaker_homing | `homing` |
18→| stronger_homing | `homing` |
19→| delayed_homing | `homing` |
20→| very_delayed_homing | `homing` |
21→| progressively_increasing_homing | `homing` |
22→| progressively_decreasing_homing | `homing` |
23→| slightly_increased_projectile_speed | `projectile` |
24→| increased_projectile_speed | `projectile` |
25→| greatly_increased_projectile_speed | `projectile` |
26→| slightly_decreased_projectile_speed | `projectile` |
27→| decreased_projectile_speed | `projectile` |
28→| greatly_decreased_projectile_speed | `projectile` |
29→| near_return | `projectile` |
30→| mid_return | `projectile` |
... [28 more lines]
All done. Two abilities and 37 modifiers tagged. Open questions deferred for later: the movement tag unification and the require/add (!/+) rules in code.
Let's step back and think things through. Given the orb system, do you think there's a better system than a tag one to handle these combinations for the player? Let's think only from the player's perspective. If you're playing a build heavy game like this where things might combine in interesting and unexpected ways, I always felt like the tag system in PoE was kind of opaque and not clear. Do you see any ways of making these possible combinations more clear, while also keeping the system simple enough that players can easily grasp things? Think outside the box, it doesn't need to be a tag system, it can be things that are visual entirely, or something else entirely, just try to think of possible systems that solve this problem from a player readability perspective in different and useful ways for our purposes.
Let me think about this from scratch, forgetting tags entirely.
The core player question is: "what works with what, and what will it do?" Tags answer the first question abstractly but fail at the second. Here are some alternative approaches:
1. "Everything works, effects adapt"
Remove compatibility entirely. Any modifier can go on any ability. But the effect changes based on context. "Homing" on a projectile = projectile curves. "Homing" on a persistent area = area drifts toward enemies. "Homing" on an instant area = area spawns at nearest enemy's position instead of at impact point.
Players never hit a wall. They never need to learn what's compatible because everything IS compatible. The question shifts from "can I?" to "what does it do here?" — which is a more fun question. The game teaches through experimentation, not labels.
The downside: every modifier needs behavior for every context, which is a lot of implementation work. And some combinations might feel underwhelming, which could be disappointing.
2. "Show, don't label"
When the player is choosing or placing a modifier, the orb physically previews the result in real-time. Add homing → you see a tiny animated projectile curving on the orb's surface. Add nova → you see projectiles fanning out. Add cascade → you see areas chaining in a line. The visual IS the explanation.
Incompatible modifiers don't need to be blocked — they just show "no change" visually, which is self-evident. Strong combos are obvious because you see dramatic visual changes.
This is basically a live simulation running on each orb as a preview. Players build intuitively by watching what happens.
3. "Phase-based slots"
Instead of abstract tags, the orb has physical slots organized by the phases of the ability's lifecycle:
[LAUNCH] → [TRAVEL] → [IMPACT] → [AFTERMATH]
Fireball: Launch (how it fires) → Travel (projectile path) → Impact (explosion) → no aftermath. Null arrow: Launch → Travel → Impact (hit damage) → Aftermath (persistent zone).
Modifiers physically slot into the phase they affect. Homing goes in Travel. Nova goes in Launch. Area cascade goes in Impact. Growing zone goes in Aftermath. Players don't learn tags — they learn "when does this modifier kick in?" which maps to intuitive understanding of cause and effect.
If a phase doesn't exist for an ability (fireball has no Aftermath), those slots simply aren't there. Compatibility is implicit in the structure.
4. "Modifier families with icons"
Collapse the many granular tags into 3-4 visually distinct categories with icons:
- A path/arrow icon = "how it travels" (homing, returning, pierce, chain, speed)
- A burst/circle icon = "what it creates" (area size, cascade, echo, magnetic)
- A clock icon = "how long" (duration, tick rate, growing/shrinking)
- A directional arrows icon = "how many / what pattern" (nova, spread, parallel, back/side shots)
Each ability shows which icons it has. Each modifier shows its icon. If the icons match, it works. 3-4 symbols to learn vs dozens of tag names.
My instinct says approach 3 (phase-based slots) fits the orb system best. It's spatial, physical, intuitive. Players already understand "first it fires, then it travels, then it hits, then it lingers." And it naturally handles the zone question — zones are just abilities that have an Aftermath phase. Modifiers that affect the Aftermath phase only appear as slots if the ability has one.
But approach 1 is the most ambitious and potentially the most fun — if you can stomach the implementation cost. What resonates?
Are there abilities that break the launch travel impact aftermath system? Check the research document full of abilities from various games to try to find ones that don't fit, gather them into a list, and then let's try to see how to make the system fit them. If you can find solutions yourself that seem reasonable then you don't need my input, but later, with whatever final system you come up with, I want to know why a certain change was made by being shown examples.
Agent
[
{
"type": "text",
"text": "Now I have the complete document. Let me analyze it systematically for abilities that break the linear LAUNCH -> TRAVEL -> IMPACT -> AFTERMATH model.\n\n---\n\nHere is the thorough analysis, grouped by the type of problem each ability poses to the 4-phase linear model:\n\n---\n\n## 1. LOOPING / REPEATING PHASES (Travel -> Impact -> Travel -> Impact...)\n\nThese abilities don't terminate after impact. They re-enter earlier phases, creating cycles rather than a linear flow.\n\n**Spectral Throw** (PoE) -- Thrown weapon travels out, then returns to the caster, hitting enemies in both directions. The TRAVEL phase happens twice (outward and return), with IMPACT occurring during both. The lifecycle is LAUNCH -> TRAVEL+IMPACT -> TRAVEL(return)+IMPACT.\n\n**Mom's Knife** (Isaac) -- Charged knife travels outward, then returns. Damages on both trips. Same outbound-return loop as Spectral Throw.\n\n**My Reflection** (Isaac) -- Tears boomerang back after max range, hitting on both trips. TRAVEL -> IMPACT -> TRAVEL(return) -> IMPACT.\n\n**Sawmerang** (RoR2) -- Saw blade travels out piercing everything, then returns, hitting on both passes. Same loop.\n\n**Skewerstorm** (Star of Providence) -- Drills fire outward, embed in walls, then are recalled en masse back to the player, hitting enemies on the return. The \"recall\" is a player-triggered second TRAVEL phase that was preceded by a terrain-IMPACT phase (embedding). LAUNCH -> TRAVEL -> TERRAIN_IMPACT(embed) -> [player trigger] -> TRAVEL(return) -> IMPACT.\n\n**Razor** (Star of Providence) -- Blades orbit the player, then are fired as projectiles, then return to orbit. The weapon cycles between orbit (a kind of persistent passive TRAVEL around the player) and launched TRAVEL+IMPACT, then returns to orbit. It's an orbit -> projectile -> orbit loop.\n\n**Arc** (PoE) / **Chain support** (PoE) / **Lich Chain Frost** (Dota 2) / **Nature's Prophet Wrath of Nature** (Dota 2) / **Medusa Mystic Snake** (Dota 2) / **Huntress Laser Glaive** (RoR2) / **Ukulele** (RoR2) / **Jacob's Ladder** (Isaac) -- Chaining abilities hit a target, then seek the next target, then hit again. Each \"chain\" is a mini TRAVEL -> IMPACT cycle that repeats N times. The overall shape is LAUNCH -> [TRAVEL -> IMPACT]_N. The number of cycles may be fixed or unlimited (Chain Frost has infinite bounces).\n\n**Flat Stone** (Isaac) -- Tears skip off the ground like stones on water, bouncing multiple times, dealing damage at each bounce point. Each bounce is a TRAVEL -> IMPACT cycle. LAUNCH -> [TRAVEL -> IMPACT]_N.\n\n**Rubber Cement** (Isaac) -- Tears bounce off walls and obstacles, persisting and potentially hitting enemies on ricochets. The TRAVEL phase restarts after each wall collision rather than the projectile being consumed.\n\n**Draven Q -- Spinning Axe** (LoL) -- The axe hits a target, then bounces back to a landing spot where Draven must catch it. If caught, the ability refreshes. IMPACT feeds back into a new LAUNCH. The cycle is LAUNCH -> TRAVEL -> IMPACT -> [catch] -> LAUNCH -> ... indefinitely.\n\n**Tiny Planet** (Isaac) -- Tears orbit the player in circles instead of traveling linearly. TRAVEL becomes an endless orbital loop with IMPACT happening whenever an orbiting tear contacts an enemy. There's no terminal state -- the tear just keeps orbiting until it expires.\n\n---\n\n## 2. NO TRAVEL PHASE (but not simply \"self-cast\")\n\nThese skip the spatial traversal phase in ways that aren't just \"cast on self.\"\n\n**Discharge** (PoE) -- Consumes all charges for an instant AoE explosion centered on caster. There's a LAUNCH (the cast) and IMPACT (the explosion), but no TRAVEL. The ability's \"targeting\" is purely \"everything near me.\" It's not self-cast either -- it's an offensive AoE that skips TRAVEL.\n\n**Karthus R -- Requiem** (LoL) -- After channeling, deals damage to all enemy champions globally regardless of position. No projectile, no travel, no area targeting. LAUNCH (channel) -> IMPACT (global simultaneous damage). There's nothing between the cast and the hit -- it just hits everyone everywhere.\n\n**Zeus R -- Thundergod's Wrath** (Dota 2) -- Same pattern: damages every enemy hero on the map simultaneously. No travel, no targeting, no spatial component at all.\n\n**Silencer R -- Global Silence** (Dota 2) -- Silences every enemy on the map. No travel, no impact in the damage sense. LAUNCH -> instant global effect. The \"impact\" is a debuff application with no spatial component.\n\n**Centaur Warrunner Q -- Hoof Stomp** (Dota 2) -- Self-centered AoE stun. No travel. But unlike self-buffs, it's clearly offensive. The ability is LAUNCH -> IMPACT with zero spatial gap.\n\n**Tidehunter R -- Ravage** (Dota 2) -- Expanding ring from caster. There IS a travel component (the ring expands outward), but it's not a projectile -- it's an expanding wavefront that hits enemies at different times based on distance. The TRAVEL and IMPACT phases are fused: the expansion IS the impact, happening progressively.\n\n---\n\n## 3. NO CLEAR \"IMPACT\" -- Persistent/Continuous Effects\n\nThese abilities don't have a discrete moment of impact. They operate continuously.\n\n**Righteous Fire** (PoE) -- Toggle that engulfs the caster in fire, burning nearby enemies and the caster indefinitely. No LAUNCH (it's a toggle), no TRAVEL, no IMPACT. It's a persistent state. The \"damage\" is continuous, not event-based.\n\n**Blade Vortex** (PoE) -- Each cast adds a spinning blade orbiting the caster. Blades hit enemies they pass through at a fixed rate. There's a LAUNCH (each cast), but then the blade just orbits dealing continuous damage. There's no TRAVEL toward a target, and IMPACT is ongoing/repeated rather than a single event.\n\n**Cyclone** (PoE) -- Channeled spinning attack. The character moves and continuously damages everything nearby. LAUNCH, TRAVEL, and IMPACT are all happening simultaneously and continuously. The phases aren't sequential -- they're concurrent.\n\n**Scorching Ray** (PoE) -- Channeled beam. Continuous damage and debuff stacking on whatever the beam touches. No projectile, no travel, no discrete impact. The beam IS the ability for its entire duration -- all phases collapsed into one.\n\n**Ludovico Technique** (Isaac) -- A single persistent tear hovers in the room, directly controlled by movement keys. There's no LAUNCH (it just exists), TRAVEL is player-controlled in real time, and IMPACT is continuous contact damage. The player IS the guidance system.\n\n**Godhead** (Isaac) -- Tears have a damaging aura around them. Enemies don't need to be hit directly -- proximity to the tear deals damage. IMPACT becomes a continuous field around the projectile during TRAVEL, not a discrete event.\n\n**Ball Lightning** (PoE) -- Slow-moving orb that repeatedly hits enemies within its radius as it passes through. The TRAVEL and IMPACT phases are concurrent -- the orb hits while traveling, at a fixed interval, and each enemy can be hit multiple times. IMPACT isn't a terminal event; it repeats during TRAVEL.\n\n**Thunderhead** (Star of Providence) -- Slow-moving orb that doesn't deal direct damage. Instead it electrifies a nearby enemy via a tether. The orb's TRAVEL is continuous, and \"IMPACT\" is a persistent tether effect that activates during travel rather than on collision.\n\n**Sacrificial Dagger / orbitals** (Isaac) -- Orbit the player continuously dealing contact damage. No LAUNCH, no discrete IMPACT. It's a persistent damage field.\n\n**Bot Fly** (Isaac) -- Orbits player. When an enemy projectile comes near, Bot Fly shoots it down. A persistent defensive entity with reactive behavior. No phase model applies -- it's an autonomous agent.\n\n**Delta** (Star of Providence) -- Razor blades linked by damaging lightning arcs. The tethers between orbital entities are damage sources. The connections between carriers are themselves persistent damage zones.\n\n**Acrid -- Vicious Wounds** (RoR2) -- Applies permanent poison that never expires. AFTERMATH (the DoT) is literally infinite, making it less of a \"phase\" and more of a permanent state change on the target.\n\n---\n\n## 4. PLAYER-CONTROLLED PHASE TRANSITIONS (Recast / Detonate / Manual Control)\n\nThe linear flow is broken because the player decides when to advance to the next phase.\n\n**Lee Sin Q -- Sonic Wave / Resonating Strike** (LoL) -- First cast fires a skillshot (LAUNCH -> TRAVEL -> IMPACT marks target). Second cast (player-triggered recast) dashes to the marked target. The transition from \"mark\" to \"dash\" is under player control and can happen at any time within a window, or not at all.\n\n**Thresh Q -- Death Sentence** (LoL) -- Hook hits and stuns an enemy. Player can recast to dash to the hooked target. The IMPACT phase branches: it can either just be CC, or the player recasts to add a gap-close. Player choice creates a branching lifecycle.\n\n**Zoe Q -- Paddle Star** (LoL) -- First cast fires a missile. Second cast redirects it toward a new location. The player interrupts TRAVEL to redirect the projectile. The TRAVEL phase is split into two segments with a player-controlled direction change in between.\n\n**Jhin R -- Curtain Call** (LoL) -- Channels to set up a cone, then the player fires up to four individual skillshots within it, each aimed separately. LAUNCH is the channel setup, then there are four player-controlled LAUNCH -> TRAVEL -> IMPACT sub-cycles within the overall ability. The player also controls when to end early.\n\n**Blade Flurry** (PoE) -- Channel builds stages; releasing the channel triggers the burst. The player controls when to transition from the channeling phase to the release phase, and the power of the release scales with how long they waited.\n\n**Zed W -- Living Shadow** (LoL) -- Places a shadow. Player can recast to swap positions. The shadow persists and mimics abilities, and the recast (position swap) is a player-controlled phase transition that can happen at any time or never.\n\n**Zed R -- Death Mark** (LoL) -- Dashes to enemy, marks them. After 3 seconds the mark detonates. Player can recast to return to starting shadow. The AFTERMATH (mark detonation) is on a fixed timer, but the escape (returning to the shadow) is player-controlled.\n\n**Twisted Fate R -- Destiny** (LoL) -- First cast reveals all enemies (global vision). Second cast teleports TF to a location. Two completely different effects gated behind a recast. LAUNCH (reveal) -> player-controlled LAUNCH2 (teleport). The first \"phase\" is informational, the second is spatial.\n\n**Detonator** (Star of Providence) -- Shoots 5 sticky bombs, then the 6th shot detonates all placed bombs. The magazine structure IS the phase system: shots 1-5 are LAUNCH -> TRAVEL -> \"IMPACT\"(stick) and shot 6 is the player-triggered mass detonation. The player controls when to place and when to detonate through the magazine mechanic.\n\n**Blastchain Mine** (PoE) -- Mines are placed at the player's feet. The player manually detonates them. LAUNCH (place mine) is separated from IMPACT (detonation) by an indefinite player-controlled delay.\n\n**Anti-Gravity** (Isaac) -- Tears hover in place as you fire. Releasing the fire key launches them all forward. The player controls the transition from \"accumulation\" to \"TRAVEL.\" LAUNCH is split into two sub-phases: placement and release.\n\n**Eye of the Occult** (Isaac) -- Tears are manually steered after being fired. The TRAVEL phase is continuously player-controlled rather than following a predetermined path. The player is the guidance system throughout.\n\n---\n\n## 5. PHASE TRANSFORMATION (Projectile becomes a different entity)\n\nThe ability changes its fundamental nature mid-lifecycle.\n\n**Tornado Shot** (PoE) -- Arrow travels to a point, then spawns secondary projectiles that fly outward. One LAUNCH produces two TRAVEL phases: the initial arrow's travel, then a burst of secondary projectiles from the impact point. LAUNCH -> TRAVEL1 -> \"IMPACT\"(transforms) -> multiple TRAVEL2 -> IMPACT2.\n\n**Molten Strike** (PoE) -- Melee hit, then molten projectiles arc from the impact point. LAUNCH is a melee strike, IMPACT creates new projectiles. LAUNCH(melee) -> IMPACT -> LAUNCH2(projectiles) -> TRAVEL2 -> IMPACT2. A single ability has two complete LAUNCH -> IMPACT cycles chained.\n\n**Toxic Rain** (PoE) -- Arrows land, creating spore pods (IMPACT creates persistent entities). Pods deal DoT, then after a delay, each pod explodes (AFTERMATH has its own IMPACT). LAUNCH -> TRAVEL -> IMPACT(create pods) -> AFTERMATH(DoT) -> delayed IMPACT2(explosions).\n\n**Volatile Dead** (PoE) -- Consumes corpses to spawn homing fire orbs. Each orb then seeks a target and explodes. The \"LAUNCH\" consumes a corpse to create an entity. That entity then has its own autonomous TRAVEL -> IMPACT lifecycle. LAUNCH(spawn orbs) -> autonomous [TRAVEL -> IMPACT] per orb.\n\n**Deployment Array** (Star of Providence) -- Fires turrets that bounce off walls. When a turret stops bouncing, it deploys and fires lasers at enemies, then expires. LAUNCH -> TRAVEL(bouncing projectile) -> velocity-based state transition -> DEPLOY(becomes turret) -> turret's own LAUNCH -> IMPACT. The projectile transforms into a completely different entity type based on physics state.\n\n**Drill** (Star of Providence) -- Hold fire for a channeled melee-range drill. Release fire to launch it as a projectile. The weapon transitions from a channeled melee entity to a launched projectile on release. Two different entity types in sequence, controlled by player input.\n\n**Spear** (Star of Providence) -- Projectile embeds in the first enemy hit, becoming an attached DoT source. The projectile doesn't just \"impact\" -- it transforms into a persistent attached entity. LAUNCH -> TRAVEL -> IMPACT(embed) -> entity-state-change to attached DoT.\n\n**Cricket's Body / Parasite / Haemolacria / Compound Fracture** (Isaac) -- On impact, tears split into secondary projectiles. IMPACT creates new LAUNCH events. Each secondary has its own TRAVEL -> IMPACT. The lifecycle branches rather than terminating.\n\n**Winter Orb** (PoE) -- While channeling, builds stages on an orb. When channeling stops, the orb persists and auto-fires projectiles at nearby enemies. The channeling phase builds a persistent entity that then has its own autonomous LAUNCH -> TRAVEL -> IMPACT cycle, independent of the player.\n\n**Council's Blessing** (Star of Providence) -- Places a halo on a targeted enemy, converting them from hostile to allied. The target starts shooting at other enemies. IMPACT doesn't deal damage in the normal sense -- it converts the target into a new autonomous entity on the player's side.\n\n---\n\n## 6. CONCURRENT / PARALLEL PHASES\n\nMultiple phases happen at the same time rather than sequentially.\n\n**Soulrend** (PoE) -- Projectile pierces enemies, applying a DoT debuff to each one AND dealing hit damage. TRAVEL, IMPACT (hit damage), and AFTERMATH (DoT application) all happen simultaneously and repeatedly as the projectile moves through enemies.\n\n**Essence Drain** (PoE) -- Projectile hits first enemy, applies DoT, and heals the caster. If the target dies, the debuff spreads to a nearby enemy. AFTERMATH includes both a DoT on the target and healing for the caster (bidirectional effect), plus a death-triggered spread that restarts the AFTERMATH on a new target.\n\n**Flame Dash** (PoE) -- Teleport + burning ground trail. The TRAVEL (teleport) and AFTERMATH (burning ground) are created simultaneously, not sequentially. The trail is generated during the TRAVEL phase, not after IMPACT.\n\n**Sword** (Star of Providence) -- Each swing simultaneously performs a melee attack AND fires a projectile. Two complete ability lifecycles (melee: LAUNCH -> IMPACT, projectile: LAUNCH -> TRAVEL -> IMPACT) are triggered concurrently from a single activation.\n\n**Crystal Maiden R -- Freezing Field** (Dota 2) -- Channeled AoE with random explosions. LAUNCH (the channel) and IMPACT (random ice explosions) happen concurrently throughout the channel. There's no sequential progression -- the channel IS the damage, applied randomly within the area.\n\n**Katarina R -- Death Lotus** (LoL) -- Channels while throwing daggers at up to 3 nearby enemies at very high rate. LAUNCH (the channel) and repeated IMPACT (the dagger hits) are simultaneous and continuous.\n\n**Kunkka R -- Ghostship** (Dota 2) -- Ship sails toward a point, dealing damage/stun on arrival, BUT allies in the ship's path receive a damage-delay buff before the ship arrives. The defensive effect happens DURING TRAVEL, the offensive effect happens AT IMPACT. Two different effects operate in different phases simultaneously.\n\n---\n\n## 7. AUTONOMOUS ENTITIES (ability creates an agent with its own lifecycle)\n\nThese abilities don't follow the player's lifecycle at all -- they create independent actors.\n\n**Storm Brand** (PoE) -- Brand attaches to an enemy, pulses damage, detaches on death, seeks a new target. The brand has its own lifecycle: DEPLOY -> ATTACH -> [PULSE -> PULSE -> PULSE...] -> target dies -> SEEK -> ATTACH -> repeat. It's an autonomous agent with its own looping phases.\n\n**Bear Trap** (PoE) -- Thrown to a location, sits dormant, triggers on enemy proximity. LAUNCH -> TRAVEL -> DEPLOY(dormant) -> [wait for trigger] -> IMPACT. The DEPLOY phase is indefinite and event-driven.\n\n**Heimerdinger Q -- Turret** (LoL) -- Places an autonomous turret that attacks enemies. The turret has its own indefinite LAUNCH -> TRAVEL -> IMPACT cycle, operating independently of the player.\n\n**Engineer -- TR12 Gauss Auto-Turret** (RoR2) -- Deployable turret that inherits all the player's items and fires autonomously. Same pattern as Heimerdinger but the turret's behavior is modified by the player's item loadout.\n\n**Raise Zombie / Summon Skeletons** (PoE) -- Creates permanent or duration-based minions that attack autonomously. The player's \"ability\" is to create an entity; that entity then has its own independent combat lifecycle.\n\n**C Section** (Isaac) -- Tears replaced with homing fetus projectiles that seek enemies, latch on, deal ticking damage, detach, seek the next enemy. Each projectile has a multi-target lifecycle: LAUNCH -> [TRAVEL(seek) -> ATTACH -> DAMAGE -> DETACH]_N. The projectile persists through multiple engagement cycles.\n\n**Lil Brimstone / Bob's Brain / Rotten Baby** (Isaac) -- Familiars that fire autonomously. Each has its own charge-fire-impact cycle independent of the player's input.\n\n**King Baby** (Isaac) -- Commands other familiars, switching them between modes. A meta-entity that controls other autonomous entities.\n\n**Mirage Archer** (PoE support) -- After using a bow skill, a ghost archer appears and continuously fires that skill autonomously. The support creates a persistent entity that has its own LAUNCH -> TRAVEL -> IMPACT cycle running independently.\n\n**Visitor's Echo** (Star of Providence) -- Creates a spectral ship that replays the player's actions with a 1-second delay. The echo is an autonomous entity whose behavior is defined by the player's past inputs. Its lifecycle is a time-shifted mirror of the player's actions.\n\n**Spectre R -- Haunt** (Dota 2) -- Creates an illusion next to every enemy hero on the map simultaneously, each attacking its nearby hero. Five autonomous entities created at once, each with independent combat. Plus the player can swap to any one of them.\n\n---\n\n## 8. DEFENSIVE / REACTIVE ABILITIES (Triggered by incoming events, not outgoing ones)\n\nThese abilities have no LAUNCH in the traditional sense. They're triggered by enemy actions.\n\n**Fiora W -- Riposte** (LoL) -- Blocks the next incoming attack/CC. If it blocked hard CC, stuns the attacker. The ability's IMPACT depends entirely on what the enemy does during the parry window. If the enemy does nothing, the ability has no effect. The lifecycle is: ACTIVATE(window) -> [enemy action] -> conditional IMPACT.\n\n**Sivir E -- Spell Shield** (LoL) -- Blocks the next enemy ability. The ability is entirely reactive -- it has no offensive lifecycle of its own. ACTIVATE -> [wait for enemy ability] -> BLOCK.\n\n**Yasuo W -- Wind Wall** (LoL) -- Creates a stationary wall that blocks all enemy projectiles. No LAUNCH/TRAVEL/IMPACT at all. It's a persistent terrain-like defensive object.\n\n**Nyx Assassin W -- Spiked Carapace** (Dota 2) -- Reflects the next damage instance back at the source and stuns them. The \"IMPACT\" of this ability is on the enemy who attacks Nyx. The ability's effect is defined by the enemy's action, not the player's.\n\n**Lotus Orb** (Dota 2 item) -- Any targeted spell cast on the buffed unit is reflected back at the caster. The item's \"ability\" is entirely defined by enemy inputs. It has no lifecycle of its own -- it mirrors enemy abilities.\n\n**Blade Mail** (Dota 2 item) -- All damage dealt to the user is reflected back. No phases at all -- it's a state that modifies incoming damage events.\n\n**Telekinesis** (Isaac) -- Freezes enemy projectiles, then redirects them at enemies. It captures enemy TRAVEL phase projectiles and repurposes them. The ability parasitizes enemy ability lifecycles.\n\n**Lost Contact** (Isaac) -- Tears destroy enemy projectiles on contact. The IMPACT phase isn't \"hit an enemy\" but \"neutralize an enemy projectile.\" Defensive use of an offensive delivery system.\n\n**Reflective keyword** (Star of Providence) -- Melee slash reflects enemy projectiles back as allied projectiles. Converts enemy TRAVEL-phase projectiles into player TRAVEL-phase projectiles. The ability hijacks enemy lifecycles.\n\n---\n\n## 9. STATE-BASED / TOGGLE / AURA (No discrete activation at all)\n\nThese have no phases because they're persistent states, not events.\n\n**Blasphemy** (PoE support) -- Converts a curse from a targeted cast into a persistent aura. No LAUNCH, no TRAVEL, no IMPACT. The curse just applies to everything nearby, always.\n\n**Drow Ranger Q -- Frost Arrows** (Dota 2) -- Toggle that adds slow to auto-attacks. It's a modifier on another ability's lifecycle, not an ability with its own lifecycle.\n\n**Huskar Q -- Burning Spear** (Dota 2) -- Each auto-attack adds a stacking DoT at the cost of HP. Same pattern -- modifies auto-attack IMPACT rather than having its own lifecycle.\n\n**Gnawed Leaf** (Isaac) -- Standing still makes Isaac invulnerable. No activation, no phases. It's a conditional state.\n\n**Vayne W -- Silver Bolts** (LoL) -- Passive: every third hit on the same target deals bonus true damage. No activation -- it modifies existing auto-attack impacts with a counter system.\n\n**Bloodseeker passive -- Thirst** (Dota 2) -- Gains bonuses based on global enemy health states. No activation, no target, no phase model. It's a passive that responds to the entire game state.\n\n**Rengar passive -- Unseen Predator** (LoL) -- Terrain-conditional auto-attack enhancement (leap from brush). The \"LAUNCH\" is conditional on terrain state rather than player activation.\n\n---\n\n## 10. RESOURCE ACCUMULATION / CONSUMPTION (Phases across multiple casts)\n\nThese abilities span multiple activations, making the lifecycle stretch across many casts rather than one.\n\n**Syndra R -- Unleashed Power** (LoL) -- Hurls all active Dark Spheres at a target. The \"LAUNCH\" phase includes all previous Q casts that created spheres. The lifecycle spans multiple ability uses: [Q -> create sphere]_N -> R -> TRAVEL(all spheres) -> IMPACT. The LAUNCH of R depends on the AFTERMATH of previous Qs.\n\n**Discharge** (PoE) -- Consumes all charges (accumulated from other sources over time). The ability's power is defined by a resource accumulated across many prior actions. No self-contained lifecycle.\n\n**Nasus Q -- Siphoning Strike** (LoL) -- Each kill with Q permanently increases future Q damage. The AFTERMATH of each cast (the permanent stacking) feeds into the IMPACT of all future casts. An inter-cast feedback loop.\n\n**Annie passive -- Pyromania** (LoL) -- Every 4th spell cast adds a stun to the next offensive ability. A counter system that modifies a future ability's IMPACT based on past casts. The \"stun\" phase doesn't belong to any single ability -- it's distributed across the counter.\n\n**Yasuo Q -- Steel Tempest** (LoL) -- Two hits build stacks, third cast becomes a tornado. The lifecycle of the tornado version spans three separate activations. LAUNCH1 -> IMPACT1(stack) -> LAUNCH2 -> IMPACT2(stack) -> LAUNCH3(tornado) -> TRAVEL -> IMPACT3(knockup).\n\n**Resonance Disc** (RoR2) -- Killing enemies charges a disc. When fully charged, the next hit fires the disc. The \"LAUNCH\" requires accumulated kills. The lifecycle bridges kill events (AFTERMATH of other abilities) into a LAUNCH event for this one.\n\n**Bloodlust keyword** (Star of Providence) -- Kills charge power for the next attack. Accumulated AFTERMATH from multiple kills feeds into a single amplified IMPACT.\n\n---\n\n## 11. META-ABILITIES / ABILITY MODIFICATION (Abilities that modify other abilities)\n\nThese don't have their own lifecycle -- they alter how other abilities flow through the 4-phase model.\n\n**Cast on Critical Strike** (PoE) -- When an attack crits, it auto-casts a linked spell. One ability's IMPACT triggers another ability's LAUNCH. Cross-ability phase chaining.\n\n**Cast when Damage Taken** (PoE) -- Taking cumulative damage triggers a spell cast. DAMAGE_RECEIVED -> auto-LAUNCH. The trigger is defensive, the result is offensive.\n\n**Spell Totem / Trap / Mine** (PoE supports) -- Fundamentally change WHO or WHAT performs the ability. The player's LAUNCH becomes: LAUNCH(deploy entity) -> entity performs its own [LAUNCH -> TRAVEL -> IMPACT] autonomously or on trigger. Adds an extra layer of indirection.\n\n**Rubick R -- Spell Steal** (Dota 2) -- Copies the last ability an enemy cast. The ability has no fixed lifecycle -- it dynamically assumes the lifecycle of whatever was stolen. It's a meta-ability whose phases are determined at runtime.\n\n**LeBlanc R -- Mimic** (LoL) -- Recasts the most recent basic ability. Same dynamic-lifecycle issue as Rubick.\n\n**Invoker -- Invoke System** (Dota 2) -- Ten different abilities accessed through orb combinations. The \"LAUNCH\" phase includes a combinatorial selection step (choosing orbs) before the actual ability is determined. The lifecycle has a pre-LAUNCH configuration phase.\n\n**Multidimensional Baby** (Isaac) -- Tears passing through this familiar are duplicated. It modifies another ability's TRAVEL phase by splitting it. The familiar is a phase-modifier positioned in space.\n\n**Tractor Beam** (Isaac) -- Forces all projectiles onto a single beam line regardless of their original trajectory. Overrides the TRAVEL phase of all other abilities.\n\n---\n\n## 12. SPATIAL MANIPULATION / NO DAMAGE (Abilities that don't fit because they don't deal damage or affect space itself)\n\n**Anivia W -- Crystallize** (LoL) -- Creates impassable terrain. No damage, no target to impact. LAUNCH -> terrain creation. The \"impact\" is on the geometry of the map itself.\n\n**Vengeful Spirit R -- Nether Swap** (Dota 2) -- Swaps positions with target. No damage, no travel. Two entities are simultaneously displaced. Both \"travel\" in opposite directions instantly.\n\n**Mordekaiser R -- Realm of Death** (LoL) -- Banishes both Mordekaiser and a target to a separate dimension. The ability doesn't follow LAUNCH -> TRAVEL -> IMPACT at all. It changes the rules of the game (who can interact with whom) for a duration.\n\n**Kindred R -- Lamb's Respite** (LoL) -- Creates a zone where nothing can die. No LAUNCH/TRAVEL/IMPACT -- it modifies the rules of damage resolution for everyone in the area.\n\n**Faceless Void R -- Chronosphere** (Dota 2) -- Freezes all units in an area except Void. The \"impact\" isn't damage -- it's a rules change (time stops for everyone else).\n\n**Bard R -- Tempered Fate** (LoL) -- Puts all units in an area into stasis regardless of allegiance. LAUNCH -> TRAVEL(projectile) -> IMPACT(stasis on everything). The IMPACT affects allies, enemies, towers, everything indiscriminately, and the \"effect\" is removing entities from the game temporarily.\n\n---\n\n## 13. DEATH-TRIGGERED / CONDITIONAL AFTERMATH CHAINS\n\nThese abilities have AFTERMATH phases that trigger new ability lifecycles on specific events.\n\n**Essence Drain** (PoE) -- If the DoT-afflicted target dies, the debuff spreads to a nearby enemy. AFTERMATH conditionally restarts itself on a new target. Death-triggered AFTERMATH -> TRAVEL(spread) -> new AFTERMATH.\n\n**Guppy Transformation** (Isaac) -- On kill, spawns attack flies. Flies kill enemies, which spawn more flies. AFTERMATH -> kill -> AFTERMATH -> kill -> chain reaction. The AFTERMATH phase is self-replicating.\n\n**Athame** (Isaac) -- On kill, a damage ring appears at the kill location. If that ring kills another enemy, another ring appears. AFTERMATH chains into AFTERMATH.\n\n**Ceremonial Dagger** (RoR2) -- On kill, fires homing daggers. If daggers kill, more daggers fire. IMPACT(kill) -> LAUNCH(daggers) -> TRAVEL -> IMPACT(kill) -> LAUNCH(more daggers)... Exponential lifecycle spawning.\n\n**Will-o'-the-Wisp / Gasoline** (RoR2) -- On kill, AoE damage at kill location. If the AoE kills neighbors, those deaths trigger their own AoE. AFTERMATH cascades.\n\n**Shatterspleen** (RoR2) -- Crits apply bleed. Bleeding enemies explode on death. Explosions can bleed neighbors. IMPACT(crit) -> AFTERMATH(bleed) -> death -> IMPACT(explosion) -> AFTERMATH(bleed on neighbors) -> chain.\n\n**Acrid -- Epidemic** (RoR2) -- Poison spreads on hit, and the spread chains to neighbors, who spread to their neighbors. IMPACT -> AFTERMATH(poison) -> spread -> new AFTERMATH -> spread... Infectious propagation.\n\n---\n\n## Summary of problem types for the 4-phase model:\n\nThe linear LAUNCH -> TRAVEL -> IMPACT -> AFTERMATH model fails to capture:\n\n1. **Looping** -- abilities that re-enter earlier phases (returning projectiles, chaining, bouncing)\n2. **Phase skipping** -- global damage with no travel/targeting\n3. **Continuous operation** -- beams, auras, toggles, orbitals with no discrete phases\n4. **Player-controlled transitions** -- recasts, detonates, charge-release where the player controls when phases advance\n5. **Phase transformation** -- projectiles that become turrets, melee hits that spawn projectiles, entity type changes\n6. **Concurrency** -- multiple phases happening simultaneously\n7. **Autonomous entities** -- minions, turrets, brands that have their own independent lifecycles\n8. **Reactive/defensive** -- abilities triggered by enemy actions rather than player actions\n9. **Stateless/persistent** -- toggles, passives, auras with no event-based lifecycle\n10. **Cross-cast accumulation** -- lifecycles spanning multiple activations (stacking counters, resource consumption)\n11. **Meta-abilities** -- abilities that modify other abilities' phases rather than having their own\n12. **Non-damage spatial effects** -- terrain creation, dimension shifts, rules modifications\n13. **Self-replicating aftermath** -- death-triggered chains where aftermath spawns new lifecycles"
},
{
"type": "text",
"text": "agentId: ab5a0cca0bc01b1d0 (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 60879\ntool_uses: 10\nduration_ms: 243426</usage>"
}
]
That's a massive list. Let me distill it into what actually matters for our system.
The key insight: most of these "breaks" are handled by the orb link system, not by the phase model. The phase model describes one ability instance's lifecycle. The orb system handles inter-ability interactions:
| Problem type | Handled by |
|---|---|
| Phase transformation (projectile → turret) | Links (Cast on Expire → new orb) |
| Autonomous entities (minions, brands) | Orbs ARE autonomous entities |
| Player-controlled transitions (recast, detonate) | Link triggers (Detonate bridge type) |
| Cross-cast accumulation (stacking charges) | Charge/energy system |
| Meta-abilities (modify other abilities) | Supports/modifiers on orbs/links |
| Reactive/defensive (triggered by enemy) | Orb activation types (Cast on Hit incoming) |
| Self-replicating aftermath (death cascades) | Links (Cast on Kill → fire same ability) |
| Stateless/persistent (toggles, auras) | Orb activation type (passive/toggle) |
That leaves only a few genuine challenges for the phase model itself:
- Looping — returning projectiles, chaining, bouncing cycle through TRAVEL↔IMPACT repeatedly
- Concurrency — beams have TRAVEL+IMPACT happening simultaneously
- Missing phases — some abilities skip phases (instant AoE has no TRAVEL)
But here's the thing: from a modifier-placement perspective, these don't actually break anything. The player doesn't need to know about loops or concurrency. They just need to know:
- "Homing" goes in TRAVEL — whether it's a projectile, a beam, or a chaining bolt, homing affects how the thing moves through space
- "Pierce" goes in IMPACT — it affects what happens when the thing hits
- "Nova" goes in LAUNCH — it affects the firing pattern
- Looping (chain, return) means the TRAVEL modifier applies to each iteration. The player doesn't need to reason about the loop — homing on a chaining projectile just makes each chain-hop curve. Intuitive.
- A missing phase just means those slots aren't shown
I also think 4 phases might be one too many. LAUNCH and TRAVEL are both "how the thing gets from me to the target." Consider 3 phases:
- Cast — how it spawns (pattern, quantity): nova, spreading, parallel, backshot, sideshots
- Path — how it travels (movement, behavior): homing, returning, pierce, chain, speed mods
- Effect — what it does on arrival (damage, areas, persistence): area size, cascade, echo, magnetic, duration, growing/shrinking, zone stacking
This maps cleanly to our existing modifiers. Every single one falls into exactly one of these three buckets without ambiguity. And 3 categories is very easy to grasp — "how it fires, how it moves, what it does."
The orb UI could literally show three sections/rings/zones on each orb. Abilities that lack a Path phase (like a self-centered nova) just don't show that section. Simple, visual, no tags needed.
What do you think — does 3 phases feel right, or is there value in keeping Impact and Aftermath separate?
OK, let's see this 3 phase system in action. Show me common abilities, abilities that showcase problems you outlined, and other more creative abilities too.
Agent
[
{
"type": "text",
"text": "The document has been created at `E:\\a327ex\\Orblike\\reference\\three_phase_system.md`.\n\nIt covers all four requested sections:\n\n1. **Common/Simple Abilities** -- 7 abilities (Fireball, Null Arrow, Lightning Bolt, Ice Nova, Basic Attack, Scorching Ray, Righteous Fire) showing clean phase mappings.\n\n2. **Abilities with Modifiers Applied** -- 4 modifier combinations using existing implemented modifiers (nova, homing, spreading_multiple_projectiles, area_cascade, growing_zone, chain, magnetic_area, returning, pierce), with tables showing exactly which phase each modifier attaches to.\n\n3. **Abilities That Tested the Phase Model** -- 13 problematic abilities (Spectral Throw, Tornado Shot, Ball Lightning, Scorching Ray, Blade Vortex, Anti-Gravity Tears, Discharge, Toxic Rain, Deployment Array, Drill, Detonator, Chain abilities, Bouncing projectiles), each with an explanation of why it works under the model and what ORBS concepts handle the edge case.\n\n4. **Creative/Exotic Combinations** -- 10 original ability concepts (Napalm Line, Artillery Barrage, Gravity Boomerang, Carpet Bomber Beam, Seeking Minefield, Projectile Absorber, Ricochet Amplifier, Echo Mine, Recursive Storm, Orbital Cannon, Singularity -- 11 total) with gameplay fantasy descriptions and phase breakdowns that reference ORBS bridge types, modifiers, and orb linking.\n\nThe document ends with a summary table mapping modifier types to their phase, and a note about the key insight: for beams, auras, and orbits, Path and Effect are concurrent but still conceptually distinct."
},
{
"type": "text",
"text": "agentId: ad3f2e015733e7fa0 (for resuming to continue this agent's work if needed)\n<usage>total_tokens: 60961\ntool_uses: 18\nduration_ms: 249230</usage>"
}
]
Bash (~/bin/neovim.exe "E:/a327ex/Orblike/reference/three_phase_system.md" -- -c "Mark...)
Command running in background with ID: b793h51vo. Output is being written to: C:\Users\adn\AppData\Local\Temp\claude\E--a327ex\tasks\b793h51vo.output
Opened the document in NeoVim with MarkdownPreview. It covers:
- 7 common abilities — clean phase breakdowns (fireball, null arrow, lightning bolt, ice nova, basic attack, beam, aura)
- 4 modifier combinations — showing how our existing modifiers slot into Cast/Path/Effect
- 13 problematic abilities — each with an explanation of why the 3-phase model handles it (returning, chaining, bouncing, beams, orbitals, player-controlled transitions, phase transformations)
- 11 creative ability concepts — exotic combos like trail-dropping projectiles, carpet bomber beams, recursive storms, gravity boomerangs
The main insight throughout: most "phase-breaking" abilities are actually handled by the orb link system, not the phase model. The phase model just needs to accept that Path and Effect can be concurrent (beams, orbitals) and that Path↔Effect can loop (chain, return, bounce). Take a look and let me know your thoughts.
For each ability, describe the fantasy, but be more brief on the cast, phase, effect description, since it's usually obvious. This entire thing reminds me of scrabble. Like, imagine an ability is made up of N pieces. Generally 3, Cast, Path, Effect. In each of those pieces there can be multiple linked modifiers. And then, usually, you want to link to other abilities on the effect. So you could imagine a board where you have these abilities linked and creating a visual stair-like thing, right? For instance...
Artillery Barrage
FAN 5 | Die at Distance | Cast on Death | Any area ability | | its modifiers keep adding down | | Cast on Kill | Another ability | its modifiers keep moving right |
And so on. In this case, shoot 5 projectiles that die after travelling a distance, once they die, they cast the area ability on death, the area ability deals its damage and if it kills, it casts the linked ability at its end and so on. Does this make sense?
Yes, that makes total sense. It's a spatial crossword/Scrabble layout where:
- Horizontal = one ability's lifecycle (Cast → Path → Effect)
- Vertical = links branching into new abilities
- Modifiers stack onto their phase slot
So you read left-to-right to understand what one ability does, and top-to-bottom to follow the chain reaction. The whole build is visible as a single connected grid.
Something like:
Fireball build:
NOVA 8 | HOMING | EXPLODE ──── Cast on Hit
FAST BIG AREA
MAGNETIC ZONE ──── Cast on Kill
GROWING
HOMING CHAIN LIGHTNING
...
The staircase naturally shows build depth/complexity. Simple builds are one short row. Complex builds cascade down and to the right. A player can immediately see "this fires 8 homing projectiles that explode, and the explosions spawn homing growing zones, and zone kills trigger chain lightning."
This also solves the readability problem we started with — you don't need tags or labels to understand compatibility because the physical structure constrains what goes where. A modifier is a tile that fits in a Cast, Path, or Effect column. If it doesn't apply, it just doesn't fit. Should I redo the reference document with this visual format and more brief descriptions?
No. Let's think about this more. Is there a way to make this visually more clear, do you think? This would replace the orb system with something visual, which makes sense to me. But the stair-like nature of it per-ability has me questioning to see if there aren't other ways to achieve this. Path of Exile's tree is visually interesting, there are many games with visually interesting systems like this that we might try to take inspiration for, even though our system is doing something that others aren't necessarily doing.
Let me think about what the visual system needs to communicate and then explore different approaches.
Core information:
- An ability has ~3 phases
- Modifiers attach to phases
- Abilities chain into other abilities via triggers
- The whole thing forms a compound behavior
Constraints from our game:
- Orbs already orbit the player — circular/orbital visual language exists
- It should be readable at a glance for simple builds, yet scale to complex ones
- Players need to both read AND compose builds
Some directions worth considering:
Vertical flow (waterfall) — Each ability is a horizontal row. Links drop downward. You read top-to-bottom to follow the chain. Avoids the diagonal staircase. Simple builds are one row, complex builds are tall columns. Like a waterfall or cascade.
┌─NOVA─8──┬──HOMING───┬──EXPLODE────┐
│ │ FAST │ BIG AREA │
│ │ │ MAGNETIC │
└─────────┴────────────┴──────┬──────┘
Cast on Hit
┌─────────┬────────────┬──────┴──────┐
│ AIMED │ HOMING │ ZONE │
│ │ │ GROWING │
└─────────┴────────────┴─────────────┘
Radial/orbital — Keeps the orb visual language. The ability sits at center. Cast modifiers in the inner ring, Path in the middle ring, Effect in the outer ring. Linked abilities are satellite clusters connected by lines. Your whole build looks like a solar system.
Node graph (Factorio-style) — Abilities are boxes with input/output ports. Modifiers are smaller nodes attached to the sides. Links are visible wires between output ports and input ports. Very clear data-flow reading but potentially cluttered.
Sentence/tile composition (Baba Is You-style) — This is the one that excites me most for our case. Each piece is a physical tile. You literally spell out your build by arranging tiles:
[FIRE] [NOVA 8] [HOMING] [EXPLODE] [ON HIT →]
[NULL] [ZONE] [GROWING] [ON KILL →]
[CHAIN]
It reads like a sentence. "Fire nova-8 homing explode; on hit, null zone growing; on kill, chain." The composition IS the reading. No abstraction layer between what you build and what you understand. And the Scrabble-like tile placement could be actual gameplay — you find tiles, place them on a board, rearrange them.
But I'm curious what pulls you. Is it the flow direction (horizontal vs vertical vs radial) you're questioning? The visual metaphor (tiles, nodes, orbits, tree)? Or something about how the composition interaction itself should feel?
<task-notification> <task-id>b793h51vo</task-id> <tool-use-id>toolu_01TDD1bLFobHyiTtX24wfirk</tool-use-id> <output-file>C:\Users\adn\AppData\Local\Temp\claude\E--a327ex\tasks\b793h51vo.output</output-file>
completed Background command "Open the 3-phase system document in NeoVim with MarkdownPreview" completed (exit code 0) </task-notification> Read the output file to retrieve the result: C:\Users\adn\AppData\Local\Temp\claude\E--a327ex\tasks\b793h51vo.output
Read (C:\Users\adn\AppData\Local\Temp\claude\E--a327ex\tasks\b793h51vo.output)
Empty output, NeoVim just opened and closed normally. Waiting on your thoughts about the visual direction.
I like the last one the most too. Let's try to do this one by doing some (do like 5) composed abilities, using the ones you described in the document you just created. Describe the behavior of the full ability in a sentence or two, then do this [X] [Y] system you just did visually. Do it in text here, don't create a new file or anything. Just 5 abilities composed of multiple smaller abilities described in that file.
1. Artillery Barrage — Fan of 5 projectiles that die at fixed range, each spawning an explosion on death.
[FAN 5] [DIE AT RANGE] [ON DEATH →]
[EXPLODE] [BIG AREA] [MAGNETIC]
2. Carpet Bomber Beam — Channeled beam that drops persistent damage zones along its length every 0.5s.
[CHANNEL] [BEAM] [SWEEPING] [TIMER 0.5s →]
[ZONE] [GROWING] [HOMING]
3. Recursive Storm — Homing projectile that explodes on hit, and each kill from the explosion fires a new homing projectile.
[AIMED] [HOMING] [EXPLODE] [ON KILL →]
[HOMING] [EXPLODE] [ON KILL → ↑ (self)]
4. Gravity Boomerang — Returning projectile that pierces everything, and each pierce drops a magnetic zone that pulls enemies in.
[AIMED] [RETURNING] [PIERCE] [ON PIERCE →]
[FAST] [ZONE] [MAGNETIC] [SHRINKING]
5. Seeking Minefield — Nova of 8 projectiles that each die at range and leave behind homing zones that chase enemies and explode on reaching them.
[NOVA 8] [DIE AT RANGE] [ON DEATH →]
[ZONE] [HOMING] [ON EXPIRE →]
[EXPLODE] [BIG AREA]
Hmmmm, [AIMED] doesn't make sense, and we need to differentiate between ABILITIES, MODIFIERS and TRIGGERS. Triggers seem to have a differentiator in ->, but abilities and modifiers are muddled.
You're right. Let me redo these with clear visual distinction. Maybe:
- ABILITIES in caps:
PROJECTILE,EXPLODE,ZONE,BEAM - modifiers in lowercase:
homing,fast,big area - Triggers with arrow:
on hit →,on kill →,on death →
And drop AIMED — the ability being a projectile already implies it goes where you aim.
1. Artillery Barrage
[fan 5] [PROJECTILE] [die at range] [on death →]
[EXPLODE] [big area] [magnetic]
2. Carpet Bomber Beam
[BEAM] [sweeping] [timer 0.5s →]
[ZONE] [growing] [homing]
3. Recursive Storm
[PROJECTILE] [homing] [EXPLODE] [on kill →]
[PROJECTILE] [homing] [EXPLODE] [on kill → ↑]
4. Gravity Boomerang
[PROJECTILE] [returning] [pierce] [on pierce →]
[fast] [ZONE] [magnetic] [shrinking]
5. Seeking Minefield
[nova 8] [PROJECTILE] [die at range] [on death →]
[ZONE] [homing] [on expire →]
[EXPLODE] [big area]
This is better. Does the cast path effect distinction matter at all here do you think?
Looking at the examples... not really? The reading order already communicates it naturally:
[nova 8] [PROJECTILE] [homing] [pierce] [on death →]
cast ability path effect trigger
The ability tile anchors the row, cast modifiers come before it, path/effect modifiers come after. You don't need labels because the meaning is obvious from context — "homing" next to "PROJECTILE" clearly means the projectile homes. "big area" next to "EXPLODE" clearly means bigger explosion. No one would be confused about which phase those belong to.
The phase distinction might still matter internally in code — for validation, preventing nonsensical combos. But as a player-facing concept, the tile layout makes it redundant. The tiles speak for themselves.
The only edge case I can think of is if a modifier could ambiguously belong to two phases. But in practice, "homing" is obviously about movement, "big area" is obviously about the effect. Can you think of a modifier where the phase ambiguity would actually confuse a player?
So this isn't functionally different from Path of Exile's linking system then, don't you think? With the exception that we can directly link to other abilities, whereas in PoE each is tied to equipment, or in PoE2 I believe they're each in their own row alone.
You're right, it's essentially the same thing. Ability + supports in a row, with triggers connecting rows. PoE2's system is almost exactly this layout — each skill has its own row of linked supports, and trigger gems connect between them.
The only real difference we've arrived at is presentation — reading the chain as a connected sentence/board rather than separate equipment pieces. That's a UI improvement, not a mechanical one.
So maybe the question isn't "how do we make a better linking system" (PoE's is already good mechanically) but rather: what does the orb system offer that gem sockets don't?
The orb system has some genuinely unique properties:
- Orbs are physical entities in the game world, orbiting the player
- They can collide with enemies (contact damage)
- Their position/orbit matters spatially
- The player character is "Orb Zero" — part of the same system
- Links between orbs are spatial relationships, not abstract socket connections
Maybe the visual system should lean into the physicality of orbs rather than trying to be a better version of PoE's flat gem layout. The interesting design space might not be in the linking UI at all, but in how orbs interact spatially during gameplay.
What's pulling you — is it the build-composition UI specifically, or are you reconsidering the orb system more broadly?
I'm reconsidering the orb system more broadly... but it seems we've reached just what PoE2 does? How does PoE2 link between abilities?
ToolSearch
[
{
"type": "tool_reference",
"tool_name": "WebSearch"
}
]
This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.
Summary: 1. Primary Request and Intent: - Remove FX visual effects drawing/switching code from Orblike and comment out FX loading - Design a tag system for abilities and modifiers (like PoE) to determine what can affect what - Go through each ability/modifier one-by-one to assign tags - Merge the duplicated
areaandzoneclasses into a singleareaclass - Implement homing sub-modifiers for persistent areas (formerly zones) - Rename all zone-related code references while keeping "zone" in player-facing modifier names - Reconsider whether a tag system is the right approach — explore alternative visual systems for players to understand ability composition - Explore a 3-phase system (Cast/Path/Effect) as an alternative to tags - Explore a tile/Scrabble-like visual composition system - Compare the resulting system to PoE2's linking system and reconsider the orb system broadly
Key Technical Concepts:
- Tag system: Abilities and modifiers have tags; shared tags determine compatibility
- Tag granting: Some modifiers add tags (e.g.,
homing_zoneaddshomingtag to an area)- Require/Add notation:
!tag= requires,+tag= adds, plaintag= affects but doesn't require (decided to handle in code, not player-facing)- Zone as composition: Zones aren't a separate type — they're areas with
duration+dottags- "Zone" in names, not tags: Player-facing modifier names keep "zone" (growing_zone), but the tag system uses
area+duration- 3-phase ability lifecycle: Cast (how it fires) → Path (how it moves) → Effect (what it does)
- Most "phase-breaking" abilities are handled by the orb link system (Cast on Hit, Cast on Kill, etc.), not the phase model
- Tile/Scrabble visual composition: Abilities as horizontal rows of tiles, with vertical links branching to sub-abilities
- Visual distinction: ABILITIES in caps, modifiers in lowercase, triggers with →
- PoE2 comparison: The tile system is functionally equivalent to PoE2's gem+support linking per skill slot
Files and Code Sections:
Orblike/main.lua
- Most critical file — the entire game codebase
- FX test code removed (fx_test_effect class, mouse4/mouse5 browser, label display)
- FX spritesheet loading wrapped in
--[[ ... --]]block comment- Ability definitions renamed:
explosion_radius→area_radius,zone_radius→area_radius,zone_duration→area_duration,zone_tick_rate→area_tick_rate,hit_sound/zone_sound→area_sound- Area and zone classes merged into single
areaclass (~lines 1046-1408 replaced)projectile:on_hitchangedzone(...)toarea(...)for caustic_arrowdamaged_by_zonerenamed todamaged_by_areaglobally- Test setup modifier names updated:
zone_stacking,homing_zone,greatly_increased_zone_speed,growing_zone- Internal variable
homing_zone_speedrenamed tohoming_area_speedfor consistency- Key merged area class structure:
lua -- Area carrier (instant explosion or persistent DoT) area = object:extend() function area:new(x, y, args) -- shared: radius, area size mods, cascade, visual setup self.duration = args.duration or (def and def.area_duration) if self.duration then -- persistent area: duration mods, tick damage, movement, homing with sub-mods, growing/shrinking -- homing sub-modifiers implemented via homing_steer_rate parameter: -- weaker_homing → steer_rate = 1.5 -- stronger_homing → steer_rate = 5 -- delayed/very_delayed_homing → delay before activation -- progressively_increasing/decreasing_homing → steer_rate ramps over time else -- instant area: immediate explosion damage, quick blink-and-die end end- Ability definitions after changes:
lua abilities = { fireball = { activation = 'manual', cooldown = 0.5, color = blue, fire_sound = 'fireball_fire', area_sound = 'fireball_hit', area_radius = 25, }, caustic_arrow = { activation = 'manual', cooldown = 0.5, color = purple, fire_sound = 'caustic_arrow_fire', area_sound = 'fireball_hit', area_radius = 33, area_duration = 4, area_tick_rate = 0.5, }, }Orblike/tags.md
- Created to track tag assignments for all abilities and modifiers
- Final state has 2 abilities and 37 modifiers tagged
- No
zonetag exists — persistent areas usearea+duration- Enemy section was removed (tags are only for abilities and modifiers)
Orblike/reference/three_phase_system.md
- Created by agent — comprehensive showcase of 3-phase system
- Covers common abilities, modifier combinations, problematic abilities, and creative/exotic combinations
- Opened in NeoVim with MarkdownPreview for user review
Orblike/orbs/system_design.md
- Read to confirm carrier type naming — "beam" is the established name
- Contains the full orb system design (links, bridge types, modifiers, etc.)
Orblike/orbs/ability_reference.md
- Read by agent to analyze abilities that break the 4-phase model
- Contains abilities from PoE, LoL, Isaac, RoR2, Nuclear Throne, Dota 2, Star of Providence
Errors and fixes:
greatly_increased_area_speedreplace_all failed: The earlierincreased_area_speed→increased_zone_speedreplace_all already caught it as a substring. Verified with grep that all zone_speed references were correct.- Internal variable naming inconsistency: User caught that
homing_zone_speedwas inconsistent with other internal variables likearea_vx,area_vy. Fixed by renaming tohoming_area_speed. The rule: internal variables usearea(matching the class name), player-facing modifier names usezone.- User corrected beam applicability: I initially proposed projectile speed mods and shot pattern mods with
projectile,beamtags. User said speed mods don't apply to beams, and shot pattern mods should be projectile-only for now (beam-specific ones can be added separately).- User corrected returning_arc: Initially set to
projectile,beambut user later said all returning/shot-pattern mods should beprojectileonly.- User corrected area size mod tags: I tagged them
area,zonebut user explained zones are always areas, soareaalone is sufficient (since zones have theareatag). Later the zone tag was eliminated entirely.Problem Solving:
- Zone/Area duplication: ~150 lines of duplicated code between area and zone classes. Solved by merging into single area class that branches on
self.duration.- Homing sub-modifiers for zones: Previously only implemented for projectiles. Solved by adding
homing_steer_rateparameter to persistent area homing, with weaker/stronger/delayed/progressive variants adjusting the steer rate.- Tag system readability: PoE's tag system felt opaque. Explored alternatives, arrived at 3-phase system (Cast/Path/Effect) and then a tile/Scrabble visual composition system. Ultimately recognized this is functionally equivalent to PoE2's linking system.
- "Zone" identity crisis: Resolved by keeping "zone" as vocabulary for player-facing names/tooltips while eliminating it as a tag. Zones are mechanically just areas with duration.
All user messages:
- "Hello. Remove the code from Orblike that's drawing the visual effects and that enables me to switch through them, we already finished that task and don't need it anymore. Also comment out (don't remove) the loading of all visual effects so that loading times aren't affected by effects we're not using."
- "Perfect, now lets work on the game's tag system. Like in path of exile, each ability, modifier, trigger, etc will have tags. Only those that share tags will be able to affect one another. So we should now decide on the tags for everything we already have implemented. Go through the codebase and gather a list of everything we have implemente in those terms and show it to me."
- "Let's go one by one. Tell me the next one and your proposed tags, I'll tell you back which ones it should have, and then you change this list somewhere. Let's keep it in a separate file called tags.md maybe?"
- "Missing only the activation method. The activation method implies the ability has a cooldown, since we decided that manual activation methods would be cooldown based while activation methods from linked orbs would be energy based. Do you think this is correct? manual should be a tag? There's the argument that if it's both used manually but also linked via an orb, it's both manual and triggered by the orb condition, right? In that case should it have both manual and whatever tag we end up using for triggered casts?"
- "OK, then fireball looks right."
- "Needs area too, since area modifiers also apply to zones. Lore-wise, there's no chaos damage, there's corrupted mana, which is what powers orbs (and thus orbs are used to clean up the world of corrupted mana). Corrupted mana spreads automatically and living beings can't interface with it directly, so it's functionally the same as 'chaos damage'. Although I'd like a better name than 'corrupted' or 'corruption' for the tag, so maybe a better name for corrupted mana in general. Any ideas?"
- "Yea, I like null. Null mana?"
- "Should be named null arrow? Nullification arrow? Something along those lines?"
- "OK."
- "It also makes sense for beams, lines?"
- "Sure."
- "Ah, should active vs. support be in the mods as well? In Path of Exile this is the case, although I'm not sure why."
- "Leave it out, also homing applies to beams as well. Brimstone/Technologies in Isaac are good examples of being affected by orbits, homing, and things that change pathing via the user of Bezier Curves to draw the beam line."
- "There's something we need to work out here. We have a homing_zone modifier, for instance, which adds the ability for a zone to seek enemies. In that case, all these homing modifiers should also work for zones. And of course, they also work for projectiles. Should these tags also have projectile, beam, zone into them? Or should homing_zone add the homing tag to a zone when its present?"
- "I feel like removing the concept of zone from tags and codebase is right, but I'm reaching for keeping it as a high-level descriptor because it captures the dot area idea better than just 'area'. But that feels like it'd be unnecessarily confusing for players."
- "Keep it in the titles too? growing_zone makes more sense than growing_area"
- "If area_vx, etc are details, then homing_zone_speed also needs to go back to being homing_area_speed. I don't know which is more right but they need to be consistent"
- "What's next?"
- "Yes." (multiple times confirming modifier tags)
- "Actually, let's have the earlier ones only apply to projectiles. We may add beam specific ones separately"
- "area, zone"
- "zones are always areas, so every modifier that has zone also will have area, the opposite is not true. area_cascade only applies to areas, but because all zones also have areas, it applies to zone creating abilities too. Does this seem too confusing? Do you think the area/zone distinction shouldn't be there?"
- "Should we get rid of 'zone' entirely and rename it to growing_area, but make it so it only affects areas with duration? I think it would be logically cleaner to have this distinction in the area object itself, because then we can have generic code for areas instead of having to copy it around for zones. But I haven't looked at both classes in a while."
- "Redo this now, also do the previous to_implement task of implementing the homing modifiers to zones (now areas with duration)."
- "These do not apply to beams"
- "returning_arc may apply to beams too"
- "Yes." (confirming area size modifier tags)
- "It also makes sense for beams, lines?" (about pierce)
- "area, force. force should be present whenever we have force-like behaviors that forcibly change enemy's positions"
- "We will have movement abilities eventually, right? Like, say, a dash. Those will likely have a movement tag. Should this ability be coded, tag-wise, as adding the movement tag to the area? Can you see how movement modifiers might benefit both movement abilities and moving zones?"
- "I feel like removing the concept of zone from tags and codebase is right..." (discussed zone naming)
- "I like the last one the most too. Let's try to do this one by doing some (do like 5) composed abilities, using the ones you described in the document you just created. Describe the behavior of the full ability in a sentence or two, then do this [X] [Y] system you just did visually. Do it in text here, don't create a new file or anything. Just 5 abilities composed of multiple smaller abilities described in that file."
- "Hmmmm, [AIMED] doesn't make sense, and we need to differentiate between ABILITIES, MODIFIERS and TRIGGERS. Triggers seem to have a differentiator in ->, but abilities and modifiers are muddled."
- "This is better. Does the cast path effect distinction matter at all here do you think?"
- "So this isn't functionally different from Path of Exile's linking system then, don't you think? With the exception that we can directly link to other abilities, whereas in PoE each is tied to equipment, or in PoE2 I believe they're each in their own row alone."
- "I'm reconsidering the orb system more broadly... but it seems we've reached just what PoE2 does? How does PoE2 link between abilities?"
- Various "Yes." and "Sure." confirmations throughout the tag assignment process
- "OK, let's see this 3 phase system in action. Show me common abilities, abilities that showcase problems you outlined, and other more creative abilities too."
- "For each ability, describe the fantasy, but be more brief on the cast, phase, effect description, since it's usually obvious. This entire thing reminds me of scrabble. Like, imagine an ability is made up of N pieces..." (the Scrabble/tile composition idea)
- "No. Let's think about this more. Is there a way to make this visually more clear, do you think? This would replace the orb system with something visual..."
- "Let's step back and think things through. Given the orb system, do you think there's a better system than a tag one to handle these combinations for the player?..."
- "OK, let's see this 3 phase system in action..."
- "Are there abilities that break the launch travel impact aftermath system? Check the research document full of abilities from various games to try to find ones that don't fit..."
- "Hmmmm, should this even be visible to the player? It seems like it's just making things more confusing, right?" (about require/add notation)
- "Let's just leave this for now, let's just add to_implement that we need to implement homing modifiers for homing_zone"
- "You can delete that entry, this is just for abilities and modifiers" (about enemies section in tags.md)
Pending Tasks:
- The
movementtag unification question is deferred (whether moving_zone, homing_zone, dashes, and projectile pathing should share amovementtag)- The require/add (
!/+) rules need to be implemented in code eventually- The broader question of whether the orb system should be reconsidered is actively being explored
- The ability name "caustic_arrow" has not been renamed to "null_arrow" in the actual game code (only in tags.md)
Current Work: The conversation has arrived at a fundamental design crossroads. The user was exploring alternative visual systems for ability composition and arrived at a tile/Scrabble-like system where:
- ABILITIES are in caps:
[PROJECTILE],[EXPLODE],[ZONE],[BEAM]- modifiers are in lowercase:
[homing],[fast],[big area]- Triggers use arrows:
[on hit →],[on kill →],[on death →]- Horizontal = one ability's lifecycle, vertical = links branching to sub-abilities
The user then recognized this is "functionally not different from Path of Exile's linking system" and asked how PoE2 links between abilities. A web search was performed showing PoE2 moved gems to character sockets with up to 5 support sockets per skill. The user's last statement before the summary request was "I'm reconsidering the orb system more broadly... but it seems we've reached just what PoE2 does? How does PoE2 link between abilities?" — they were in the middle of evaluating whether their system offers something genuinely different from PoE2.
Optional Next Step: The user was in the middle of reconsidering the orb system broadly after realizing the tile composition system mirrors PoE2's linking. The natural next step is to continue this design discussion — specifically addressing what the orb system offers that's genuinely different from PoE2's gem socket system. The user's exact words: "I'm reconsidering the orb system more broadly... but it seems we've reached just what PoE2 does?" — the PoE2 search results had just been delivered but the user hadn't yet responded to them before requesting this summary.
If you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: C:\Users\adn.claude\projects\E--a327ex\37eb9c54-8d92-4beb-9c86-07f4a56846aa.jsonl Continue the conversation from where it left off without asking the user any further questions. Resume directly — do not acknowledge the summary, do not recap what was happening, do not preface with "I'll continue" or similar. Pick up the last task as if the break never happened.
Read (E:\a327ex\Orblike\orbs\system_design.md)
1→# ORBS System Design
2→
3→The Orb composition system. Each orbiting orb IS an ability. Orbs connect to each other via links, creating trigger chains. Modifiers customize individual orbs. The system has no position-ordering — composition is a graph, not a sequence.
4→
5→---
6→
7→## Orb Zero: The Player
8→
9→The player is Orb Zero — a special orb that always exists, with the player character as its carrier. The player-carrier has events just like any other carrier:
10→
11→| Player carrier event | Analogous to | Description |
12→|---------------------|-------------|-------------|
13→| Hit (outgoing) | Cast on Hit | Player's body contacts an enemy (contact damage) |
14→| Hit (incoming) | Cast on Hit | Player takes damage from any source |
15→| Crit (outgoing) | Cast on Crit | Player deals a critical hit (any source) |
16→| Kill | Cast on Kill | Player kills an enemy (any source) |
17→| Death | Cast on Death | Player dies |
18→| Movement | Timer / threshold | Player covers X distance |
19→| Dodge/Evade | — | Player avoids an incoming attack |
20→| Low Health | — | Player HP drops below a threshold |
21→| CC'd | — | Player is stunned/rooted/silenced |
22→
23→Links from Orb Zero to other orbs use these events as bridge types, just like links from any other orb. "Cast when Damage Taken → Fireball" = Link from Orb Zero with bridge type Hit (incoming) → Fireball orb.
24→
25→This unifies the system: **all conditional activations are links with bridge types.** There is no separate "trigger" category for player events. The only intrinsic activation modes on an orb are:
26→
27→- **Manual** — player chooses to activate via input
28→- **Passive** — always active, no activation event
29→- **Periodic** — internal timer, fires automatically at intervals
30→
... [386 more lines]
So the key question on the table: how is the orb system different from PoE2's linking, and is there something genuinely distinct being offered?
Having re-read the system design, I think there are several real differences, though they exist on a spectrum:
What PoE2 does:
- Each skill gem gets its own socket with up to 5 support gem slots
- Supports only modify the skill they're socketed with (payload isolation — same as orbs)
- Trigger gems exist (Cast on Crit, Cast when Damage Taken) but they replace the skill's activation, they don't add an activation source
- No inter-skill linking — skills are independent silos
What the orb system does differently:
Graph topology, not isolated slots. PoE2's model is a flat list of independent skills, each with their own supports. The orb system is a directed graph — orbs connect to each other. This is the fundamental structural difference. In PoE2, Fireball and Ice Nova are completely independent. In the orb system, Fireball → Cast on Hit → Ice Nova is a first-class composition. The Frostbolt + Ice Nova example (Cast at Carrier) is something PoE2 hardcodes per skill rather than making composable.
Multiple activation sources per orb. An orb can be self-cast AND link-triggered AND triggered from Orb Zero. In PoE2, a skill is either self-cast or trigger-supported, not both simultaneously. The orb system lets the same ability serve multiple roles in the build.
Modifiers on links vs. on orbs. PoE2 only puts supports on skills. The orb system lets you put modifiers on the connection — so the same ability behaves differently depending on how it was activated. Formation(nova) on the Cast on Hit link but not on the orb means link-triggered = nova pattern, self-cast = single target.
Bridge type diversity. PoE2 has a handful of trigger supports. The orb system has 12+ bridge types including Timer (flying bomber pattern), Cast at Carrier (Frostbolt+Nova pattern as general-purpose), Detonate, Cast on Transition, etc. Many of these are hardcoded interactions in PoE2 rather than composable.
Orb Zero. The player-as-orb concept unifies "when I get hit" / "when I dodge" / "when I move" triggers into the same link system used between abilities. PoE2 has CWDT as a special support gem — the orb system makes it structurally identical to Cast on Hit from a projectile.
Physical/lore integration. Orbs are objects in the world, tied to the null mana lore. They orbit the player visually. Finding an orb with an interesting link configuration is a discovery event, not a menu operation.
Where the overlap is real:
- Payload isolation (modifiers don't bleed between skills/orbs) — same in both
- Support gems ≈ orb modifiers — functionally equivalent
- The within-a-single-ability modification is basically the same
The honest assessment:
The orb system is PoE2's system plus an inter-ability linking graph. That's not a small addition — it's the difference between "I have 5 independent skills" and "I have a web of abilities that trigger and feed into each other." But it's an evolutionary step from PoE2, not a paradigm shift.
The tile/Scrabble visualization you were designing earlier is actually a good way to represent this. Each horizontal row is one orb (ability + its modifiers), which maps directly to PoE2's per-skill socket group. The vertical links between rows — those are what PoE2 doesn't have and what makes the orb system distinct. The graph connections are the differentiator.
So the question becomes: is the inter-ability linking graph enough of a difference to justify the system's complexity? Or should the orb system lean harder into what makes it different — the graph topology, the multiple activation sources, the link-scoped modifiers — and downplay the parts that are just "PoE2 but with orbs"?
Let's forget the orb system for now. Let's just imagine the ability system and the UI representation of it, ignoring any manifestations in game reality. If we have a system where each ability is linked like PoE, where supports can support it, but additionally we can link to other abilities supported by their own supports via triggers, do we get something significantly different and do we have a clean way of representing this visually in an UI sense?
Yes, it's significantly different, and the difference is clear to articulate:
PoE2: 5 independent skill islands. Each skill has supports. Skills never interact.
This system: Skills can trigger other skills, and each triggered skill has its own full support loadout. This creates chains, branches, and fan-in.
That's not a small delta. In PoE2, if you want Cast on Crit → Ice Nova, Ice Nova shares the support group with your main skill and the trigger gem. It's a second-class citizen. Here, the triggered Ice Nova gets its own full set of supports. And Ice Nova can itself trigger a third ability, also fully supported. And multiple abilities can all trigger the same target ability. None of this exists in PoE2.
The UI problem, though, is real. The moment you allow inter-ability connections, you're representing a graph, and graphs are notoriously hard to display cleanly. PoE2's UI is simple because skills are independent — it's just a list of rows.
The question is whether the graph stays small enough to be visually clean. In practice, a build probably has:
- 3-5 abilities
- Each with 2-5 supports
- 2-4 trigger connections total
That's a small flowchart, not a neural network. For the common case — linear chains and simple branching — it's very clean:
[FIREBALL] [homing] [multi-shot]
↓ on hit
[EXPLOSION] [bigger area] [fire damage]
↓ on kill
[CHAIN LIGHTNING] [longer chain]
Branching:
[FIREBALL] [homing] [multi-shot]
├─ on hit → [EXPLOSION] [bigger area]
└─ on kill → [CHAIN LIGHTNING] [longer chain]
Two sources feeding one ability:
[FIREBALL] [homing] [BEAM] [sweeping]
↓ on hit ↓ on hit
└──────────┐ ┌──────────────┘
↓ ↓
[EXPLOSION] [bigger area]
That last one is where it starts getting visually messy. Fan-in creates crossing lines, and once you allow cycles (A triggers B triggers A, energy-throttled), you can't even represent it as a tree anymore.
So the honest answer is: the system is genuinely different and interesting, but the UI cleanliness depends entirely on what topological constraints you impose. There's a spectrum:
| Constraint | UI complexity | Expressiveness |
|---|---|---|
| Linear chains only (A→B→C) | Trivial — vertical list | Low |
| Trees (branching, no fan-in) | Clean — indented flowchart | Medium |
| DAGs (branching + fan-in, no cycles) | Manageable — node graph lite | High |
| Arbitrary graphs (cycles allowed) | Full node editor (Blender/Unreal) | Maximum |
The sweet spot for a game (not a programming tool) is probably trees, maybe DAGs. That gives you the interesting compositions — multi-stage abilities, branching trigger chains — without needing a node editor. And it's visually representable as a top-down flowchart where each row is an ability strip with its supports.
The thing I'm uncertain about: is the tree/DAG constraint too limiting? The self-referencing loop (projectile → Cast on Expire → fires another projectile) and the fan-in (multiple abilities feeding one target) are some of the most interesting compositions in the system design doc. Cutting those cuts real expressiveness. But allowing them pushes you toward a node editor UI, which is a very different game feel than PoE2's clean socket rows.
What's your instinct on where to land on that spectrum?
We can probably create a version with arbitrary graphs that feels intuitive to use. Or we could constrain things by having it be arbitrary graphs, but where the nodes and paths are preallocated, like in Path of Exile's skill tree, except each node is a slot where something can go into.
The pre-allocated graph idea is much stronger, I think.
The free-form graph can be made intuitive — Factorio's circuit network, Noita's wand editor, shader node editors all prove graphs can work in games. But they all feel like tools. You're engineering, not playing. That's fine for those games, but it's a different vibe from PoE's "socket your gems and go."
The pre-allocated structure solves the UI problem by dissolving it. The player never draws connections — they fill slots on a shape that already exists. The visual is always clean because the designer controlled the layout. And it introduces something genuinely interesting: the topology itself is content.
Picture a board like:
[ability]─on hit─→[ability]─on kill─→[ability]
╎ ╎ ╎
[mod] [mod] [mod]
[mod] [mod]
Another board, different shape:
[ability]─on hit─→[ability]
│
└──on kill──→[ability]
╎
[mod]
[mod]
[mod]
A fan-in board:
[ability]─on hit─┐
├→[ability]
[ability]─on hit─┘ ╎
╎ [mod]
[mod] [mod]
[mod] [mod]
A looping board:
[ability]─on expire─→[ability]
↑ │
└────on hit──────────┘
╎
[mod]
Each board is a fixed graph with ability slots, modifier slots, and typed trigger connections. The player's job is filling the slots, not designing the graph. Finding a board with an interesting topology — that's loot. A board with a rare fan-in structure, or an unusual trigger type, or 4 mod slots on one node — that's exciting in the same way finding a good rare item is exciting.
This does several things at once:
Sidesteps the UI problem. The graph is pre-laid-out by designers. It's always readable because it was designed to be.
Constraint breeds creativity. You find a board with on expire → on hit → loop and think "what abilities exploit this shape?" That's a puzzle — fitting your abilities into a topology you didn't choose. PoE's passive tree works for exactly this reason: navigating a fixed structure forces tradeoffs.
Topology as balance lever. Powerful topologies (deep chains, fan-in, loops) can be made rare. Simple topologies (two abilities, one trigger) are common. You don't need to balance arbitrary compositions because the designer controls what topologies exist.
Discovery. Finding a board you've never seen before and realizing "wait, this enables something wild" is a moment that free-form building doesn't produce. In free-form, everything is always possible — there's no discovery, only optimization.
The downside is reduced freedom. A player might want A→B→C but their best board only has A→B with a branch. But I think that's actually good — it means builds are shaped by what you find, not just what you theorize. And if boards are tradeable/craftable/farmable, hunting for the right topology becomes its own goal.
The thing I'm less sure about: should the trigger types on connections be fixed ("this connection is always on-hit") or should they also be slottable ("this connection exists, you choose what trigger type fills it")? Fixed triggers make each board more distinct and constrained. Slottable triggers give more freedom but reduce the board's identity — at that point the topology is just "how many connections," not "what kind of connections."
They should also be slottable generally, although some boards might come with them pre-made. I think the pre-made ones should have higher energy by default, while flexible ones should have less.
That's a clean solution. Pre-made trigger connections are stronger but rigid, flexible ones are weaker but adaptable. It creates a genuine tradeoff: do you want a board that perfectly fits one build at high power, or a board that fits many builds at lower power?
And it adds another axis to board identity. Two boards with identical topology could feel completely different — one with all pre-made triggers (specialist), one with all flexible slots (generalist), one mixed. The same shape becomes multiple items.
The energy distinction also makes intuitive sense from a player's perspective. A connection that's "built for" on-hit feels like it should be more robust than a generic socket you jammed a trigger into. It's the difference between a purpose-built machine and a jury-rigged one.
So a board now has three types of slots:
- Ability slots — socket any ability
- Modifier slots — socket any compatible modifier, attached to a specific ability slot
- Trigger slots — either pre-made (fixed type, high energy) or flexible (player chooses type, low energy), connecting two ability slots
And a board in full might look something like:
[ability]══on hit══→[ability]───( )───→[ability]
╎ ╎ ╎
[mod] [mod] [mod]
[mod] [mod]
Where ══on hit══ is a pre-made connection (thick/strong, high energy) and ───( )─── is a flexible slot (the player drops in a trigger type, lower energy).
Does this feel like the right set of slottable elements, or are there other things on the board that should be variable too? For instance, should the number of modifier slots on a node also vary per board (some nodes have 1 mod slot, others have 4), or should that be uniform?
Yes, nodes may vary between maybe 0 and 6 mod slots, 5-6 being the rarest. I think this system is as good as it will get. As an added bonus, each "board" can correspond to an orb cleanly. However, this removes the orb-linking idea entirely. Do you agree or disagree?
Agree. The board system subsumes orb-linking completely. Everything orb-linking did — trigger chains, multi-stage abilities, branching — now lives inside a single board. There's no need for a second layer of graph on top.
And it's cleaner for it. The old system was a graph of graphs — orbs had modifiers internally, then orbs linked to each other externally. Two levels of composition. The board collapses that into one: the board IS the graph, and each orb is one board. One level of composition, self-contained.
Cross-orb synergy doesn't disappear, it just moves to the right mechanisms:
- Charges — shared state pool, orbs produce/consume without direct links
- Exert — temporal modifier buffering across orbs
- Global modifiers — affect all orbs matching a condition
These are all indirect synergies, which is correct. Direct trigger chains belong inside a board. Indirect systemic synergy belongs between boards. Clean separation.
The one thing that needs resolving is Orb Zero / player-event triggers. "Cast when damage taken," "cast on dodge," etc. In the old system these were links from the player-orb to other orbs. With boards, I think these become a type of trigger input on the board — some boards have a player-event source node:
(on damage taken)══→[ability]───( )───→[ability]
╎ ╎
[mod] [mod]
[mod]
Where the leftmost source isn't an ability slot but a pre-made player-event input. That makes "defensive boards" a category of loot — boards with player-event inputs are inherently suited for reactive builds.
Does that feel right for handling player-event triggers, or do you see them working differently?
Absolutely. Any other insights you have about the system before we formalize it into a document?
A few things worth surfacing:
Board size as rarity curve. A 1-ability, 2-mod board is a common drop — it's just a skill with two supports. A 4-ability, 12-mod, mixed-trigger board is build-defining legendary loot. This means early game is naturally simple (small boards, straightforward abilities) and complexity scales with progression. Players learn the system incrementally by finding bigger boards, not by being presented with the full system upfront.
Modifier slot compatibility. Right now modifier slots are generic — any mod in any slot. But some boards could have typed modifier slots that only accept certain tags. A mod slot marked with a fire icon only accepts fire modifiers. This adds another axis to board identity and prevents every board from being equally flexible. Though this might be overcomplicating things — worth deciding whether it adds enough to justify the cognitive load.
The "activation" question. In the old orb system, each orb had its own activation mode (manual, passive, periodic). With boards, which ability on the board is the "entry point"? Options:
- The leftmost/topmost ability slot is always the manually activated one, everything downstream is trigger-driven
- Each ability slot on the board can independently have an activation mode
- The board itself has one activation mode that applies to its root ability
I think the first option is cleanest. One entry point, everything else cascades from triggers. If you want two manual abilities, that's two orbs (two boards). This preserves the "one orb = one button press" mental model.
Ability reuse across boards. Can the same ability gem go in multiple boards simultaneously, or is it consumed by one? If consumed: finding duplicate ability gems matters, builds compete for resources. If reusable: purely about topology and modifiers, abilities are unlocked once and used freely. Very different game feel.
Empty slots as intentional design. A board with 3 ability slots but you only fill 2 should be valid. The empty slot's triggers just never fire. This means players can use boards "partially" — a complex board found early can still be useful even if you only have one good ability to put in it. You grow into the board over time.
Board modification/crafting. Can players modify boards? Adding a mod slot, rerolling a trigger type, converting a flexible connection to pre-made? If yes, boards become craftable endgame items. If no, they're purely found. PoE's crafting system is one of its deepest hooks — board crafting could serve the same role here.
Visual language. The board needs to communicate a lot at a glance: which slots are abilities vs. modifiers, which connections are pre-made vs. flexible, trigger types, energy levels, compatibility restrictions. This is a graphic design problem more than a game design problem, but getting it wrong makes the whole system feel opaque. The PoE passive tree works partly because its visual language (node size, color, connection lines) is extremely consistent.
I think the biggest open question is the activation/entry point one — it shapes how players think about each orb and how many "buttons" a build has. Everything else feels like parameters that can be tuned.
Leftmost is the first ability, but it may not always be manual, some abilities may have "auto" for instance, which would trigger them automatically on a timer. We may also have boards where the leftmost ability gets automatically turned into another activation mode like auto or something else.
Abilities should be resources so no reuse.
Yes, this system naturally generates board modification as a resource sink for the game.
I feel I have the visual language handled for this and it's going to be somewhat consistent across the game.
If you have no further questions, please generate a document named orblike_ability_system.md at the top level folder describing our decisions for the system and covering all angles possible for it. If you have questions that must be answered while writing the document please ask me and don't add a "questions to be answered" or similar at the end of the document.
ToolSearch
[
{
"type": "tool_reference",
"tool_name": "AskUserQuestion"
}
]
AskUserQuestion
User has answered your questions: "A few things I want to clarify before writing. First: Orb Zero (player-event triggers like 'on damage taken', 'on dodge'). Should these be board features (some boards have player-event input nodes baked in), or should player-event triggers be usable as trigger slot fillers on any flexible connection?"="Both", "Second: charges, exert, and global modifiers — the cross-orb synergy mechanisms from the old system. Should the document include these as-is, or are you rethinking them now that the board system changes the picture?"="Include as-is", "Third: should the document cover board crafting/modification in detail, or just acknowledge it exists and defer specifics?"="Sketch out". You can now continue with the user's answers in mind.
Read (E:\a327ex\Orblike\orbs\system_design.md)
190→## Shared State: Charges
191→
192→Some orbs can **produce** charges as a byproduct of their events. Other orbs can **consume** charges for conditional bonuses. Charges are a shared communication channel between orbs that don't need direct links.
193→
194→Example:
195→- Orb A: Projectile, produces Power Charge on crit
196→- Orb B: Beam, consumes 5 Power Charges for bonus damage burst
197→
198→No link between A and B needed. They communicate through the shared charge pool. This enables emergent composition — any charge-producing orb implicitly powers any charge-consuming orb.
199→
200→---
201→
202→## Exert (Temporal Modifier Buffering)
203→
204→Some orbs can produce temporary modifiers that apply to the NEXT N activations of other orbs (PoE 2's Warcry Exert system). The modifier is stored in a buffer and consumed by future orb activations.
205→
206→Example:
207→- Orb A (Warcry): When activated, produces "next 5 orb activations gain +Fire damage"
208→- Orb B: Projectile — its next 5 shots gain fire damage
209→- Orb C: Beam — if activated before B consumes all 5, it also gets fire damage
210→
211→The buffer is shared across all orbs. First N activations of ANY orb consume one charge each. This creates timing decisions: activate the buff, then spam your strongest ability to consume all charges.
212→
213→---
214→
215→## How It Maps to Every System We Analyzed
216→
217→### PoE support gems → Orb modifiers
218→"Add GMP to Fireball" = add Multi (fan) modifier to a Projectile orb.
219→
... [197 more lines]
Read (E:\a327ex\Orblike\ORBS.md)
File does not exist. Note: your current working directory is E:\a327ex.
Glob (Orblike/ORBS.md)
No files found
Read (E:\a327ex\Orblike\orbs\ability_design_reference.md)
<persisted-output>
Output too large (52.9KB). Full output saved to: C:\Users\adn\.claude\projects\E--a327ex\37eb9c54-8d92-4beb-9c86-07f4a56846aa\tool-results\toolu_018nzBxib2rE7Co1PUK2UDgS.txt
Preview (first 2KB):
1→# ORBS Ability Design Reference
2→
3→High-level building blocks for describing abilities. An ability is defined by choosing a carrier and one entry from each building block table. Some abilities combine multiple entries (noted with +).
4→
5→Detailed ability descriptions: `ability_reference.md`
6→
7→---
8→
9→## Carriers — The fundamental object of the ability
10→
11→The carrier is what the ability creates in the game world — the entity or phenomenon that delivers effects to targets. Different carriers have different **modifier surface area**: the number of meaningful ways they can be changed by modifiers. Higher surface means more build diversity potential.
12→
13→**Design principle:** The most compositionally rich abilities deviate from "standard" carrier defaults on 1–2 axes while keeping other axes standard. The standard axes are where modifiers attach; the deviation is what gives the ability its identity.
14→
15→| Carrier | Description | Composability | Examples |
16→|---------|-------------|---------------|---------|
17→| Projectile | Discrete entity traveling through space | Very High — trajectory, count, and hit behavior all independently modifiable | Fireball, Ezreal Q, Sven Storm Hammer |
18→| Zone | Persistent region at a location, ticks over time | High — tick rate, duration, stacking, spatial mods | Caustic Arrow, Teemo mushroom, Gasoline |
19→| Trap | Dormant zone that activates on a condition | High — zone properties plus conditional activation layer | Techies mines, PoE traps/mines, Teemo shroom |
20→| Area | Instant region of effect at a point | High — inherent multi-hit, size/shape/cascade mods | Crystal Nova, Centaur Stomp, Reverse Polarity |
21→| Melee | Close-range hit or swing | Medium-High — attack speed × on-hit, splash is transformative | Nasus Q, Loader punch, Mercenary Laser Sword |
...
</persisted-output>
Read (E:\a327ex\Orblike\orbs\ability_design_reference.md)
50→This table documents both categories for reference. When classifying abilities from other games, most "triggers" map to links from Orb Zero.
51→
52→**Intrinsic activation modes** (orb properties):
53→
54→| Activation | Description | Examples |
55→|------------|-------------|---------|
56→| Manual | Player activates via input | Fireball, Ezreal Q, Cyclone, Brimstone |
57→| Passive | Always in effect with no activation | Vayne W, Bloodseeker Thirst, Kalista Martial Poise, Drow Frost Arrows, Gnawed Leaf |
58→| Periodic | Fires automatically on a fixed timer | Storm Brand pulses, Engineer turrets auto-fire, Lil Brimstone charge cycle |
59→
60→**Event-based activation** (links from Orb Zero or other orbs):
61→
62→These are documented here for classification reference. In the ORBS system, each of these is a link with a bridge type, not an orb property.
63→
64→| Event | Bridge type (from Orb Zero) | Examples |
65→|-------|---------------------------|---------|
66→| On-hit (outgoing) | Cast on Hit | Ukulele, ATG Missile, Sticky Bomb, Brilliant Behemoth, Needletick |
67→| On-kill | Cast on Kill | Will-o'-the-Wisp, Gasoline, Ceremonial Dagger, Guppy, Athame, Frost Relic |
68→| On-crit | Cast on Crit | Predatory Instincts, Harvester's Scythe, Cast on Critical Strike |
69→| On-damage-taken | Cast on Hit (incoming) | Cast when Damage Taken, Medkit, Holy Mantle, Tougher Times, The Polaroid |
70→| On-damage-taken (threshold) | Cast on Hit (incoming) + Accumulation modifier | Cast when Damage Taken (PoE, damage threshold scales with gem level) |
71→| On-low-health | Cast on Low Health | Abaddon R auto-trigger, Crown of Light (HP full, inverse), Adrenaline (PoE) |
72→| On-dodge/evade | Cast on Dodge | — |
73→| On-CC | Cast on CC | Mercury Treads cleanse, Olaf R |
74→| On-movement (threshold) | Cast on Movement | Flame Dash trail (PoE, partially), Kite (EBB) |
75→| On-death | Cast on Death | Dead Cat, 1UP, Dio's Best Friend, Zilean R buff, Lazarus' Rags |
76→| Proximity | Cast on Hit (incoming, range-based) | Bear Trap, Teemo mushrooms, Techies mines, Engineer Pressure Mines |
77→| Accumulation | Accumulation modifier on link | Annie passive (4 spells), Yasuo Q (3rd hit), Jhin passive (4th shot), Lead Pencil (every 15 tears) |
78→| Conditional | Filter modifier on link | Runald's/Kjaro's Band (damage threshold), Crown of Light (HP full), Rengar (in brush) |
79→| Reactive | Cast on Hit (incoming) + Filter (direct attack only) | Fiora Riposte, Sivir Spell Shield, Nyx Spiked Carapace, Blade Mail, Bot Fly |
... [522 more lines]
Now I have a thorough understanding of the full system. Let me write the document.
Write (E:\a327ex\Orblike\orblike_ability_system.md)
File created successfully at: E:\a327ex\Orblike\orblike_ability_system.md
Document written to Orblike/orblike_ability_system.md. It covers:
- Board structure — the three slot types (ability, modifier, trigger), connections, pre-made vs. flexible
- Player-event inputs — both as board features and flexible trigger fillers, pre-made ones with higher energy
- Entry point and activation — leftmost ability, manual/auto/passive, board-level activation overrides
- Topology examples — linear, branching, fan-in, loop, player-event input, complex
- Abilities as resources — consumed when socketed, duplicates matter
- Modifier scoping — payload isolation, connection-scoped modifiers, tag compatibility
- Trigger types — all carrier events and player events, multiplying triggers, Cast at Carrier
- Energy system — capacity, recharge, pre-made vs. flexible energy difference, loop throttling
- Cross-board synergy — charges, exert, global modifiers (carried over as-is)
- Boards as loot — rarity axes, board identity, partial use, progression
- Board crafting — operations sketched out (add/remove mod slots, reroll triggers, convert flexible/pre-made, add connections, add player-event inputs, reroll energy)
- Seven example boards — from common single-ability to rare loops and Cast at Carrier patterns
- Relationship to orbs — one board = one orb, old linking system fully replaced
Very good document, marvelous work, I am extremely happy with this.
Thank you — that's genuinely good to hear. The system landed in a really clean place. The board-as-loot idea in particular solved multiple problems at once (UI clarity, topology as content, natural complexity scaling) in a way that the free-form graph and the old orb-linking system couldn't.
Given that we still have orbs happening, can you envision this system in terms of the orbs? Like, maybe a circular/spherical representation, or would that be too weird?
Not weird at all — I think it's more natural than the rectangular "board" framing, actually. The word "board" was functional but didn't match the fantasy. A circular layout fits the orb identity directly.
Picture it: you inspect an orb and see its internal structure — a circular arrangement of interconnected slots.
Simple orb (1 ability):
╭─────────────╮
│ [mod] │
│ ╎ │
│ [ABILITY] │
│ ╎ │
│ [mod] │
╰─────────────╯
Two-ability orb with connection:
╭──────────────────────╮
│ [mod] [mod] │
│ ╎ ╎ │
│ [ABILITY]══on══→[ABILITY] │
│ ╎ hit ╎ │
│ [mod] [mod] │
│ [mod] │
╰──────────────────────╯
Three-ability branching:
╭────────────────╮
│ [ABILITY] │
│ ╎ ╎ │
│ [mod] [mod] │
│ ╱ ╲ │
│ (on hit) (on kill) │
│ ╱ ╲ │
│[ABILITY] [ABILITY]│
│ ╎ ╎ │
│[mod] [mod]│
╰────────────────╯
Loop — this is where it gets perfect. A loop topology inside a circle is completely natural:
╭────────────────╮
│ [ABILITY] │
│ ╎ ↑ ╲ │
│ [mod] │ (on hit)│
│ │ ╲ │
│ (on expire) [ABILITY]│
│ │ ╎ │
│ └──── [mod]│
╰────────────────╯
The circular framing does several things:
- Loops look right. A circular path inside a circle — no awkward arrows going backwards.
- Small graphs stay readable. 1-4 nodes distributed around a circle is clean. You never have more than 4 ability slots, so it never gets too dense.
- Modifier slots cluster around their parent like satellites — small dots orbiting each ability node. Reinforces the orbital metaphor at every level.
- Player-event inputs pierce the boundary — they come from outside the circle, visually communicating that the source is external (the player), not internal.
- Pre-made connections could glow or pulse while flexible ones are dim/dashed. The orb's visual intensity tells you at a glance how "charged" it is.
- Rarity reads instantly. A common orb looks sparse inside — one node, two small satellites. A rare orb looks dense and interconnected. You can feel the complexity before reading any details.
The concern is that complex boards (4 abilities, multiple connections, lots of modifier slots) could get cluttered inside a circle. But honestly, 4 nodes with 5-6 satellites each inside a circle is comparable to what skill trees and constellation systems handle routinely. And the most complex boards are the rarest — most of what players interact with is 1-3 ability nodes, which sits very comfortably in a circular layout.
The metaphor stacks nicely too: orbs orbit the player, modifier slots orbit ability nodes, everything is circles within circles.
Hmmm, this is where you being able to generate images would help probably... I can't picture what you mean that well
Fair enough — ASCII art is terrible for circular layouts. Let me make an HTML file with actual visual mockups you can open in a browser.
Write (E:\a327ex\Orblike\reference\orb_visual_mockup.html)
File created successfully at: E:\a327ex\Orblike\reference\orb_visual_mockup.html
Bash (start "" "E:/a327ex/Orblike/reference/orb_visual_mockup.html")
Opened Orblike/reference/orb_visual_mockup.html in your browser. It shows 8 orb mockups:
- Common — single ability with 2 mod slots (simple, sparse interior)
- Uncommon Bomber — 2 abilities with a glowing pre-made on-hit connection
- Rare Chain Reaction — 3 abilities in a linear chain (pre-made + flexible connections)
- Rare Branching — 3 abilities, one source triggering two targets
- Rare Defensive Reactor — player-event input (red "P" node) piercing into the orb from outside
- Rare Loop — curved return connection creating a cycle
- Legendary Complex — 4 abilities, branching + player-event input, lots of mod slots
- Uncommon Generalist — all flexible (dashed) connections, empty ability slots marked "?"
Visual conventions used:
- Glowing orange lines = pre-made connections (high energy)
- Dashed blue lines = flexible connections (low energy)
- Red "P" node outside the orb boundary = player-event input
- Large colored circles = ability slots
- Small satellite dots = modifier slots (filled = occupied, dashed = empty)
Let me know what you think and what needs adjusting.
Oh, damn, yea, I can see it now. Perhaps you could take inspiration from Path of Exile's skill tree (I believe the one from the second game has lines that are more circular as well) and try something like that? So the connections between mods and nodes and between nodes can also be seen. Just do a single, big orb, like the "legendary: complex" one which covers most cases.
ToolSearch
[
{
"type": "tool_reference",
"tool_name": "WebSearch"
}
]
WebSearch (Path of Exile 2 passive skill tree visual design circular connections)
Web search results for query: "Path of Exile 2 passive skill tree visual design circular connections"
Links: [{"title":"Path of Exile 2 Passive Skill Tree Planner - Maxroll.gg","url":"https://maxroll.gg/poe2/passive-tree"},{"title":"Exploring Passive Skill Tree in Path of Exile 2 - MMOJUGG","url":"https://www.mmojugg.com/news/exploring-passive-skill-tree-in-path-of-exile-2.html"},{"title":"Passive Skill Tree - Path of Exile 2 - poe.ninja","url":"https://poe.ninja/poe2/passive-skill-tree"},{"title":"Passive Skill Tree - Path of Exile","url":"https://www.pathofexile.com/passive-skill-tree"},{"title":"Passive skill - Path of Exile Wiki","url":"https://pathofexile.fandom.com/wiki/Passive_skill"},{"title":"Passive Skill Tree Guide and List of Passives | Path of Exile 2 (PoE 2)|Game8","url":"https://game8.co/games/Path-of-Exile-2/archives/487065"},{"title":"General Discussion - POE 2 Passive tree. - Forum - Path of Exile","url":"https://www.pathofexile.com/forum/view-thread/3591295"},{"title":"PoE Planner - For Exiles, By Exiles","url":"https://poeplanner.com/"},{"title":"Path of Exile 2 Passive Skill Tree Explained","url":"https://bo3.gg/games/articles/path-of-exile-2-passive-skill-tree-explained"},{"title":"POE 2 Tree - Path of Exile 2 Passive Skill Tree Planner | Devpost","url":"https://devpost.com/software/poe-2-tree-path-of-exile-2-passive-skill-tree-planner"}]
Based on the search results, here's what I found about Path of Exile 2's passive skill tree visual design and circular connections:
## Visual Design Differences from Path of Exile 1
The design of the PoE2 passive skill tree diverges greatly from the PoE1 tree's symmetrical and structured layout. In PoE1, the tree follows a circular and relatively predictable design with all classes attached to their respective branches.
## Circular Connections and Cluster Shapes
Rather than maintaining circular node clusters, PoE2's clusters vary with unique designs corresponding to specific weapon types, making for a visually intricate tree resembling constellations. These unique shapes include swords, crossbows, and axes, and they aim to visually convey specialization areas based on weaponry.
## Design Philosophy
PoE2 introduces more diverse paths and cluster shapes—each branch more variable in appearance and functionality. The PoE2 tree invites players to "get weird" with their builds, using asymmetry and non-standard shapes to create a more adventurous experience.
The overall approach represents a shift away from the structured circular design of the original Path of Exile toward a more specialized, visually diverse constellation-like layout that reflects different weapon types and playstyles.
REMINDER: You MUST include the sources above in your response to the user using markdown hyperlinks.
Read (E:\a327ex\Orblike\reference\orb_visual_mockup.html)
1→<!DOCTYPE html>
2→<html>
3→<head>
4→<meta charset="utf-8">
5→<title>Orb Visual Mockups</title>
6→<style>
7→ body {
8→ background: #1a1a2e;
9→ color: #e0e0e0;
10→ font-family: 'Segoe UI', sans-serif;
11→ display: flex;
12→ flex-wrap: wrap;
13→ gap: 40px;
14→ padding: 40px;
15→ justify-content: center;
16→ }
17→ h1 {
18→ width: 100%;
19→ text-align: center;
20→ color: #a0c4ff;
21→ font-size: 28px;
22→ margin-bottom: 0;
23→ }
24→ .orb-container {
25→ display: flex;
26→ flex-direction: column;
27→ align-items: center;
28→ gap: 12px;
29→ }
30→ .orb-label {
... [578 more lines]
Now let me redo it as a single large orb with PoE skill-tree-inspired aesthetics — curved connections everywhere, visible paths between all nodes, trigger types as nodes sitting on connections, constellation-like glow.
Write (E:\a327ex\Orblike\reference\orb_visual_mockup.html)
The file E:\a327ex\Orblike\reference\orb_visual_mockup.html has been updated successfully.
Bash (start "" "E:/a327ex/Orblike/reference/orb_visual_mockup.html")
Should be open now. This is a single large orb with PoE skill-tree-inspired aesthetics:
- 4 ability nodes arranged in an organic circular layout (PROJ, AREA, CHAIN, ZONE)
- Curved connections between all nodes — no straight lines, everything arcs
- Trigger nodes as diamonds sitting on the connection paths (on hit, on kill, flexible empties)
- Modifier nodes as small circles radiating from their parent ability via visible paths — like small passive clusters around a notable in PoE
- Modifiers on a connection — the PROJ→AREA connection has its own small mod cluster branching off the path (one filled "nova", one empty)
- Player-event input — red "P" node outside the orb boundary, connection piercing through the shell into the ZONE node
- Loop — ZONE→PROJ flexible dashed connection arcing around the bottom
- Entry point — dashed ring and "ENTRY" label on PROJ
- Legend in the bottom-left corner
The visual language: glowing orange = pre-made/strong, dashed blue = flexible/weak, red = player events, diamonds = triggers, circles = abilities and mods.