Updated 20260820
- Decker 1.70 and my optimizations make it smoother
https://codeberg.org/woodring/decker.decker-xdc/src/branch/decker-xdc/noxdc.html contains both fixes for comparison
Both in conjunction appear to help. Wigglypaint is the smoothest with both patches. I can draw with the pen and the marker doesn’t skip when it paints under it. Normally, it skips a lot for me. Also, the other way I can tell is if I load my Huffman deck and paste a large amount of text it it. Usually the menu and the listener are nearly impossible to use. With these, it’s somewhat acceptable, though still slow.
I didn’t know what was the best way other than eyeballing it. I can tell it’s smoother, but for “raw computation” it doesn’t make a difference. I don’t know if that’s due to how things get timesliced between rendering and compute.
If you can tell me a proper way to benchmark this, I can do the hard work to test it if it’s worthwhile - I know empirical eyeballing is a bit hard to judge.
I’m less sure if this helps, because on top of the other one, I can’t tell if it does. I’ll have to test without the other optimization.
I know if I do this sort of lookup dispatch in my C forths/lisps, they don’t help. Usually the switch-case is best - probably due to inlining rather than function pointer tables. I’ve never sat down to look, just empirical testing. But, maybe it helps in JS.
turns the switch-case into a lookup table:
diff --git a/js/lil.js b/js/lil.js
index 31b10f4..a88963c 100644
--- a/js/lil.js
+++ b/js/lil.js
@@ -714,50 +714,52 @@ docall=(f,a,tail)=>{
issue(f.a.length==1&&f.a[0][0]=='.'?env_bind(f.c,[f.a[0].slice(3)],monad.list(a)): env_bind(f.c,f.a,a),f.b)
calldepth=max(calldepth,state.e.length)
}
+ops={
+ [op.DROP ]:(o,imm,b)=>{arg()},
+ [op.DUP ]:(o,imm,b)=>{const a=arg();ret(a),ret(a);},
+ [op.SWAP ]:(o,imm,b)=>{const a=arg();b=arg();ret(a),ret(b);},
+ [op.OVER ]:(o,imm,b)=>{const a=arg();b=arg();ret(b),ret(a),ret(b);},
+ [op.JUMP ]:(o,imm,b)=>{setpc(imm)},
+ [op.JUMPF]:(o,imm,b)=>{if(!lb(arg()))setpc(imm)},
+ [op.JUMPT]:(o,imm,b)=>{if( lb(arg()))setpc(imm)},
+ [op.LIT ]:(o,imm,b)=>{ret(blk_getimm(b,imm))},
+ [op.GET ]:(o,imm,b)=>{ret(env_get(getev(),blk_getimm(b,imm)));},
+ [op.SET ]:(o,imm,b)=>{const v=arg();env_set(getev(),blk_getimm(b,imm),v),ret(v);},
+ [op.LOC ]:(o,imm,b)=>{const v=arg();env_local(getev(),blk_getimm(b,imm),v),ret(v);},
+ [op.LOCS ]:(o,imm,b)=>{env_locals(getev(),blk_getimm(b,imm));},
+ [op.BUND ]:(o,imm,b)=>{const r=[];for(let z=0;z<imm;z++)r.push(arg());r.reverse(),ret(lml(r));},
+ [op.OP1 ]:(o,imm,b)=>{ ret(monadi[imm](arg()));},
+ [op.OP2 ]:(o,imm,b)=>{const y=arg();ret(dyadi [imm](arg(),y));},
+ [op.OP3 ]:(o,imm,b)=>{const z=arg(),y=arg();ret(triadi[imm](arg(),y,z));},
+ [op.IPRE ]:(o,imm,b)=>{const s=arg(),i=arg();ret(i),docall(s,i.v[imm]);if(lion(s)||lii(s)||linat(s)){for(let z=0;z<=imm;z++)i.v[z]=null}},
+ [op.IPOST]:(o,imm,b)=>{const s=arg(),i=arg(),r=arg();ret(i.v[imm]?r:s),ret(i),ret(s);},
+ [op.AMEND]:(o,imm,b)=>{
+ let v=arg(),r=arg(),i=ll(arg()),ro=arg(),n=blk_getimm(b,imm),t={v:1}
+ if(i.length&&!i[0]){i=i.filter(x=>x),t.v=0}r=amendv(ro,i,v,0,t);if(t.v&&!lin(n))env_set(getev(),n,r);ret(r);
+ },
+ [op.CALL ]:(o,imm,b)=>{const a=arg(),f=arg();docall(f,a,o==op.TAIL);}, // same as tail
+ [op.TAIL ]:(o,imm,b)=>{const a=arg(),f=arg();docall(f,a,o==op.TAIL);},
+ [op.BIND ]:(o,imm,b)=>{const f=arg(),r=lmon(f.n,f.a,f.b);r.c=getev(),env_local(getev(),lms(f.n),r),ret(r);},
+ [op.ITER ]:(o,imm,b)=>{const x=arg();ret(lil(x)?x:ld(x));ret(lid(x)?lmd():lml([]));},
+ [op.FIDX ]:(o,imm,b)=>{const x=arg(),f=arg();if((lid(f)||lil(f)||lis(f))&&lil(x)){ret(lml(x.v.map(x=>l_at(f,x))));setpc(imm)}else{ret(x)};},
+ [op.FMAP ]:(o,imm,b)=>{const x=arg(),f=monadi[imm];ret(lid(x)?lmd(x.k,x.v.map(f)):lml(ll(x).map(f)));},
+ [op.EACH ]:(o,imm,b)=>{
+ const n=arg(),r=arg(),s=arg();if(count(r)==count(s)){setpc(imm),ret(r);}
+ else{const z=count(r), v=lml([s.v[z],lid(s)?s.k[z]:lmn(z),lmn(z)]);
+ state.e.push(env_bind(getev(),n,v)),ret(s),ret(r);}
+ },
+ [op.NEXT ]:(o,imm,b)=>{const v=arg(),r=arg(),s=arg();state.e.pop();if(lid(r))r.k.push(s.k[r.v.length]);r.v.push(v),ret(s),ret(r),setpc(imm);},
+ [op.COL ]:(o,imm,b)=>{
+ const ex=arg(),t=arg(),n=tab_cols(t),v=ll(monad.cols(t));ret(t)
+ n.push('column'),v.push(t),issue(env_bind(getev(),n,lml(v)),ex);
+ },
+}
runop=_=>{
op_count+=1
const b=getblock();if(!liblk(b))ret(state.t.pop())
const pc=getpc(),o=blk_getb(b,pc),imm=(oplens[o]==3?blk_gets(b,1+pc):0); setpc(pc+oplens[o])
- switch(o){
- case op.DROP :arg();break
- case op.DUP :{const a=arg();ret(a),ret(a);break}
- case op.SWAP :{const a=arg(),b=arg();ret(a),ret(b);break}
- case op.OVER :{const a=arg(),b=arg();ret(b),ret(a),ret(b);break}
- case op.JUMP :setpc(imm);break
- case op.JUMPF:if(!lb(arg()))setpc(imm);break
- case op.JUMPT:if( lb(arg()))setpc(imm);break
- case op.LIT :ret(blk_getimm(b,imm));break
- case op.GET :{ret(env_get(getev(),blk_getimm(b,imm)));break}
- case op.SET :{const v=arg();env_set(getev(),blk_getimm(b,imm),v),ret(v);break}
- case op.LOC :{const v=arg();env_local(getev(),blk_getimm(b,imm),v),ret(v);break}
- case op.LOCS :{env_locals(getev(),blk_getimm(b,imm));break}
- case op.BUND :{const r=[];for(let z=0;z<imm;z++)r.push(arg());r.reverse(),ret(lml(r));break}
- case op.OP1 :{ ret(monadi[imm](arg() ));break}
- case op.OP2 :{const y=arg();ret(dyadi [imm](arg(),y ));break}
- case op.OP3 :{const z=arg(),y=arg();ret(triadi[imm](arg(),y,z));break}
- case op.IPRE :{const s=arg(),i=arg();ret(i),docall(s,i.v[imm]);if(lion(s)||lii(s)||linat(s)){for(let z=0;z<=imm;z++)i.v[z]=null}break}
- case op.IPOST:{const s=arg(),i=arg(),r=arg();ret(i.v[imm]?r:s),ret(i),ret(s);break}
- case op.AMEND:{
- let v=arg(),r=arg(),i=ll(arg()),ro=arg(),n=blk_getimm(b,imm),t={v:1}
- if(i.length&&!i[0]){i=i.filter(x=>x),t.v=0}r=amendv(ro,i,v,0,t);if(t.v&&!lin(n))env_set(getev(),n,r);ret(r);break
- }
- case op.CALL : // fall through:
- case op.TAIL :{const a=arg(),f=arg();docall(f,a,o==op.TAIL);break}
- case op.BIND :{const f=arg(),r=lmon(f.n,f.a,f.b);r.c=getev(),env_local(getev(),lms(f.n),r),ret(r);break}
- case op.ITER :{const x=arg();ret(lil(x)?x:ld(x));ret(lid(x)?lmd():lml([]));break}
- case op.FIDX :{const x=arg(),f=arg();if((lid(f)||lil(f)||lis(f))&&lil(x)){ret(lml(x.v.map(x=>l_at(f,x))));setpc(imm)}else{ret(x)};break}
- case op.FMAP :{const x=arg(),f=monadi[imm];ret(lid(x)?lmd(x.k,x.v.map(f)):lml(ll(x).map(f)));break}
- case op.EACH :{
- const n=arg(),r=arg(),s=arg();if(count(r)==count(s)){setpc(imm),ret(r);break}
- const z=count(r), v=lml([s.v[z],lid(s)?s.k[z]:lmn(z),lmn(z)]);
- state.e.push(env_bind(getev(),n,v)),ret(s),ret(r);break
- }
- case op.NEXT :{const v=arg(),r=arg(),s=arg();state.e.pop();if(lid(r))r.k.push(s.k[r.v.length]);r.v.push(v),ret(s),ret(r),setpc(imm);break}
- case op.COL :{
- const ex=arg(),t=arg(),n=tab_cols(t),v=ll(monad.cols(t));ret(t)
- n.push('column'),v.push(t),issue(env_bind(getev(),n,lml(v)),ex);break
- }
- }while(running()&&getpc()>=blk_here(getblock()))descope()
+ ops[o](o,imm,b)
+ while(running()&&getpc()>=blk_here(getblock()))descope()
}
fchar=x=>x=='I'?'i': x=='B'?'b': x=='L'?'s': x=='t'?'J': x=='T'?'J': x
Oh - the hashes probably aren’t right if you try to do a git apply. It’s from my fork of decker that I use for WebXDC. And, I fixed a small thing just now in the patch that does !==undefined where it was doing >=0 before (from indexOf to an Object/hashmap).
And, yeah, I loaded a Wigglypaint in the old version, and I can tell the difference between the old and new. It’s a lot smoother.
I couldn’t figure out a good way to benchmark it, but this feels snappier, unless I’m experiencing placebo. Certainly, I wasn’t seeing any speedup in the listener operations (did a simple fibonacci) but I could do something like paste a lot of text into a field and the UI wouldn’t completely bog down. It was still responsive.
Another one is Wigglypaint marker didn’t skip for me. Normally with my laptop (old Thinkpad) Wigglypaint JS, the marker is not a fluid line. I’m pretty sure this isn’t placebo, but again, I couldn’t figure out a good benchmark other than “feeling” the snappiness.
patch to lil.js that turns operation look ups to be inverse hashmaps:
diff --git a/js/lil.js b/js/lil.js
old mode 100755
new mode 100644
index 5c89b4d..07163fa
--- a/js/lil.js
+++ b/js/lil.js
@@ -237,6 +237,7 @@ monad={
return r
},
}
+monadk=Object.fromEntries(Object.keys(monad).map((e,i)=>[e,i]))
dyad={
'+': vd((x,y)=>lmn(ln(x)+ln(y))),
'-': vd((x,y)=>lmn(ln(x)-ln(y))),
@@ -453,6 +454,7 @@ table_swap_rows=(tab,a,b)=>{
if(a==b||a<0||b<0||a>tab_rowcount(tab)-1||b>tab_rowcount(tab)-1)return tab
const pv=monad.range(lmn(tab_rowcount(tab)));pv.v[a]=lmn(b),pv.v[b]=lmn(a);return dyad.take(pv,tab)
}
+dyadk=Object.fromEntries(Object.keys(dyad).map((e,i)=>[e,i]))
triad={
'@orderby': (col,tab,order_dir)=>{
const rt=orderby(tab,dyad.take(lmn(count(tab)),lml(ll(col))).v,ln(order_dir))
@@ -477,8 +479,9 @@ triad={
return lin(x)?r:dyad[','](lt(x),r)
},
}
+triadk=Object.fromEntries(Object.keys(triad).map((e,i)=>[e,i]))
-findop=(n,prims)=>Object.keys(prims).indexOf(n), as_enum=x=>x.split(',').reduce((x,y,i)=>{x[y]=i;return x},{})
+findop=(n,prims)=>prims[n], as_enum=x=>x.split(',').reduce((x,y,i)=>{x[y]=i;return x},{})
let tnames=0;tempname=_=>lms(`@t${tnames++}`)
op=as_enum('JUMP,JUMPF,JUMPT,LIT,DUP,DROP,SWAP,OVER,BUND,OP1,OP2,OP3,GET,SET,LOC,LOCS,AMEND,TAIL,CALL,BIND,ITER,EACH,NEXT,COL,IPRE,IPOST,FIDX,FMAP')
oplens= [ 3 ,3 ,3 ,3 ,1 ,1 ,1 ,1 ,3 ,3 ,3 ,3 ,3 ,3 ,3 ,3 ,3 ,1 ,1 ,1 ,1 ,3 ,3 ,1 ,3 ,3 ,3 ,3 ]
@@ -492,9 +495,9 @@ blk_gets=(x,i )=>0xFFFF&(blk_getb(x,i)<<8|blk_getb(x,i+1))
blk_op =(x,o )=>{blk_addb(x,o);if(o==op.COL)blk_addb(x,op.SWAP)}
blk_opa =(x,o,i)=>{blk_addb(x,o),blk_adds(x,i);return blk_here(x)-2}
blk_imm =(x,o,k)=>{let i=x.locals.findIndex(x=>match(x,k));if(i==-1)i=x.locals.length,x.locals.push(k);blk_opa(x,o,i)}
-blk_op1 =(x,n)=>blk_opa(x,op.OP1,findop(n,monad))
-blk_op2 =(x,n)=>blk_opa(x,op.OP2,findop(n,dyad ))
-blk_op3 =(x,n)=>blk_opa(x,op.OP3,findop(n,triad))
+blk_op1 =(x,n)=>blk_opa(x,op.OP1,findop(n,monadk))
+blk_op2 =(x,n)=>blk_opa(x,op.OP2,findop(n,dyadk ))
+blk_op3 =(x,n)=>blk_opa(x,op.OP3,findop(n,triadk))
blk_lit =(x,v)=>blk_imm(x,op.LIT,v)
blk_set =(x,n)=>blk_imm(x,op.SET,n)
blk_loc =(x,n)=>blk_imm(x,op.LOC,n)
@@ -659,10 +662,10 @@ parse=text=>{
blk_op3(b,'@ins');return
}
if(matchsp('(')){if(matchsp(')')){blk_lit(b,lml([]));return}expr(b),expect(')');return}
- const s=peek().v;if(findop(s,monad)>=0&&({'symbol':1,'name':1})[peek().t]){
+ const s=peek().v;if(findop(s,monadk)!==undefined&&({'symbol':1,'name':1})[peek().t]){
next();if(matchsp('@')){
let depth=0,l=lmblk();while(matchsp('@'))depth++
- expr(b),blk_opa(l,op.FMAP,findop(s,monad))
+ expr(b),blk_opa(l,op.FMAP,findop(s,monadk))
while(depth-->0){const t=tempname(),m=lmblk();blk_loop(m,[ls(t)],_=>{blk_get(m,t),blk_cat(m,l)}),l=m}
blk_cat(b,l)
}else{expr(b),blk_op1(b,s)};return
@@ -678,7 +681,7 @@ parse=text=>{
blk_sets(l,fidx,blk_here(l))
while(depth-->0){const t=tempname(),m=lmblk();blk_loop(m,[ls(t)],_=>{blk_get(m,t),blk_cat(m,l)}),l=m}
blk_cat(b,l);return
- }const s=peek().v;if(findop(s,dyad)>=0&&({'symbol':1,'name':1})[peek().t]){next(),expr(b),blk_op2(b,s)}
+ }const s=peek().v;if(findop(s,dyadk)!==undefined&&({'symbol':1,'name':1})[peek().t]){next(),expr(b),blk_op2(b,s)}
}
const b=lmblk();if(hasnext())expr(b);while(hasnext())blk_op(b,op.DROP),expr(b)
if(blk_here(b)==0)blk_lit(b,NIL);return b
Updated 20260820
Updated 20260713
Old historical, see my top post in this thread for the latest single widget version
https://woodring.itch.io/patternshop tutorial and deck
See: https://missooni.itch.io/patternshop-cinnamon for a Decker drawing with layers
https://woodring.itch.io/patternshop
Also: see https://missooni.itch.io/patternshop-cinnamon for a more Decker-like experience with layers
reload if the video is not playing

tutorial and playable deck: https://woodring.itch.io/patternshop
direct download of deck: https://codeberg.org/woodring/decker.patternshop/raw/branch/main/patternshop.deck
project and readme, which is pasted below: https://codeberg.org/woodring/decker.patternshop/src/branch/main
see https://codeberg.org/woodring for more decker things
It’s a module and several contraptions that emulate layered drawing in other drawing tools. Or, it can be thought of as “Decker mega-underpaint mode.”
https://woodring.itch.io/patternshop
make at least three cards
put the patternshop contraption on each of those cards.
switch to interactive mode.
press 0 (zero) on each of the cards to activate the
contraption.
draw on the first card in interactive mode.
switch to the second card. what was drawn on the first card appears there.
draw on the second card.
switch to the third card. what was drawn on the second will be under the first.
et voila, layered drawing!
Why is this important? Imagine:
card1 has a rough sketch.
card2 shows card1, to trace over card1’s sketch.
card1 is deactivated, automatically removing the sketch, without having to manually erase it from card2.
card3 shows the outline of card2, to add shading without worrying about staying exactly in the lines.
patternshop is a contraption that combines multiple images into
one. In another PS or other drawing programs, it’s what’s called
layering. In graphics, it’s what we call compositing or
image composition.
Another analogy is that it’s like old school cel drawing, for animation, where the scene is composed of several stacked layers of transparent celluloid.
The mode of operation is to draw on the patternshop canvas,
which is a transparent drawing surface, in front of the card
background. All of the active patternshop drawing surfaces are
combined together - in a stack. Then, the combined stack is
copied onto each card background of each card that patternshop
is on. This provides the combined result on each layer - i.e.,
card.
Images are combined in card order. To change it so a drawing
appears under another, reorder the cards in the Decker menu, i.e.
File->Cards. Later cards in the stack appear under the ones
in front of it. For n layers, make n+1 cards with
patternshop on it. The final card will show everything
together, by itself.
IMPORTANT: Drawing on patternshop requires interactive
mode - not drawing mode. Decker drawing mode only works on
cards. To use the Decker drawing tools, draw on a card then use
the patternshop swap function to move the image to the
patternshop contraption. Alternatively, use ps_layercake
contraption to copy it.
When drawing on a card, with an image on the patternshop
canvas, it will be under the canvas - which is what might be
wanted for more accurate underpainting. Drawing on patternshop
only supports the freehand pencil with custom brushes and
patterns.
But, what makes patternshop different is that allows for
drawing on the canvas and simultaneously seeing the card
background underneath - to trace over it. It’s like Decker’s
built-in underpainting, but with patternshop there’s an
arbitrary number of underpainting layers.
Note: the only composite mode that patternshop supports is
under. To achieve over, move the card to the front of the
card stack. The other compositing modes that Decker likely aren’t
used in practice by most people and are not included.
To save your work, save the deck. Everything is retained between sessions.
Reordering cards to change layer combining order is immediate, after closing the card order dialog.
The starting visibility of patternshop and ps_layercake is
transparent for the canvas to provide tracing over the card
background, by default. They can both toggle between “solid”,
“transparent”, “invert”, and “none”.
Everything is hotkey driven - for two reasons: (1) save screen
real estate, but more importantly (2) when drawing, especially
with a tablet, it is common to have a hand hovering over
hotkeys or program the tablet for the hotkeys. It starts with
a toolbar that can be hidden with q after learning the
hotkeys.
4, Z, and X only work with hotkeys. They
are visually present to show what they hotkey is.As noted earlier, there’s a second contraption, ps_layercake,
which is an advanced rubberband/select box that knows how to
work with patternshop contraptions and cards.
The patternshop and ps_layercake contraptions can be used
in any Decker project, as long as the ps module is copied
over, too. They will not work without it.
Switching to a card will composite (combine) all layers if
there is an activate patternshop on a card. It only updates
“on view”; i.e., changing cards or having an animated widget on
that card. But, having an animated widget on a patternshop
card will slow Web Decker down quite significantly. While
drawing, I do not suggest having animated widgets on the cards.
patternshoppatternshop home row
1: toggle canvas visibility between “solid”, “transparent”,
“invert” and “none”. starts in “transparent”. “solid” will hide
the card background and “none” will hide the canvas.
2: toggle to the last brush used. starts with brush “1”.
3: toggle to pattern 0 and back to the last pattern used.
4: eyedropper tool that picks a pattern under the cursor. picks
from the canvas first and the card second. does not change the
pattern if it is pattern 0.
5: goto a card named palette. to be used in concert with 4,
w, e, and ps_layercake’s custom brush function - to store
patterns and brushes on a card, like a real-world palette. the
palette card can contain whatever you wish - as long as it has
a patternshop contraption on it. pressing 5 when on the palette
card returns you to the previous card.
after learning the hotkeys or needing to temporarily move it
q: hide the toolbar
palette support
w: copy the brush from the palette card.
e: copy the pattern from the palette card
off patternshop home row
6: swap the image between the canvas and the card. to use
Decker drawing tools - draw on the card and then swap it to the
canvas.
7: erase the canvas.
8: set the brush. it will ask to provide a brush number or
custom brush name. 2 swaps between two brushes.
9: set the pattern. it will ask to provide a pattern number in.
for quicker pattern changing, use 4 and 5 with 3.
0: activate/deactivate the patternshop on this card. once
activated, it will show all other active patternshops combined
in card order. to remove a layer, deactivate it with 0.
IMPORTANT activating a pattershop will ERASE any
existing card background image.
undo and redo
z: infinite undo for canvases - up to available memory. no
effect for cards. TODO need to put an undo limiter in to not
accidentally run out of memory.
x: redo after undo. drawing after redoing will clear the redo
stack.
ps_layercake support
r: make a ps_layercake at 100,100
ps_layercake hotkey to support patternshop
y: remove all layercake instances from the card. IMPORTANT:
if there is more than one on a card, it will remove ALL of
them.
ps_layercakeps_layercake is a select/rubberband box companion for
patternshop. It can copy and move selections around the canvas
and card background, more selectively than the patternshop swap
(6) function.
It works in interactive mode, like patternshop. The upper-left
box moves it and the lower-right box resizes it. All of the other
controls are hotkey driven, like the rest of patternshop.
Also, it can be copy and pasted to any other card, just like any other contraption, moving the copied image between cards.
layercake to-and-from the card background
c: copy card background into layercake.
v: paste layercake onto card background.
n: merge card background into layercake. this is
different than c, because it treats pattern 0 as transparent.
m: merge layercake onto card background. like n,
this is different than v, because it also treats pattern 0 as
transparent.
layercake to-and-from the canvas
d, f, h, j: copy, paste, merge into, merge onto for the
canvas layer, instead of the card background.
other functions
t: toggle layercake visibility between “solid”,
“transparent”, “invert” and “none”.
g: erase the layercake image contents.
b: save the layercake image contents as a custom brush. it
will ask for a name.
y: remove all layercake instances from the card. IMPORTANT
if there is more than one on a card, it will remove ALL of
them.
on patternshop to support layercake
r: make a ps_layercake at 100,100
I added a quick and dirty hack inspired by Missooni’s Patternshop Cinnanom, on my Patternshop page. It’s completely a single contraption now (Patternshop Neo) without any modules. Download the patternshop_neo.html to try it out or get it from my codeberg page.
This can be much more easily used standalone now as a contraption.
It has several properties for programming:
And most importantly:
Please see
https://woodring.itch.io/patternshop-neo
or
https://missooni.itch.io/patternshop-cinnamon
for a simpler, more Decker-like, drawing with layers
See https://missooni.itch.io/patternshop-cinnamon for a more Decker-like drawing experience with layers (rather than my complicated mess)
Not to jump on the bandwagon to bash - but, to give another supporting perspective on this.
Even if it was an LLM, mistake or not, we shouldn’t support that either.
Silicon Valley has perpetrated en masse plagiarism and are telling us that it’s okay. And, they’re in the process of reselling our culture back to us. That’s… a problem.
Personally, I’ve been directly on the receiving end of plagiarism - where someone has stolen my published work to get an advanced university degree. My institution would do nothing about it and threatened to fire me if I kept pressing. It feels bad man - to have your work claimed by others and be normalized.
I generally give my stuff away - because I don’t care if someone copies it or learns from it. But, most people want to be recognized for their work, even if that doesn’t result in compensation. I feel good when someone uses something that I made. I’ll keep my copyright on things that I’ve put enough work into that I want to say, “I made that, isn’t it cool?”
I think that’s why so many people here in the Decker community are anti-AI/ML or are standing up for anti-plagiarism: it’s because we want to share - we want to see someone enjoy something we’ve made. And so, it feels awful when someone else claims your work as their own; LLMs or otherwise, no matter the circumstances.
Plagiarism should never be okay, no matter what form it comes in. Even if it’s a mistake, it’s wrong and we can say it’s wrong.
It may not have everything you want, but this does have layers with toggable visibility for freehand pencil/paint:
https://woodring.itch.io/patternshop
It used to have different compositing operators, other than underpaint. But, I figured the 99% use case was underpaint compositing, so I dropped the rest.
For your first one, as a stop gap, my editor card can be modified to read & write the deck script:
https://codeberg.org/woodring/decker.whiteboard/src/branch/main/whiteboard.deck
search and replace:
deck.modules[w.module_name.text].script
to
deck.script
lines 71, 84, 96, 108
After editing the whiteboard deck, load it and cut and paste the editor card into your deck. Then, I’d have an offscreen button with a hotkey that takes you to the card that has the editor or last card. (though, you can’t have control+key hotkeys for buttons)
or alternatively
Modify the card to add four new buttons and copy the scripts editor.1 through 4. Attach them to the 4 new buttons that read and write the deck.script, instead, which retains the module editor functionality, too.
Totally stop gap, but the editor card is how I do live module edits. I flip between the cards with arrow keys and move it where I need it. That’s just my workflow, though.
Updates:
20260521
20260515
20260514
20260513
To be added:
Just a random thing:
Why is Manipulate and Sleuth the same skill?
Do you know the old TV show Columbo? He’s a master homicide detective because he can: (1) get into people’s heads, (2) understand them to manipulate them, but (3) also figure how they commited the murder because he’s inside their head. Empathy & Read Intent are in-the-moment gut read of someone, where Manipulate & Sleuth are calculated. Columbo is good at both in YARNS. And, Rapport, too.
Cool. If you have any suggestions on how to improve it, I’m all ears.
For instance, I had it set up to work with my tablet, but I set the keys to match what I defined. There’s probably a more logical setting for Photoshop/Krita, but I don’t use them enough to know what they are off the top of my head.
Updated 20260820
Updated 20260708
Updated 20260701
Updated 20260630
Updated 20260627
Updated 20260429
Updated 20260430
Updated 20260501
https://woodring.itch.io/patternshop tutorial and playable deck
https://itch.io/post/15815697 - I started it two days before that post, so it’s been 21 days.
To be fair to everyone else, I’d give myself 8 more days and submit it by the 29th without any more edits after that.
Would that be OK? This jam came at a convenient time and will force me to finish the tutorial by the 29th.
OK - it’s been redone. Hopefully this is the last one for a while.
no more layer buttons; order is implicit on card order
no more operation buttons; probably the only thing that is wanted is underpaint. over is achieved by reordering the cards.
everything has a hotkey and the buttons show what they are. they can be hidden.
UNDO! (infinite right now, but I should limit it - otherwise, you can run out of memory)
support for the palette card: a goto/return button, an eyedropper, copy brush and pattern from palette
probably a few other things
As an aside - I only just now noticed there was a WigglyPaint retrospective.
Related to that - in my attempts for making this easier to use:
make compositing order implicit based on the card order, since there’s already the ability to reorder cards
make the background automatically the composite target
probably all that is wanted is underpaint/masking, so I’ll probably remove operator selection. or make it less up front and center
I like the palette/swatch selection. so make it go directly to a “physical palette” where you can select the pattern visually with a select tool. apply your patterns in swatches like paint. kind of like how layercake can be used now to lift brushes off the palette.
this will remove all of the buttons and probably all that’s needed is a few hot keys to operate the layercake tool.