Code Review: Fidenza by Tyler Hobbs

12/15/2021
Fidenza - Tyler Hobbs (2021)

This series explores the code that powers generative art in the metaverse. The goal is to break down different approaches to making art with code and understand how different artists are adding unique style to their work. While all pieces that are analyzed in this series have code that lives permanently in the blockchain, it is not meant to encourage the unauthorized use of copyrighted code in copyminted work. You should know that anyone caught stealing artwork is subject to litigation based upon the licenses attached to the artwork.

Inspecting the file

Fidenza is compiled and minified to Javascript, so many of the method names and variables are obfuscated. This is best practice for any time you ship Javascript, and that extends to generative art that lives online. Consider using a tool like Webpack to package your work before you upload to your favorite NFT marketplace. Although it ain’t pretty, we can still dig in to the code and observe specific strategies and patterns. Beautified, the program is roughly 680 LOC in JS. It’s entirely possible that it’s much bigger or smaller in it’s native format of Quill.

let tokenData = {
    "tokenId": "78000001",
    "hash": ""
}

...

const rnd=() => {
    const a=state[0],
    b=state[1],
    c=state[2],
    e=state[3],
    f=0|a0+m0*a,
    g=0|a1+m0*b+(m1*a+(f>>>16)),
    h=0|a2+m0*c+m1*b+(m2*a+(g>>>16));state[0]=f,
    state[1]=g,
    state[2]=h,
    state[3]=a3+m0*e+(m1*c+m2*b)+(m3*a+(h>>>16));const i=(e<<21)+((e>>2^c)<<5)+((c>>2^b)>>11);return eps*((i>>>(e>>11)|i<<(31&-(e>>11)))>>>0)},
    hash32=(a,
    b=0)=>{const c=16,
    e=65535,
    f=255;for(var g,
    j=1540483477,
    m=a.length,
    n=b^m,
    o=0;4<=m;)g=a[o]&f|(a[++o]&f)<<8|(a[++o]&f)<<16|(a[++o]&f)<<24,
    g=(g&e)*j+(((g>>>c)*j&e)<<c),
    g^=g>>>24,
    g=(g&e)*j+(((g>>>c)*j&e)<<c),
    n=(n&e)*j+(((n>>>c)*j&e)<<c)^g,
    m-=4,
    ++o;switch(m){case 3:n^=(a[o+2]&f)<<c;case 2:n^=(a[o+1]&f)<<8;case 1:n^=a[o]&f,
    n=(n&e)*j+(((n>>>c)*j&e)<<c);}return n^=n>>>13,
    n=(n&e)*j+(((n>>>16)*j&e)<<16),
    n^=n>>>15,
    n>>>0}

Starting off we see a key part of generative art on the blockchain being implemented. Although this program utilizes random() to great extent, it should always produce the same result for the token holder. How do you do that? You use a pseudo random number generator with a seed value that is tied to the hash. This way you “lock in” the results of random methods to a reproducible series.

//RUN 1
randomSeed(1);
random(); //returns .1;
random(); //returns .9;
random(); //returns .4;
random(); //returns .8;

//RUN 2
randomSeed(1);
random(); //returns .1;
random(); //returns .9;
random(); //returns .4;
random(); //returns .8;
// **Pseudocode, not from Fidenza. **

This is appears to be based off the fascinating work on Linear Congruential Generators.

Now the program will need to always use the rnd method established above. Using Math.random() or random() will muck the whole thing up since it’s not repeatable when the program reruns. It’s important that generative artwork on the blockchain generates the same image on different machines at different times… Unless you explicitly want to break that rule.

If you would like to implement a similar method to rnd, I would recommend actually using P5JS’s randomSeed method and sticking with the libraries random() method.

Some would even argue that they less you use random() the better your results will be. It’s the vanilla flavour of randomization. Fidenza appears to use Gaussian distribution among other strategies to break up the uniformity.

Jumping down a bit, we see how Fidenza becomes responsive…

const dw = 2000;

function setup() {
windowHeight >= 1.2 * windowWidth ? (ww = windowWidth, wh = 1.2 * windowWidth) : (wh = windowHeight, ww = windowHeight / 1.2), 
wr = ww / dw,
...
}

function vrtx(a, b) {
    vertex(a * wr, b * wr)
}

function swght(a) {
    strokeWeight(a * wr)
}

There’s special consideration here to ensure that no matter how big or small the screen, Fidenza renders beautifully. Above we see that the stroke weight is subtly effected by the larger screen dimensions size (wr) divided by 2000 (dw).

"low" === i ? r = w(.02) : "highAF" == i && (r = w(.007));

The draw cycle

P5JS operates by continuously calling the draw() method. This “refreshes” the canvas and allows for code within the function to be rerun and update the canvas.

function draw() {
    noLoop(); 
    background(40, 10, 90);
    ...
}

One thing that immediately stands out is noLoop(), a method that stops P5JS from ever calling draw() more than 1 time. From this we can assume that Fidenza runs fairly quickly and in one pass. This isn’t always the case for generative art though, some algorithms (like Chaos Blocks) take many seconds to fully render. One could do it in a single draw(), but the browser may become unresponsive. Fidenza does not appear to exhibit this issue. After doing some basic performance metrics, the entire Javascript execution takes around 1.2 seconds.

function wc(a) {
    const b = rnd();
    let c = 0;
    for (let e = 0; e < a.length - 1; e += 2) {
        const f = a[e],
            g = a[e + 1];
        if (c += g, b < c) return f
    }
    return a[a.length - 2]
}

I started to notice the function wc() appear a large number of times once I got to this point of the program. Upon inspection, it appears to allow you to get a value from an array with weighted outcomes. This allows attributes to become more rare than others. In the sample below, a and d will appear less than b and c.

wc(['a', .15, 'b', .4,'c', .3, 'd', .15]);
//a === the possible result, .15 === the probability

Usually I just write a method to get a random value from an array and it is equally weighed. While this works, I appreciate the extra care given in Fidenza.

We can now see how much layered randomization is inherent in the code.

let a = wc([V1, .03, V2, .01, V3, .04, V5, .18, V6, .5, V7, .04, V4, .2]);
let b = wc(["none", .15, "low", .2, "med", .45, "high", .2]);
V7 === a && (b = wc(["none", .4, "low", .3, "med", .3]));

This is also where we see the assignment of colors start to take place.

const c = wc([pi(.5), .1, pi(0), .1, pi(.25), .2, pi(.75), .2, pi(.05), .1, pi(.95), .1, pi(.45), .1, pi(.55), .1]),
      d = wc(["luxe", .55, "gS", .1, "rad", .09, "bkd", .05, "pltq", .05, "wM", .04, "AM", .03, "rose", .02, "blk", .02, "cool", .01, "wOC", .01, "pG", .01, "dL", .01, "lD", .01]);

The colors that comprise the Fidenza system:

wht = [40, 2, 98],
dRed = [358, 64, 86],
red = [358, 80, 82],
tan = [25, 40, 88],
midTan = [25, 40, 60],
orng = [25, 78, 90],
pOrng = [25, 68, 93],
pYllw = [43, 60, 99],
yllw = [43, 90, 99],
pnk = [11, 35, 97],
pPnk = [12, 18, 97],
xGrn = [125, 55, 55],
grn = [170, 75, 65],
pGrn = [170, 35, 80],
ppGrn = [160, 15, 85],
pppGrn = [160, 10, 90],
ppYllwGrn = [125, 12, 90],
ppBlue = [200, 15, 90],
pBlue = [200, 35, 75],
blue = [210, 65, 55],
dBlue = [220, 65, 35],
ddBlue = [225, 65, 20],
bgrndDBlue = [225, 60, 25],
paleIndigo = [220, 35, 75],
lavender = [260, 14, 88],
pBrwn = [28, 42, 39],
brwn = [25, 45, 33],
dBrwn = [25, 45, 23],
ddBrwn = [25, 45, 13],
nwsprnt = [40, 12, 88],
bgrndNws = [40, 8, 92],
blk = [0, 0, 10]

And some names for the color schemes that combine them.

pcLx, pcLxD1, pcLxD2, makeLxD, pcRad, pbcRad, pcBaked, pbcBaked, pcCool, pcBlack, pbcBlack, pcPolitique, pcRetro, pbcRetro, pcWhtMono, pbcWhtMono, pcAM, pbcAM, pcDarkLifestyle, pbcDarkLifestyle, pcPartyGirl, pbcPartyGirl, pcWhtOnCrm, pbcWhtOnCrm, pcGolfSocks, pbcGolfSocks, pcRose, pbcRose,

Beyond this we get in to the unique mechanics that generate the graphical elements. For this writeup I did not try and run the program in order to peel back the layers of logic that drive the unique look of Fidenza, but I can make some educated guesses on how it works under the hood.

The classic lines in Fidenza in? Those are cSegs.

function cSegs(a, b, c, e, f, g, i, j, l, m, n) {
	...
}

cSegs appear to be the lines that comprise Fidenza. They are highly variable and use a flow field to get their general direction. They have a possibility of different style beginning and end points. There is collision detection to determine whether they overlap or not.

Below we see the implementation of collision detection between cSegs. This sort of algorithm can be difficult to perfect if you want geometry to become layered. In Fidenza cSegs appear to stop if they touch, unless you’re a lucky collector with an interaction that has that turned off.

function cllsn(a, b, c, e, f, g) {
    if (g && dist(a, b, w(.5), h(.4)) <= 1.3 * c) return !0;
    const j = sctrs(a, b, c);
    for (let h = 0; h < j.length; h++) {
        let [g, i] = j[h];
        const k = e[g][i];
        for (const g of k) {
            const [e, h, i, j] = g;
            if (dist(a, b, e, h) <= c + i && f !== j) return !0
        }
    }
    return !1
}

After the lines have found their home, they become a Javascript object with this shape.

p.push({
      points: n,
      margin: b,
      id: r
  })

In closing

When we view generative art, we’re studying the unique qualities of the visuals that the underlying code has created. This code is not the focus, and nor should it be. It may be too short, too long, unoptimized, over-optimized, or in an unpopular language. These arbitrary critiques are meaningless, they don’t alter the beauty of the work in any way. The code is a means to an end, a way to introduce some unique attributes of programming to creative processes. Sure, you could hand draw 10,000 circles on a canvas, but I can do that with a for loop. Using these techniques allows us as artists to choose abstractions that in turn achieve our vision without compromising the individuality of our dreams. Notes