I wrote a new blog post outlining a number of useful design patterns and ways of working that fall out of the “stack-of-cards” metaphor shared by Decker and HyperCard:
http://beyondloom.com/blog/sketchpad.html
I'd love to hear your thoughts!
I wrote a new blog post outlining a number of useful design patterns and ways of working that fall out of the “stack-of-cards” metaphor shared by Decker and HyperCard:
http://beyondloom.com/blog/sketchpad.html
I'd love to hear your thoughts!
Most of the time, the fields and other widgets on different cards of a deck contain entirely different data. Occasionally though, as in this question, you might have data like a game's score counter that you want to display on and modify from many cards. There's an elegant way to (ab)use Prototypes and Contraptions to create this kind of "singleton" widget state:
When the value of a field in a Contraption instance has never been modified, the value of the corresponding field in the Contraption's Prototype is inherited. By exposing a ".value" attribute for our Contraptions which read from and write to a field in the Prototype, rather than the Contraption instance, every instance of the Contraption will share a single value:
on get_value do card.def.widgets.f.data end on set_value x do card.def.widgets.f.data:x end
This becomes slightly more complicated if we want the field to be unlocked and user-editable, since we can no longer rely on inheriting the prototype's value automatically. In this case, we will need to explicitly replicate changes back to the prototype and poll the value from the prototype when the contraption is updated by a view[] event:
on get_value do card.def.widgets.f.data end on set_value x do card.def.widgets.f.data:x end on change do set_value[f.data] end on view do f.data:get_value[] end
As a complete example, here's a "scoreCounter" prototype which uses this mechanism:
%%WGT0{"w":[{"name":"sc","type":"contraption","size":[100,20],"pos":[302,95],"def":"scoreCounter","widgets":{"f":{}}}],"d":{"scoreCounter":{"name":"scoreCounter","size":[100,20],"margin":[0,0,0,0],"description":"an example of a \"singleton\" contraption which shares data across every instance.","script":"on get_value do card.def.widgets.f.data end\non set_value x do card.def.widgets.f.data:x end\non change do set_value[f.data] end\non view do f.data:get_value[] end\n","attributes":{"name":["value"],"label":["Value"],"type":["number"]},"widgets":{"f":{"type":"field","size":[100,20],"pos":[0,0],"style":"plain","align":"center","value":"0"}}}}}
Anywhere a scoreCounter appears within a deck, it will represent an identical view of the same underlying value. A pleasant side-effect of this approach is that scripts on cards can refer to any scoreCounter when they want to read or modify the game's score; there's no need to reach for a "source of truth" on some specific card.
I hope some of you find this to be a useful technique for your projects!
If you want to increment a local variable, like "t" in your example, you must use the assignment operator ":" (read aloud "becomes" or "gets") like so:
t:t+1
Local variables exist only within the lifespan of a single event.
Persistent state, like an inventory or a system for tracking a user's action, must reside in a widget somewhere. This has been discussed many times and places within this forum. I recommend starting with this thread: https://itch.io/t/2702720/how-to-have-interaction-between-cards
By default, the Ply renderer doesn't have the deck, cards, or any local widgets in scope when it evaluates a Lil fragment, so it can't manipulate parts of the deck.
If you're using "twee.render[story passagename vars]" directly like in this demo, "vars" can be a dictionary containing arbitrary bindings, including references to the widgets of the current card.
The PlyPlayer contraption has similar functionality, but it is less flexible; you can read and write its .vars attribute, but this feature is only designed to store data that can be serialized in a field's .data attribute, not references to deck parts.
The easiest way to have Ply stories interact with a surrounding deck is to use the passage[] event that the PlyPlayer emits every time it visits a new passage. By keeping deck-influencing logic separate from the Ply story itself the story will still be entirely playable within the Twine editor. There's some relevant discussion in this thread.
Does that help at all?
Fun little update for PublicTransit- I added 8 new transition effects:
Itch.io GIF uploads have been busted for quite a while, so no preview GIF; just follow the link above, give them a spin, and see if any stir some inspiration in you!
Just to be clear, a field's .value attribute, which is represented as a rich text table, is the complete, underlying contents stored in the field. Fields have .text, .images, and .data attributes which can be read and written, but all three are alternate views/subsets of the same contents (as a plain-text string, as a list of inline images, and as encoded/decoded LOVE data, respectively), as scripting conveniences.
Buttons have a .value attribute as well, but it represents the boolean (1/0) value of checkboxes, and has no relationship to the .text attribute of the button, which controls the button's label.
Sliders have a .value attribute which corresponds to their numerical value, and Grids have a .value attribute which corresponds to the table data they contain.
From the reference manual's description of the Prototype Interface,
when a definition is updated, thename,pos,show,locked,animated,volatile,font,pattern, andscriptattributes of Contraptions will be preserved, as well thevalue,scroll,row,col, and image content of the widgets they contain (as applicable) if they have been modified from their original values in the prototype, but everything else will be regenerated from the definition. The state of contraptions is kept, and the behavior and appearance is changed.
Any other state that you wish to survive prototype modification should be stashed in internal widgets; I recommend refreshing these settings (alongside applying settings from the enclosing contraption instance like .font and .locked, where appropriate) in the view[] event handler in the prototype script.
You could write a script to record the position of all the canvases automatically (rather than recording every coordinate by hand), stash those positions in a field somewhere, and then have another script re-apply them when appropriate.
Let's say you have widgets something like this:

The "remember" script looks like:
on click do p:select key value..pos where value..type="canvas" from card.widgets positions.data:raze p end
And the "restore" script looks like:
on click do each pos name in positions.data card.widgets[name].pos:pos end end
In practice, this field and buttons will probably be hidden (set to "show none"). If you ever want to record the current state of the canvases without un-hiding the buttons you can simulate a click via the listener:
remember.event.click
Does that make sense?
An offline copy of Decker's documentation is included with the Native-Decker download inside the "Docs" folder.
You can also access the latest docs through the links in the Help menu, or the links on the main decker website.
State in a deck, like a score counter, needs to live in a widget, on some card.
The straightforward way to have a global score counter would be to make a field (or slider) widget somewhere on a specific card; let's call the card "inventory" and the field "score". Scripts on another card can then refer to that field by one of its "fully-qualified" names:
deck.cards.inventory.widgets.score inventory.widgets.score
For example, to add 100 points to the score, a button script might be
on click do inventory.widgets.score.data:100+inventory.widgets.score.data end
Or, using a local variable to reduce repetition,
on click do s:inventory.widgets.score s.data:100+s.data end
In fact, if you're referring to this widget constantly throughout your deck, you might consider setting up an alias in the deck-level script:
scoreCounter:deck.cards.inventory.widgets.score
After which point you could just use the name "scoreCounter" in any script, on any card.
Each card could have their own "score" field (perhaps volatile) that is drawn from the central source-of-truth score counter and updated when the card gets a view[] event:
on view do score.data:scoreCounter.data end
(Note that when you change the score counter you will generally also want to call view[] to perform this update.)
Does that make sense?
If you're interested in making this even tidier- at the cost of some additional up-front complexity- I could describe how to use a contraption to build a "game HUD" that behaves like a single object shared across many cards.
There are many reasons a card can be sent a view[] event, including exiting editing tools, (re)opening a deck, or closing a dialog box. It should not be understood as an event that is fired when a card is visited at all, but rather a request to update the contents of a card. I do not recommend attempting to use timing as a method to disambiguate the origin of a view[] event; such methods are inherently brittle and error-prone.
To address may's problem, I would recommend altering the script on the originating card to send a new event- one not generated by Decker itself- to the destination card after the transition. Let's call the new event "visited":
on click do go["Next" "SlideDown"] deck.card.event["visited"] end
(Note that after go[] has completed, "deck.card" will refer to the card we just navigated to.)
The destination card can then fire the dialog in response to this "synthetic" event:
on visited do dd.open[deck a] dd.say["Kindling Village"] dd.close[] end
Reviewing vactur, it seems like some of these operations reimplement Lil primitives or brief idioms. For example,
vac.constant: on _ n c do on _ do c end @ (range n) end end
Replicates n copies of a constant c. This could be implemented in terms of the primitive "take"; possibly with an enclosed version of c if it may be something other than an atom:
3 take 5 # (5,5,5) 3 take list 11,22 # ((11,22),(11,22),(11,22))
Likewise,
vac.isin: on _ v s do t: vac.constant[(count v) 0] each x in s t: t | (v = x) end t end
The core of this loop could be expressed as
max each x in s v = x end
But the whole operation appears to be equivalent to the primitive "in":
(2,3,5) in (4,11,17,5,3) # (0,1,1)
The "partitioning[]" function is implemented in terms of "where" (a primitive in some APL-family languages, but not Lil);
vac.where: on _ m do extract index where value from m end vac.partitioning: on _ m do vac.where[m],vac.where[!m] end
But we can do the equivalent in a single query by using a "by" clause to group rows:
extract index by value from m
Not only does this reduce overhead, it makes the formulation more general:
vac.partitioning[1,0,1,1,0,1] # (0,2,3,5,1,4) extract index by value from 1,0,1,1,0,1 # (0,2,3,5,1,4) extract index by value from "ABAABA" # (0,2,3,5,1,4)
Please don't take this as discouraging your efforts; just pointing out some alternatives.
There are quite a few similar pirate websites spawned from the overwhelming popularity of WigglyPaint. None of them are by me, or created with my permission. I have no ability to update the version of WigglyPaint they provide, I cannot vouch for those sites being free of ads or malware, and many of them don't link to my itch.io pages or my personal website (beyondloom.com), so they actively prevent the discovery of my other projects.
I also have never published Android or iOS "apps" for WigglyPaint or Decker; anything you find to the contrary is illegitimate.
I would appreciate it if people refrained from linking to these pirate sites and apps. I don't use social media, so it is impossible for me to even consider correcting the countless misinformed users who share these URLs with one another. It is very frustrating to offer my work for free and still have it stolen.
When the arrow keys are pressed, a navigate[] event is sent to the current card with an argument of "up", "down", "left", or "right". If you wanted to make these events move a canvas, you might have an event handler in the card-level script something like
on navigate dir do
deltas:("up","down","left","right") dict ((list 0,-1),(list 0,1),(list -1,0),(list 1,0))
myCanvas.pos:myCanvas.pos+deltas[dir]*32
end
If you wanted that canvas to trigger "exits" when it matches certain positions, you could mark them with buttons (perhaps set to "Show None") and then send them a "click" event on an overlap; the buttons could in turn go[] to other cards, trigger alert boxes, etc.
on navigate dir do
deltas:("up","down","left","right") dict ((list 0,-1),(list 0,1),(list -1,0),(list 1,0))
myBug.pos:myBug.pos+deltas[dir]*32
each button in extract value where value..type="button" from card.widgets
if button.pos~myBug.pos button.event["click"] end
end
end
For a fully-worked example of a game that uses arrow keys for tile-based movement, you should take a look at Decker Sokoban.
If you give columns in a grid widget a format character of "L", "I", or "T", the column will not be user-editable. L displays cells in a column as plain strings (preformatted), I interprets cells as an icon index, and T displays cells as non-editable Rich Text.
Grids are containers for tables. To rearrange the rows of a table you probably want a query with an orderby clause. For example, sorting the contents of a grid "myGrid" by the "price" column, ascending, might require a button with a script like
on click do myGrid.value:select orderby price asc from myGrid.value end
Note that the default order[] event handler for grids offers something like this out of the box; you could overload this on a grid-by-grid basis if you wanted more elaborate behavior:
on order col do if !me.locked me.value:select orderby me.value[col] asc from me.value end end
If you have a column in a table, you can index it by name to get a list. The "fuse" operator can convert any list into a flat string:
myGrid.value.fruit
# ("cherry","banana","elderberry","apple","durian")
"" fuse myGrid.value.fruit
# "cherrybananaelderberryappledurian"
"|" fuse myGrid.value.fruit
# cherry|banana|elderberry|apple|durian
The simplest way to perform ciphering would be to use a dictionary. We can make a dictionary that maps characters to other characters like so:
rot13: "abcdefghijklmnopqrstuvwxyz" dict "nopqrstuvwxyzabcdefghijklm"
Given a string, we can use each character to index into our dictionary with the "@" operator:
rot13 @ "something else"
# ("f","b","z","r","g","u","v","a","t",nil,"r","y","f","r")
For characters like spaces, which aren't defined in the rot13 dictionary, we get a nil. As a catchall, we can use "fill" to substitute a generic replacement for all nils:
" " fill rot13 @ "something else"
# ("f","b","z","r","g","u","v","a","t"," ","r","y","f","r")
And if we wanted to collapse that sequence of characters into a flat string, we can use "fuse". Note that this step isn't necessary if we're stashing the result in a field's .text attribute; it implicitly fuses lists of strings as a convenience:
"" fuse " " fill rot13 @ "something else" # "fbzrguvat ryfr"
Putting that all together, given fields "input" and "output", give "input" a change[] handler:
on change do rot13: "abcdefghijklmnopqrstuvwxyz" dict "nopqrstuvwxyzabcdefghijklm" output.text:" " fill rot13 @ input.text end
And now we have a (crude) rot13 translator. It would probably be much more useful if we preserved all the non-alphabetic characters intact instead of turning them all into spaces. We can be a little fancier about how we construct the translation dictionary and address this:
on change do a1:"abcdefghijklmnopqrstuvwxyz" a2:"nopqrstuvwxyzabcdefghijklm" A1:"%u" format a1 A2:"%u" format a2 chars:"%a" format list range 256 rot13:(chars dict chars),(a1 dict a2),(A1 dict A2) output.text:rot13 @ input.text end
If you want to do a translation based on fixed-size bigrams or trigrams, or something along those lines, "window" might be useful:
2 window "SomeWords!"
#("So","me","Wo","rd","s!")
And you can use "drop" to exclude characters you wish to ignore (or conversely, "take" to retain only characters you care about):
(" ","!") drop "Some Words!"
# ("S","o","m","e","W","o","r","d","s")
For even fancier find-and-replace scenarios you could use RText.replace[]. Note that this returns a rich-text table and should be assigned to the .value attribute of a field, rather than .text, or converted into a string with rtext.string[].
rtext.replace["some text" ("me","ex") ("you","post")]
# +----------------+------+-----+-----+
# | text | font | arg | pat |
# +----------------+------+-----+-----+
# | "soyou tpostt" | "" | "" | 1 |
# +----------------+------+-----+-----+
rtext.string[rtext.replace["some text" ("me","ex") ("you","post")]]
# "soyou tpostt"
Does any of that help?
You have several approaches available.
The @ operator can be used to index a list, dictionary, or table by a list of indices or keys. For example:
items.value
# +--------------+-------+--------+
# | fruit | price | amount |
# +--------------+-------+--------+
# | "apple" | 1 | 1 |
# | "cherry" | 0.35 | 15 |
# | "banana" | 0.75 | 2 |
# | "durian" | 2.99 | 5 |
# | "elderberry" | 0.92 | 1 |
# +--------------+-------+--------+
items.value.fruit @ 0,2,3
# ("apple","banana","durian")
Indexing a table in this way gives us a list of rows (each a dictionary), but we can glue those back together with the "table" operator:
items.value @ 0,2,3
# ({"fruit":"apple","price":1,"amount":1},{"fruit":"banana","price":0.75,"amount":2},{"fruit":"durian","price":2.99,"amount":5})
table items.value @ 0,2,3
# +----------+-------+--------+
# | fruit | price | amount |
# +----------+-------+--------+
# | "apple" | 1 | 1 |
# | "banana" | 0.75 | 2 |
# | "durian" | 2.99 | 5 |
# +----------+-------+--------+
To clarify my earlier examples, in most applications the "in" operator attempts to check whether an item on the left (or each item in a list on the left) appears within a list on the right:
3 in 2,4,5 # 0 5 in 2,4,5 # 1 (3,5) in 2,4,5 # (0,1)
Within a query, you could do something roughly equivalent to our first example by referencing the implicit "magic column" named "index":
select where index in 0,2,3 from items.value # +----------+-------+--------+ # | fruit | price | amount | # +----------+-------+--------+ # | "apple" | 1 | 1 | # | "banana" | 0.75 | 2 | # | "durian" | 2.99 | 5 | # +----------+-------+--------+
If your desired indices are stored in a table, you'll need to pluck out a column of that table before you can compare it to another column with "in" or index with "@":
invgrid.value # +-----------+ # | itemIndex | # +-----------+ # | 0 | # | 2 | # | 3 | # +-----------+ invgrid.value.itemIndex # (0,2,3) table items.value @ invgrid.value.itemIndex # +----------+-------+--------+ # | fruit | price | amount | # +----------+-------+--------+ # | "apple" | 1 | 1 | # | "banana" | 0.75 | 2 | # | "durian" | 2.99 | 5 | # +----------+-------+--------+ select where index in invgrid.value.itemIndex from items.value # +----------+-------+--------+ # | fruit | price | amount | # +----------+-------+--------+ # | "apple" | 1 | 1 | # | "banana" | 0.75 | 2 | # | "durian" | 2.99 | 5 | # +----------+-------+--------+
You might consider keeping track of inventory items based on their names, or some other semantically meaningful "id" or key column, rather than by index; it could be more human-readable, and if you make any mistakes writing your queries it may be more obvious where something has gone wrong.
I would also like to note that grids aren't the only way to store collections of data within Decker, and may not always be the simplest choice. For example, you could also store a list of indices in a field widget via its .data attribute. Say we have a field named "myList":
myList.data:11,22,33
The field will visually show the data as "[11,22,33]". If we read myList.data we can reconstitute the list:
myList.data # (11,22,33)
Does that help?
Grid widgets, like this example named "items":

contain tables. You can read or write the table of a grid widget via the .value attribute of the grid. Tables can be indexed numerically (looking up rows) or by strings (looking up columns):
items.value
# +--------------+-------+--------+
# | fruit | price | amount |
# +--------------+-------+--------+
# | "apple" | 1 | 1 |
# | "cherry" | 0.35 | 15 |
# | "banana" | 0.75 | 2 |
# | "durian" | 2.99 | 5 |
# | "elderberry" | 0.92 | 1 |
# +--------------+-------+--------+
items.value[3]
# {"fruit":"durian","price":2.99,"amount":5}
items.value.fruit
# ("apple","cherry","banana","durian","elderberry")
In the simplest case of populating another grid with a specific column of a source grid, assigning a list to the destination grid's .value attribute will implicitly cast it to a table:
dest.value:items.value.fruit

For more complex transformations of data, there's Lil's query language:
select fruit price where amount>2 from items.value # +----------+-------+ # | fruit | price | # +----------+-------+ # | "cherry" | 0.35 | # | "durian" | 2.99 | # +----------+-------+
A query never modifies tables in-place; it returns a new table. Just like before, we can take the source grid's .value, query it, and assign the result to another grid's .value attribute:
dest.value:select fruit amount where fruit in "cherry","banana" from items.value

There are many examples of using queries on tables in the Lil reference manual section Lil, The Query Language. If you aren't using it already, Decker's Listeneris very helpful for trying queries interactively or generally testing out snippets of code.
Does any of that point you in a useful direction? I can try to furnish more specific examples of how to formulate queries for your project if you provide additional detail about how your data is organized.
This is really fun- thanks for sharing!
If you set the "shortcut" field for the buttons in your "navigator" contraption you can make them respond to the a/w/d keys for keyboard-based movement; it would also be possible to overload the top-level navigate[] event if you wanted to respond to cursor keys or swipe gestures as another input method.
The "rows" primitive turns a table into a list of dictionary, with each dictionary representing a row.
You can thus iterate over the rows of the table stored in a grid something like:
each row in rows mygrid.value # ... end
More broadly, though, what are you trying to do? There might be better alternatives depending on the application.
As a concrete example, assuming the default palette, if you wanted to set animated pattern 28 to red and black alternating as fast as possible you could use The Listener to issue the following Lil snippet:
patterns[28]:35,47
To halve the speed you could double up each pattern index, e.g.:
patterns[28]:35,35,47,47
There are more examples of this sort of thing in All About Color.
Is that clear?
Can you believe we've been doing this for three years now?
I just posted a summary of everything added and changed in Decker between versions 1.44 and 1.60: http://beyondloom.com/blog/unionstate3.html
You can set the .widths property of a grid widget to a list of column widths in pixels. Any columns thus set to 0px wide will be completely hidden from view and skipped over when navigating by cell. Grids will also automatically hide any rightmost columns that don't fit within the width of the widget, so making leftward columns a bit wider can scrunch the secret columns out of view. (Columns have resize handles in between column labels while in Interact mode!)
The .text attribute of a field is a string. Comparing numbers to strings in Lil can have surprising results, because in this case Lil makes the comparison lexicographically:
3>"12" # 1 3>12 # 0
To avoid this problem, you can use an arithmetic identity operation to coerce the string into a number before performing a comparison:
myField.text # "12" 0+myField.text # 12
Alternatively, use the .data attribute of the field instead: this will ask Decker to interpret the contents of the field as LOVE (a superset of JSON):
myField.text # "12" myField.data # 12 otherField.text # "[11,22,33]" otherField.data # (11,22,33)
Yet another option would be to store numbers in Slider Widgets instead of fields; a slider's .value attribute is always a number. Does that help?
I did some pondering and tried to come up with an approach that minimizes the amount of scripting needed:
http://beyondloom.com/decker/goofs/petquiz.html
The structure of the deck is as follows:

on click do go["Next"] end
When we get to the last card of the quiz, the user clicks a button to discover their result. The script there will find all the question cards based on the "q_" naming convention, look at all their checkboxes, and count up the number of checks for a given result name. The name which had the most checks will be the name of our result card:
on click do cards:extract value where value..name like "q_*" from deck.cards counts:sum each card in cards raze select key value..value where value..type="button" from card.widgets end go[first extract key orderby value desc from counts] end
This is probably a bit daunting. Using the Listener, we can break this script down step-by-step; assume we're on the card "decide" and we've already filled out our quiz responses.
Find the question cards:
cards:extract value where value..name like "q_*" from deck.cards # (<card>,<card>,<card>)
For now, consider only the first card. Find the button widgets and form a table from their names ("key") and checked status ("value..value"):
select key value..value where value..type="button" from cards[0].widgets # +-----------+-------+ # | key | value | # +-----------+-------+ # | "chicken" | 1 | # | "fish" | 0 | # | "dog" | 0 | # | "cat" | 0 | # +-----------+-------+
Razing a table forms a dictionary mapping its first column to its second column:
raze select key value..value where value..type="button" from cards[0].widgets
# {"chicken":1,"fish":0,"dog":0,"cat":0}
We'll need to do this process for each card, not just the first one:
each card in cards
raze select key value..value where value..type="button" from card.widgets
end
# ({"chicken":1,"fish":0,"dog":0,"cat":0},{"fish":0,"cat":1,"chicken":0,"dog":0},{"dog":0,"chicken":1,"cat":0,"fish":0})
If we sum a list of dictionaries, we'll get a dictionary with the union of the keys in each dictionary and the sum of all the corresponding values:
counts:sum each card in cards
raze select key value..value where value..type="button" from card.widgets
end
# {"chicken":2,"fish":0,"dog":0,"cat":1}
To find the key with the largest value, we'll sort the keys by the values (descending):
extract key orderby value desc from counts
# ("chicken","cat","fish","dog")
The first element of that list is our answer:
first extract key orderby value desc from counts # "chicken"
So we can go[] to the corresponding card by name:
go[first extract key orderby value desc from counts]
This query doesn't care about how many results exist, how many questions are in the quiz, or how many answers are available for each question; you can easily add to the quiz or make an entirely different quiz without modifying the query.
(Note: this quiz won't work right if your checkboxes are named inconsistently. If you seem to get goofy results, you can try the above step-by-step in the Listener to see if you get any unexpected options in this result list!)
Does that make sense?
You could make a dictionary that maps slider names to result cards and "go[]" to the card corresponding to the top-ranked result:
on click do sliders:likesDogs,likesCats,likesFish,likesChickens order:extract value..name orderby value..value desc from sliders results.likesDogs:"TheDogCard" results.likesCats:"TheCatCard" results.likesFish:"TheFishCard" results.likesChickens:"TheChickensCard" go[results[first order]] end
If your quiz result cards are named to correspond to the sliders, you could skip the dictionary and "go[]" to a card by name:
on click do sliders:likesDogs,likesCats,likesFish,likesChickens order:extract value..name orderby value..value desc from sliders go[first order] end
(Using consistent naming conventions for cards and widgets can often make scripts a good bit simpler!)
If the sliders aren't on the same card as the result-calculating button, you'll need to reference them through their container card. Say the card with the counters is named "counters":
on click do sliders:counters.widgets.likesDogs, counters.widgets.likesCats, counters.widgets.likesFish, counters.widgets.likesChickens order:extract value..name orderby value..value desc from sliders go[first order] end
Or- more concisely-
on click do sliders:counters.widgets @ "likesDogs","likesCats","likesFish","likesChickens" order:extract value..name orderby value..value desc from sliders go[first order] end
Or, if the sliders are the only widgets on that card:
on click do sliders:range counters.widgets order:extract value..name orderby value..value desc from sliders go[first order] end
Does that help at all?
Decker is fairly well-suited to making dollmaker-style games.

I did a brief writeup on modifying The Ornamented Ovum over on the Decker community forum: https://itch.io/t/2769322/the-ornamented-ovum . Anyone is welcome to use this game as a starting point for their own creations!
Decker comes with a number of interactive tutorial decks, but the most immediately relevant ones might be All About Draggable and All About Color.
FlickGame is a very simple game engine for making short-form point-and-click games; you can find hundreds of games created with this tool here on itch.io alone!
Today I wrote a tool- Flicker- with Decker which can decode .html exports generated by FlickGame and convert them into a deck with the same behavior. It is then possible to use Decker's normal editing tools and scripting capabilities to enhance or modify the games, or simply run them on machines that don't have a web browser. The tool can use a customizable naming convention for producing cards to help support merging multiple FlickGames into a single deck.

Try it here with any of the games on the FlickGame gallery!
I hope this is a helpful tool for FlickGame enthusiasts and a useful demonstration of how Decker can make tools that in turn create other decks. Questions, anyone?
Millie helped me track down the source of your reported crash. A fix is included in Decker 1.60.