Profile
- Name
- Complex Remix v1.0
- ID
- 109182
- Shared with
- Public
- Parent
- Complex_Remix v0.9
- Children
- None
- Created on
- September 18, 2025 at 17:19 PM UTC
- Updated on
- September 20, 2025 at 17:03 PM UTC
- Description
Remixes multiple selections simultaneously. For many selections, lower the Slot Budget.
Best for
Code
--[[
v1.0.2
- bugfix
v1.0
- Major code refactoring.
- Now it is possible to Remix up to 6 selections
v0.9
Complex Remix for 2 selections
https://fold.it/recipes/103304
Remix multiple selections simultaneously.
It tests all possible relative Remix solutions for all the selections. Then it fuses all of them and finds the best scored solution.
Slot Budget is the main setting to limit the total number of fuses.
Example: if you Complex Remix two selections and each has 30 remix solutions, in total you need to try 30×30 = 900 fuses. Slot Budget lets you cap this. If you set Slot Budget to 20, each selection will use only 10 solutions (20=10+10), so the total is 10×10 = 100 fuses.
For 2+ selections, lower the Slot Budget. The combinatorics grows sky thousands.
Example: with four selections and Slot Budget = 16, each selection will test up to 4 solutions, so the total is 4×4×4×4 = 256 fuses.
If you don’t lower the Slot Budget, and each of the 4 selections has 20 Remix solutions, the recipe will have to test 160 000 fuses.
]]--
version = " 1.0"
script_name = "Complex Remix"
function FastFuze()
behavior.SetClashImportance(0.05)
structure.ShakeSidechainsAll(1)
structure.WiggleAll(10)
behavior.SetClashImportance(1)
structure.WiggleAll(10)
end
function Fuze()
recentbest.Save()
behavior.SetClashImportance(0.05)
structure.ShakeSidechainsAll(1)
structure.WiggleAll(2)
behavior.SetClashImportance(0.2)
structure.ShakeSidechainsAll(1)
structure.WiggleAll(1)
behavior.SetClashImportance(1)
structure.WiggleAll(1)
behavior.SetClashImportance(0.3)
structure.ShakeSidechainsAll(1)
behavior.SetClashImportance(1)
structure.WiggleAll(20)
recentbest.Restore()
end
function ScoreReturn()
local x = current.GetEnergyScore()
return x - x % 0.01
end
DEFAULT_SOLUTIONS_TO_SEARCH = 94
SLOT_BUDGET = 94
MAX_PREPASS_RESULTS = 95
MIN_SELECTION_LENGTH = 3
MAX_SELECTION_LENGTH = 9
MAX_SELECTIONS = 6
DEFAULT_SELECTION_COUNT = 2
reportLevel = 2
slotBudget = DEFAULT_SOLUTIONS_TO_SEARCH
proteinLength = 0
selectionStates = {}
selectionCount = DEFAULT_SELECTION_COUNT
slotBases = {}
initScore = ScoreReturn()
function Log(level, message, ...)
if reportLevel >= level then
if select('#', ...) > 0 then
print(string.format(message, ...))
else
print(message)
end
end
end
function Clamp(value, minVal, maxVal)
if value < minVal then
return minVal
end
if value > maxVal then
return maxVal
end
return value
end
function ClampRange(range)
local lengthLimit = math.max(proteinLength, 1)
local minLen = math.min(MIN_SELECTION_LENGTH, lengthLimit)
local maxLen = math.min(MAX_SELECTION_LENGTH, lengthLimit)
local startVal = Clamp(math.floor((range.start or 1) + 0.5), 1, lengthLimit)
local finishVal = Clamp(math.floor((range.finish or startVal) + 0.5), 1, lengthLimit)
if startVal > finishVal then
local tmp = startVal
startVal = finishVal
finishVal = tmp
end
local length = finishVal - startVal + 1
if length < minLen then
local deficit = minLen - length
local leftShift = math.floor(deficit / 2)
startVal = startVal - leftShift
finishVal = finishVal + (deficit - leftShift)
if startVal < 1 then
local delta = 1 - startVal
startVal = 1
finishVal = math.min(lengthLimit, finishVal + delta)
end
if finishVal > lengthLimit then
local delta = finishVal - lengthLimit
finishVal = lengthLimit
startVal = math.max(1, startVal - delta)
end
end
length = finishVal - startVal + 1
if length < minLen then
finishVal = math.min(lengthLimit, startVal + minLen - 1)
startVal = math.max(1, finishVal - minLen + 1)
end
length = finishVal - startVal + 1
if length > maxLen then
local center = (startVal + finishVal) / 2
local newStart = math.floor(center - (maxLen - 1) / 2 + 0.5)
local newEnd = newStart + maxLen - 1
if newStart < 1 then
newStart = 1
newEnd = maxLen
end
if newEnd > lengthLimit then
newEnd = lengthLimit
newStart = math.max(1, lengthLimit - maxLen + 1)
end
startVal = newStart
finishVal = newEnd
end
return { start = startVal, finish = finishVal, length = finishVal - startVal + 1 }
end
function ScanSelections()
local selections = {}
local currentStart = nil
for i = 1, proteinLength do
if selection.IsSelected(i) then
if currentStart == nil then
currentStart = i
end
if i == proteinLength then
table.insert(selections, { start = currentStart, finish = i })
currentStart = nil
end
elseif currentStart ~= nil then
table.insert(selections, { start = currentStart, finish = i - 1 })
currentStart = nil
end
end
return selections
end
function BuildDefaultSelections(existing, desiredCount)
local defaults = {}
local count = math.min(#existing, desiredCount)
for i = 1, count do
local original = existing[i]
local sanitized = ClampRange(original)
if reportLevel >= 2 then
if sanitized.start ~= original.start or sanitized.finish ~= original.finish then
Log(2, "Adjusted selection %d to %d-%d (len %d) during preset", i, sanitized.start, sanitized.finish, sanitized.length)
end
end
defaults[i] = sanitized
end
local baseLength = math.min(5, MAX_SELECTION_LENGTH)
local halfSpan = math.floor((baseLength - 1) / 2)
for i = count + 1, desiredCount do
local center = math.floor(proteinLength / (desiredCount + 1) * i)
center = Clamp(center, 1 + halfSpan, proteinLength - halfSpan)
local startVal = center - halfSpan
local sanitized = ClampRange({ start = startVal, finish = startVal + baseLength - 1 })
if reportLevel >= 2 then
Log(2, "Initialized selection %d to %d-%d (len %d)", i, sanitized.start, sanitized.finish, sanitized.length)
end
defaults[i] = sanitized
end
if desiredCount == 0 then
return BuildDefaultSelections(existing, DEFAULT_SELECTION_COUNT)
end
return defaults
end
function CreateSelectionState(range, index)
local sanitized = ClampRange(range)
return {
index = index,
start = sanitized.start,
finish = sanitized.finish,
length = sanitized.length,
currentLength = sanitized.length,
initialStart = sanitized.start,
initialLength = sanitized.length,
center = (sanitized.start + sanitized.finish) / 2,
hasReset = false,
solutionCount = 0,
assignedSlots = 0
}
end
function ApplySelectionState(state)
for pos = state.start, state.finish do
selection.Select(pos)
end
end
function ApplySelectionStates()
selection.DeselectAll()
for _, state in ipairs(selectionStates) do
ApplySelectionState(state)
end
end
function SelectSingle(index)
selection.DeselectAll()
ApplySelectionState(selectionStates[index])
end
function AssignLength(state, newLength)
newLength = Clamp(newLength, MIN_SELECTION_LENGTH, MAX_SELECTION_LENGTH)
local halfSpan = newLength - 1
local leftSpan = math.floor(halfSpan / 2)
local center = state.center
local startVal = math.floor(center + 0.5) - leftSpan
local finishVal = startVal + newLength - 1
local clamped = ClampRange({ start = startVal, finish = finishVal })
state.start = clamped.start
state.finish = clamped.finish
state.length = clamped.length
state.currentLength = clamped.length
state.center = (clamped.start + clamped.finish) / 2
end
function AdvanceLength(state)
local prevLength = state.length
local nextLength
if prevLength < MAX_SELECTION_LENGTH then
nextLength = prevLength + 1
if state.hasReset and nextLength == state.initialLength then
return false
end
else
nextLength = MIN_SELECTION_LENGTH
if state.hasReset then
if nextLength == state.initialLength then
return false
end
else
state.hasReset = true
if nextLength == state.initialLength then
return false
end
end
end
AssignLength(state, nextLength)
return true
end
function TryRemixWithLengthAdjustment(index)
local state = selectionStates[index]
while true do
save.Quickload(1)
SelectSingle(index)
local count = structure.RemixSelected(2, MAX_PREPASS_RESULTS)
save.Quickload(1)
if count > 0 then
state.solutionCount = count
Log(3, "Selection %d length %d (%d-%d) produced %d solutions.", index, state.length, state.start, state.finish, count)
ApplySelectionStates()
save.Quicksave(1)
return true
end
local advanced = AdvanceLength(state)
if not advanced then
Log(1, "ERROR: Selection %d failed to produce remix solutions after cycling lengths.", index)
return false
end
ApplySelectionStates()
save.Quicksave(1)
Log(3, "Selection %d produced no solutions; trying length %d (%d-%d).", index, state.length, state.start, state.finish)
end
end
function PreMeasureSolutions()
for idx = 1, selectionCount do
if not TryRemixWithLengthAdjustment(idx) then
return false
end
end
return true
end
function AllocateSlots(totalBudget)
local counts = {}
local sum = 0
for idx, state in ipairs(selectionStates) do
counts[idx] = state.solutionCount
sum = sum + state.solutionCount
end
local assigned = {}
if sum <= totalBudget then
for idx, count in ipairs(counts) do
assigned[idx] = math.min(count, totalBudget)
end
return assigned
end
local base = math.floor(totalBudget / selectionCount)
if base < 1 then base = 1 end
local used = 0
for idx = 1, selectionCount do
assigned[idx] = math.min(counts[idx], base)
used = used + assigned[idx]
end
local leftover = totalBudget - used
local order = {}
for idx = 1, selectionCount do
order[idx] = idx
end
table.sort(order, function(a, b)
if counts[a] == counts[b] then
return a < b
end
return counts[a] > counts[b]
end)
while leftover > 0 do
local distributed = false
for _, idx in ipairs(order) do
local capacity = counts[idx] - assigned[idx]
if capacity > 0 then
local add = math.min(capacity, leftover)
assigned[idx] = assigned[idx] + add
leftover = leftover - add
distributed = true
if leftover <= 0 then
break
end
end
end
if not distributed then
break
end
end
local totalAssigned = 0
for idx = 1, selectionCount do
totalAssigned = totalAssigned + assigned[idx]
end
if totalAssigned > totalBudget then
local excess = totalAssigned - totalBudget
table.sort(order, function(a, b)
if assigned[a] == assigned[b] then
return a < b
end
return assigned[a] > assigned[b]
end)
for _, idx in ipairs(order) do
if excess <= 0 then break end
local reducible = math.min(assigned[idx] - 1, excess)
if reducible > 0 then
assigned[idx] = assigned[idx] - reducible
excess = excess - reducible
end
end
end
return assigned
end
function Ordinal(n)
local suffix = "th"
local mod10 = n % 10
local mod100 = n % 100
if mod10 == 1 and mod100 ~= 11 then suffix = "st"
elseif mod10 == 2 and mod100 ~= 12 then suffix = "nd"
elseif mod10 == 3 and mod100 ~= 13 then suffix = "rd" end
return string.format("%d%s", n, suffix)
end
function SummarizeSelections()
if reportLevel < 2 then return end
if selectionCount == 2 then
local a = selectionStates[1]
local b = selectionStates[2]
print(string.format("Sel1 is %d-%d\t / sel2 is\t%d-%d", a.start, a.finish, b.start, b.finish))
else
for idx, state in ipairs(selectionStates) do
Log(2, "Selection %d %d - %d", idx, state.start, state.finish)
end
end
end
function ExtractRangesFromDialog(dlg)
local ranges = {}
for idx = 1, selectionCount do
local startSlider = dlg["sel" .. idx .. "Start"]
local endSlider = dlg["sel" .. idx .. "End"]
if startSlider and endSlider then
ranges[idx] = ClampRange({ start = startSlider.value, finish = endSlider.value })
else
ranges[idx] = ClampRange({ start = 1, finish = MIN_SELECTION_LENGTH })
end
end
return ranges
end
function ShowSelectionDialog(existingSelections)
local defaults = BuildDefaultSelections(existingSelections, selectionCount)
while true do
local dlg = dialog.CreateDialog(script_name..version)
dlg.slotCount = dialog.AddSlider ("Slot Budget", slotBudget, selectionCount, SLOT_BUDGET, 0)
for idx = 1, selectionCount do
if idx > 1 then
dlg["label" .. idx] = dialog.AddLabel("Selection " .. idx)
end
dlg["sel" .. idx .. "Start"] = dialog.AddSlider("Selection " .. idx .. " start", defaults[idx].start, 1, proteinLength, 0)
dlg["sel" .. idx .. "End"] = dialog.AddSlider("Selection " .. idx .. " end", defaults[idx].finish, 1, proteinLength, 0)
end
dlg.report = dialog.AddSlider("Report level", reportLevel, 1, 4, 0)
dlg.ok = dialog.AddButton("OK", 1)
dlg.add = dialog.AddButton("Add Sel", 2)
dlg.cancel = dialog.AddButton("Cancel", 0)
local result = dialog.Show(dlg)
if result == 0 then
print("Canceled by user")
return nil
elseif result == 2 then
if selectionCount >= MAX_SELECTIONS then
print(string.format("Maximum selections reached (%d).", MAX_SELECTIONS))
else
local current = ExtractRangesFromDialog(dlg)
selectionCount = selectionCount + 1
defaults = BuildDefaultSelections(current, selectionCount)
slotBudget = math.max(slotBudget, selectionCount)
end
elseif result == 1 then
slotBudget = math.floor(dlg.slotCount.value + 0.5)
reportLevel = math.floor(dlg.report.value + 0.5)
local ranges = {}
for idx = 1, selectionCount do
ranges[idx] = ClampRange({
start = dlg["sel" .. idx .. "Start"].value,
finish = dlg["sel" .. idx .. "End"].value
})
end
return {
slotCount = slotBudget,
reportLevel = reportLevel,
ranges = ranges
}
end
end
end
function PrepareSelections(config)
selectionStates = {}
for idx = 1, selectionCount do
selectionStates[idx] = CreateSelectionState(config.ranges[idx], idx)
if reportLevel >= 3 then
Log(3, "Selection %d prepared as %d-%d (len %d)", idx, selectionStates[idx].start, selectionStates[idx].finish, selectionStates[idx].length)
end
end
ApplySelectionStates()
end
function ComputeSlotBases()
slotBases = {}
slotBases[1] = 2
local highest = 1
for level = 1, selectionCount do
if level > 1 then
slotBases[level] = slotBases[level - 1] + selectionStates[level - 1].assignedSlots
end
local maxSlot = slotBases[level] + selectionStates[level].assignedSlots - 1
if maxSlot > highest then
highest = maxSlot
end
end
if highest > 100 then
Log(1, "ERROR: Slot allocation needs quicksave slot %d, which exceeds the limit of 100. Reduce solutions to search.", highest)
return false
end
return true
end
-- Recursively count exact number of leaf fuzes (sum of childCount
-- across all blocks) without performing any fuzing or mutating budgets.
-- (Removed) Exact prepass counting functions to speed up execution.
function RunRecursiveRemix(level, outerRemaining, path)
if outerRemaining == nil then outerRemaining = 1 end
if path == nil then path = {} end
local state = selectionStates[level]
local baseSlot = slotBases[level]
local budget = state.assignedSlots
SelectSingle(level)
local count = structure.RemixSelected(baseSlot, budget)
if count == 0 then
Log(1, "ERROR: Selection %d returned no remix solutions (budget %d).", level, budget)
return 0
end
state.solutionCount = count
Log(3, "Got\t%d\tsolutions for %s selection", count, Ordinal(level))
if count < budget then
Log(2, "Selection %d produced only %d solution(s); budget capped from %d", level, count, budget)
end
if level == selectionCount then
return count
end
local finalBase = slotBases[level + 1]
for i = 0, count - 1 do
save.Quickload(baseSlot + i)
local nextPath = {}
for k, v in ipairs(path) do nextPath[k] = v end
nextPath[#nextPath + 1] = i + 1
local childCount = RunRecursiveRemix(level + 1, outerRemaining * (count - i), nextPath)
if level == selectionCount - 1 and childCount > 0 then
local combinationsLeft = count - i
-- total remaining blocks at this and outer levels:
-- current outer branch has 'combinationsLeft' blocks left; each other outer branch (outerRemaining-1)
-- still has the full 'count' blocks. Each block contains 'childCount' leaf fuzes.
local totalBlocksLeft = combinationsLeft + (outerRemaining - 1) * count
local totalLeft = totalBlocksLeft * childCount
if reportLevel == 2 then
-- Use on-the-fly estimate instead of exact prepass count
Log(2, string.format("\t%d\t fuzes to go. Best\t%.2f", totalLeft, bestScore))
else
if outerRemaining > 1 then
-- Example: Have (6 + (3 - 1) * 7) * 23 = 460 fuzes to go
Log(3, string.format("( \t%d\t+\t(%d\t-\t1 )\t*\t%d )\t*\t%d\t=\t%d\t fuzes to go", combinationsLeft, outerRemaining, count, childCount, totalLeft))
else
Log(3, string.format("\t%d\t*\t%d\t=\t%d\t fuzes to go", combinationsLeft, childCount, totalLeft))
end
end
-- track best candidate within this fast-fuze block
local blockBestSlot = nil
local blockBestScore = -999999
for slotIdx = finalBase, finalBase + childCount - 1 do
save.Quickload(slotIdx)
save.Quicksave(99)
FastFuze()
save.Quicksave(slotIdx)
local currentScore = ScoreReturn()
local marker = ""
-- update per-block best (will need that later for full fuze); store its unfuzed version into slot 97
if currentScore > blockBestScore then
blockBestScore = currentScore
blockBestSlot = slotIdx
save.Quickload(99)
save.Quicksave(97)
save.Quickload(slotIdx)
marker = "\t*"
end
if currentScore > bestScore then
bestScore = currentScore
bestSlot = slotIdx
save.Quicksave(98) --saving fuzed version for the best solution found
save.Quickload(99) --loading unfuzed version of the best solution
save.Quicksave(100)--saving unfuzed version of the best solution
marker = marker.." best"
end
local parts = {}
for k, v in ipairs(path) do parts[#parts+1] = tostring(v) end
parts[#parts+1] = tostring(i + 1)
parts[#parts+1] = tostring(slotIdx - finalBase + 1)
local label = table.concat(parts, " - ")
Log(2, string.format("Remix\t%s\t: slot\t%d\tscore\t%.2f%s", label, slotIdx, currentScore, marker))
end
-- after finishing fast fuzes for this block, run a full fuze on the best candidate using its unfuzed copy in slot 97
if blockBestSlot ~= nil then
save.Quickload(97) -- unfuzed best for this block
local preFullScore = blockBestScore -- best fast-fuzed score for report
Fuze()
local fullScore = ScoreReturn()
temp = ""
if fullScore > bestScore then
bestScore = fullScore
bestSlot = blockBestSlot
save.Quicksave(98) -- save full-fuzed best
save.Quickload(97) -- restore unfuzed best for saving
save.Quicksave(100) -- save the unfuzed version
temp = "\t* best"
end
Log(2, "Full Fuzed slot %d score %.2f to score %.2f %s", blockBestSlot, preFullScore, fullScore, temp)
end
-- No exact remaining fuze counter when prepass is disabled
end
end
return count
end
function ExecuteRemixes()
ApplySelectionStates()
local resultCount = RunRecursiveRemix(1, 1, {})
return resultCount > 0
end
function main()
bestScore = -999999
bestSlot = 0
proteinLength = structure.GetCount()
local startScore = ScoreReturn()
print(string.format("Starting score %.2f", startScore))
save.Quicksave(1)
save.Quicksave(100)
save.Quicksave(98)
local existingSelections = ScanSelections()
if #existingSelections == 0 then
selectionCount = DEFAULT_SELECTION_COUNT
else
selectionCount = math.min(math.max(#existingSelections, DEFAULT_SELECTION_COUNT), MAX_SELECTIONS)
end
local config = ShowSelectionDialog(existingSelections)
if not config then
return
end
slotBudget = Clamp(config.slotCount, selectionCount, SLOT_BUDGET)
reportLevel = Clamp(config.reportLevel, 1, 4)
PrepareSelections(config)
save.Quicksave(1)
if not PreMeasureSolutions() then
return
end
SummarizeSelections()
local allocations = AllocateSlots(slotBudget)
for idx, state in ipairs(selectionStates) do
local assigned = math.max(1, math.min(state.solutionCount, allocations[idx] or state.solutionCount))
if reportLevel >= 3 and state.assignedSlots ~= 0 and assigned ~= state.assignedSlots then
Log(3, "Selection %d slot budget adjusted from %d to %d", idx, state.assignedSlots, assigned)
end
state.assignedSlots = assigned
if state.assignedSlots < state.solutionCount then
Log(2, string.format("Selection %d: %d solutions, budget %d (trimmed %d)", idx, state.solutionCount, state.assignedSlots, state.solutionCount - state.assignedSlots))
else
Log(2, string.format("Selection %d: %d solutions, budget %d", idx, state.solutionCount, state.assignedSlots))
end
end
if not ComputeSlotBases() then
return
end
-- Skip exact prepass; progress will use an on-the-fly estimate
-- Ensure a clean selection state before the main run
ApplySelectionStates()
if not ExecuteRemixes() then
return
end
Cleanup("Finished")
end
function Cleanup(err)
print ("Cleanup")
undo.SetUndo(true)
-- creating undo stack
save.Quickload(1)
save.Quickload(100)
save.Quickload(98)
currentScore=ScoreReturn()
behavior.SetClashImportance(1)
if (currentScore > initScore) then
print ("Total gain:", currentScore-initScore)
else
save.Quickload(1)
print("No improve. Restored to "..ScoreReturn())
end
if selectionStates ~= nil and #selectionStates > 0 then
ApplySelectionStates()
if reportLevel ~= nil and reportLevel >= 2 then
Log(3, "Restored %d selection(s) on exit", #selectionStates)
end
end
if err ~= nil then
print(err)
end
end
xpcall ( main , Cleanup )