Skip to content
HN On Hacker News ↗

Four Years with Decker

▲ 63 points • 17 comments • by ibobev • 2w ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is human-written.

0 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 1 of 1
SEGMENTS · AI 0 of 1
WORD COUNT 1,504
PEAK AI % 0% · §1
Analyzed
Oct 21
backend: pangram/v3.3
Segments scanned
1 windows
avg 1504 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,504 words · 1 segments analyzed

Human AI-generated
§1 Human · 0%

Four Years With Decker October is upon us again, so as usual it’s time to take stock of Decker’s progress over the past year, this time between releases 1.61 and 1.71: New Horizons: capabilities which expand the range of projects that are possible with Decker. Growing a Language: improvements to Lil, Decker’s scripting language. The Deckerverse: highlights from Decker’s userbase. A Pledge for Human Creativity: a promise. New Horizons One of the most prominent new features introduced to Decker this year is support for gamepad input, with fallback to keyboard controls where available. Many Decker users are squarely interested in building video games, and gamepad brings arcade and action genres within reach: The gamepad tester deck There’s still significant room to build out more examples and libraries that demonstrate what’s possible with this functionality. For starters, the sokoban example is now playable with a gamepad or keyboard. The dialogizer module quietly upgrades any game that uses it to support keyboard controls for advancing dialog boxes and making choices in dialog trees. Dialogizer also takes advantage of the new app.cursor field to simulate the hover behavior of ordinary buttons while in “synchronous” scripts. Another important enhancement requires no action on the part of users: better image compression. For the sake of simplicity, Decker originally relied upon simple bit-packing and run-length encoding when storing graphics, but datablock formats left room for introducing better codecs in the future. The new IMG3 image encoder shares the LZW-based innards of Decker’s GIF codec, and achieves a 2–4x improvement in overall filesize for many decks, especially those which make use of dithered color graphics. In situations where the new codec doesn’t perform any better than IMG0, IMG1, or IMG2, the old codecs are selected. E-Zine authors can now fit a heck of a lot more on a floppy disk. Decker’s contraptions are subtly more capable with the introduction of new “attribute types”, which control the custom UI for configuring contraption settings. These attributes can now be "data" or "note". A "data" attribute is displayed as and parsed from a field of LOVE, making it easier to expose structured values without needing to invent one-off textual formats for describing them. A "note" attribute displays an inline block of prose instead of offering an editor, providing more ways for contraptions to be self-documenting: A contraption with a note attribute explaining how to use it Support for color continues to expand and improve. All widgets now have a .pattern attribute that controls their appearance in combination with the existing .show and .font attributes. You can quickly alter the pattern of selected widgets via Widgets → Pattern… in the menu or simply clicking a pattern swatch on the right toolbar: Changing the .pattern attribute of widgets. With rich text selected, you can now likewise use the right toolbar to quickly change the pattern or color of text: Changing the pattern of rich text runs. I’ve made many improvements to existing features to make authoring decks easier and more intuitive, too. For example, pasting images into scaled canvases now adjusts the dimensions of the “logical size” of the canvas, to avoid accidentally cropping the pasted image, and images in the clipboard can be pasted as new canvas widgets from any editor mode: Pasting an image into a canvas and as a new canvas. The Font/DA Mover displays more useful information about resources, including their contribution to the size of a deck on disk. You can now also access the scipt of Lil module from this dialog: Viewing resource metadata. As usual, there have been a variety of additions to scripting APIs: The System Interface now features sys.z for obtaining the local time zone offset, and sys.ops, for measuring the work done by Lil’s bytecode interpreter. The App Interface has gained app.gridsize, app.penstroke, and app.penfill, for inspecting and modifying the state of Decker’s editing tools, app.kiosk for locking down Decker in display environments, and app.params for accessing URL parameters provided to Web-Decker. The RText Interface offers rtext.trim[] as a more general version of the new trim operator for rich text tables. The Sound Interface linearly interpolates across samples when accessed via non-integral sample indices, the sound.scale[] function can squash, stretch, and reverse waveforms with respect to time, and the read["sound" "samples"] overload can import large audio files as an Array of samples. The Image Interface provides image.transform["turn"] as a shortcut for rotating images 180 degrees, image.outline[] for adding a border along the edges of any opaque region of the image, and image.merge[] now supports XOR-compositing with the "^" operator. The Danger Interface offers danger.open[], for instructing Decker to open a specific document, either by URL/path or by a Deck Interface, making it possible to build launchers or stitch together very large projects from multiple decks. The Array Interface now supports casts of "f32l", "f32b", "f64l", or "f64b", for working with floating-point values, and the array.value attribute offers a convenient shortcut for converting an entire array into a primitive list or string. Growing a Language The evolution of Decker’s scripting language, Lil, continues to “cool down” toward stability, but there have been a few refinements and additions, starting with some corrections to the behavior of several operators: The comparison operators < and > are overloaded to perform numeric comparisons when given a pair of numeric arguments, and to lexicographically compare a pair of strings. This is quite handy! When given values of mixed types, these operators originally “preferred” to interpret both arguments as if they were strings. In practice, this behavior lead to some extremely silly and confusing results for common situations, such as users storing a number in a field and accessing the field’s .text attribute (a string) instead of its .data attribute (which in this case would be numeric): theField.text      # "23"3 > theField.text  # 1 The < and > operators now instead “prefer” a numeric interpretation if either argument is numeric, consistent with the behavior of the | (maximum) and & (minimum) operators: 3 > "23"   # 030 > "23"  # 1 I also corrected a subtler flaw in the behavior of the table operator. This operator will examine the structure of its argument to coerce both list-of-dict and list-of-list data into a Lil table value. With the introduction of the nil value, it was helpful to adjust this coercion behavior, accepting nils in a list as empty lists, and nils in a list of dictionaries as empty dictionaries: table "%j" parse "[[11,22],null,['a','b']]"# +-----+-----+# | c0 | c1 |# +-----+-----+# | 11 | 22 |# | nil | nil |# | "a" | "b" |# +-----+-----+table "%j" parse "[{'a':11},null,{'a':22}]"# +-----+# | a |# +-----+# | 11 |# | nil |# | 22 |# +-----+ Finally, I made some adjustments to the raze operator, which is responsible for reducing lists as if through repeated applications of the , operator. The base cases of raze would originally collapse single-element values into atoms, and the revised behavior keeps them lists, for better internal consistency: raze list 23  # was 23 now is (23)raze 23       # was 23 now is (23)raze "AB"     # was ("A","B") now is ("AB") Lil gained four new monadic operators to simplify common idioms: trim, ceil, rev, and distinct. The trim operator removes leading and/or trailing whitespace from a string. It can conform across a whole list for cleaning up entire columns of input: trim " ab c\n"      # "ab c"trim (" a"," b")     # ("a","b") The ceil (ceiling) operator rounds numbers up, a counterpart to floor. This also conforms: ceil (0.1,2.5,37.9)  # (1,3,38) The rev operator reverses the order of elements a string, list, or dict; this is like |: in K: rev "abc"            # "cba"rev (11,22,33)       # (33,22,11)rev "abc"dict"def"   # {"d":"a","e":"b","f":"c"} The distinct operator extracts the non-duplicate elements from a string, list, dict, or table; this is like ?: in K: distinct "cabba"     # "cab"distinct (2,1,3,2,1) # (2,1,3) The trim operator avoids unnecessary internal string copies when used to trim prefixes- as in many parsers- and distinct uses internal hashsets much like in. Used appropriately, the new operators can have a dramatic positive impact on performance. Benchmarking practical code also revealed some simple ways to make while loops slightly more efficient and improve the performance of unions and amends of dictionaries. The Bits interface was extended with .decode[] and .encode[] operations for breaking down numbers into lists of binary digits (which we can readily manipulate with Lil’s conforming arithmetic operators) or repacking binary digits into numbers, respectively. This helps handle a number of common data-preparation tasks which previously required explicit loops: bytes:112,112,32,112,168,32,80,80i:image[8,count bytes]i.pixels:bits.decode[8 bytes]i.pixels# ((0,1,1,1,0,0,0,0),# (0,1,1,1,0,0,0,0),# (0,0,1,0,0,0,0,0),# (0,1,1,1,0,0,0,0),# (1,0,1,0,1,0,0,0),# (0,0,1,0,0,0,0,0),# (0,1,0,1,0,0,0,0),# (0,1,0,1,0,0,0,0))bits.encode[8 i.pixels]# (112,112,32,112,168,32,80,80) Another tool for squeezing extra performance from Lil is the newly generalized KeyStore Interface. Originally designed as a means for modules to serialize persistent data, KeyStores can now also be freely constructed with the keystore[] function and used as general-purpose mutable “dictionary-builders” whenever it is undesirable to construct an entire dictionary value at once with dict. This kind of generalization helps Lil’s concise standard library pack more punch!