[Whitepaper] Awesome.AI: A Dynamics-Based Framework for Thought Simulation

Joakim JacobsenJoakim Jacobsen
17 min read

Version: Prototype

Document Type: Technical White Paper (Draft)

Author: Joakim Jacobsen

Repository: https://github.com/copenhagen-ai

Website: https://www.copenhagen-ai.com

Note: This system is experimental and subjective by design.

1. Overview

1.1 Problem Statement

AI systems today utilises many different ML algorithms and frameworks. These, including state-of-the-art LLMs, are good at pattern recognition, they process input → output in mostly static fashion, but lack internal dynamics that simulate continuous thought. This framework produces that dynamics.

1.2 Project Goal

The Awesome.AI framework proposes a new computational way to simulate the dynamics of the mind, using concepts borrowed from classical and modern physics. It aims to:

  • Create a simulated thought as smooth and continuous as in a biological system

  • Simulate will, emotion and motivation using dynamics

  • Present a new approach and paradigm in AI (and Psychology)

1.3 What It Is

  • At its core, it is a decision engine, a highly autonomous agent. Does the thought go up or does it go down and from this more advanced systems emerge.

  • There are two ways to view this system:

    • as an agent built on the concept of competing micro-agents, called UNITs. Each UNIT represents a candidate thought that competes for becoming an actual thought.

    • as an infinite statemachine, UNITs being states and nonlinear or nondeterministic mechanics for navigating these states.

  • Other AI frameworks try to map every neuron in the brain, instead this framework maps entire networks(HUBs and UNITs) in the brain and uses dynamics to move between these networks.

2. Core Concepts

One can think of HUBs as the context/problems and UNITs as answers/solutions.

2.1 UNIT (Thought Node)

A UNIT is a single representation of a thought. It moves along the x-axis (UNIT-space).

interface Unit {
  index: number;   // Positional value between 0.0 and 100.0, along the x-axis (aka UNIT-space)
  data: string;    // Static or generated by ChatGpt according to index and HUB subject
  credit: number;  // Score between 0.0 and 10.0
  ticket: string;  // Used for matching UNIT with external object
}

A UNIT can in itself, be viewed as a micro-agent - Competing to become current UNIT and eventually an actual thought, by continuosly adjusting index.

In order to continuously better itself, the system dynamically adds and removes UNITs.

This means UNITs have width (ALPHA) around a point in UNIT-space. This area can be arbitrarily narrow, and therefore still adds up to an infinite number of UNITs.

NOTE: Alternatively UNITs can be viewed as states in an infinite statemachine governed by a nonlinear or nondeterministic overall framework.

2.2 HUB (Context Group)

A HUB is a collection of related UNITs, bound by a shared context.

interface Hub {
  id: string;
  subject: string; // (e.g., “work”, “having fun”, “friends”)
  units: Unit[]; // can be empty
  max_num_units: number;
}

3. Mechanics

The mechanics are metaphores for the dynamics of the mind. These are tested to be working, others may exist.

3.1 Mech Noise (Low Layer)

Pseudocode, see Appendix A

  • Uses: Newtons 2. law, F = ma, m <- car mass and a <- deduced from current UNIT index

  • This was the base mech during the formation of the project.

  • Two cars connected by a rope or chain, they are going opposite directions and car one is dragging with a constant force, while car two is dragging with a variable force.

  • This produces the noise (momentum), centered around ~0.0, which is used in later mechs.

  • This simulates the Heart/Soul/Will of the system.

Friction

  • In order to calculate the friction of mech noise, the system uses the dynamic credits of UNIT, to find the friction coeficient.

  • Now the actual force for the mechanics can be calculated.

3.2 Mech One (High Layer)

Pseudocode, see Appendix B

  • Like Mech Noise, 2 cars connected by a rope or chain. Instead of oscilating around 0.0, the system here uses a Sine(time) + Noise from Mech One, to calculate the dynamics.

  • This is used for making and sending promtps to ChatGPT or other tasks.

3.3 Mech Two (High Layer)

Pseudocode, see Appendix C

  • This setup simulates a ball balancing on top of a hill. The ball can go down the sides. The hill can be more or less steep. By pushing the ball up the hill, the game is to keep it from falling down the sides.

  • Like Mech One the system uses a Sine(time) + Noise to produce the dynamics, for sending promtps to ChatGPT or other tasks.

Terrain Expansion (Planned Feature)

  • In later versions of the system, one can imagine entire landscapes of hills and valleys.

3.4 Mech Three (Source Only)

  • Here the setup simulates a rocket trying to "leave orbit" of a black hole. The system calculates momentum and opposing forces.

  • Its only in source, because of the complexity of the numbers being much greater in a setup like this.

Common Pattern in All Mechanics:

  • The similarities for these mechanics, are that there is a static force dragging one direction and a variable force dragging or pushing the other way. ie. the car with a constant pressure on the the pedal (Mech One) and gravity (Mech Two, Mech Three).

From these mechanics the system gets a momentum, and if the momentum is increasing, the thought goes up and decreasing the thought goes down.

4. Dataflow, Layers and Algorithm

4.1 Dataflow

The system operates in a feedforward architecture:


Mech Noise (Low Layer)
   ↓
Current UNIT
   ↓
→ Mech One → Thoughtpattern/Mood Index → Prompt A  
→ Mech Two → Thoughtpattern/Mood Index → Prompt B  
→ Mech Three → Thoughtpattern/Mood Index → Prompt C
  • The dataflow between the two running mechanics is FeedForward. Meaning that the lower layer (mech noise) is feeding its output (current unit) into the higher layer (mech one or two). Higher layers does not produce a current unit. This makes it a feed forward dataflow between the two layers.

4.2 Layers and Thoughtpatterns

  • Higher layers produces thoughtpatterns. These are just versions of a given mech. Like is the Sine going from -1 to 1 (GENERAL), 0 to 1 (GOOD) or -1 to 0 (BAD). This is used to produce the mood of the system.

4.3 Overall Algorithm

It is build on the notion: many (eg. 500) impressions, produces one thought.

// this pseodocode is conceptual

Initialize system state
For i = 1 to N:
    Apply Mechanics (noise) + friction
    Apply filters (credit, direction, lowcut)
    Find current UNIT
Return most frequently selected UNIT
Repeat From Top
  • After N iterations, it finds the statistically dominant UNIT. Which is then considered "the actual thought"

5. Filters, Selection and Internal State

Pseudocode, see Appendix D

Pseudocode, see Appendix E

5.1 Filters

the three main filters are:

Direction

  • Removes UNITs not aligned with the current direction.

Credit

  • Prevents overuse of any single UNIT.

  • Credit is continuesly updated.

  • Credit must be > 1.0 for a UNIT to be valid in UNIT-space.

  • Current UNITs credit reduces (fast) and refills when not "current UNIT" (slow).

LowCut

  • Removes the heaviest UNITs from selection (UNIT-space).

  • This can be used to hide certain UNITs.

  • Hidden UNITs are not subconcious thoughts, but lowcutted possible thoughts. Meaning, currently (long term) not available to the system.

  • The idea of this dynamics was such a thought/UNIT.

5.2: Selection Of Current Unit

After applying filters, the current UNIT is selected by:

  • determining a point (near) in opposite end of UNIT-space, that current UNIT should jump to, in order to balance the mechanics

  • determining "near": near = 100.0 - Normalize(momentum, 100)

  • selecting, from valid UNIT-space, the UNIT closest to "near"

5.3: Handling Internal State

Initially UNITs are scattered randomly across UNIT-space.
While selecting current UNIT, the state of UNIT-space is also updated.

Add UNIT

  • if criterias are met, the system adds a new UNIT in an arbitrarily narrow area (ALPHA) around "near"

Criterias are:

  • number of UNITs in HUB is less than HUB.max_num_units

  • distance from near to (newly) selected UNIT is more than avarage distance (100.0 / HUB.units count)

Remove UNIT:

  • the system removes all UNITs in an arbitrarily narrow area (ALPHA) around "near"

Adjust Index:

  • updates current UNITs index by: MyRandomDouble * ETA * direction

6. The Hack, Down and the Quantum Connection

Pseudocode, see Appendix F

6.1 Background

  • Down is an enum (YES/NO) representation of direction.

  • The meaning of Down, is that the system say/decides No or Yes to going down.

6.2 Changing Direction

Since the beginning of the project, the system used a "hack", where the flip of direction, was done only for best performance. This hack has now been removed and replaced by these options.

These only apply to Low Level (Mech Noise). MyQuantumXOR connects the Awesome.AI agent with another simpler agent.

When delta momentum is below 0.0, direction is true.

ModeDescription
Classical (legacy, valid or logic error?)The system flips the value (changes direction). This produces the noise.
ProbabilisticUses momentum to calculate a probability for flipping the value (changing direction).
Quantum (Experimental)Uses a Qubit-like XOR of two agents: MyQuantumXOR(awesome_agent.direction, simple_agent.direction)

6.3 Variants of Down

  • HARD
    A binary value, is delta momentum above or below 0.0:
    Range: YES, NO

  • FUZZY
    A fuzzy value, based on the value and interval of delta momentum:
    Range: ← VERYNO | NO | MAYBE | YES | VERYYES →

  • PERIOD
    Evaluates a series of HARD Downs (e.g., past 100 steps) to determine trend.

7. Prompt Generation / Monologue

The monologue has two modes, the default is deterministic.

7.1 Deterministic Mode

  • This mechanism uses static texts.

  • The texts are chosen based on current mood-index and HUB-subject.

  • If texts are positive or negative, they are combined with "..and..", if different then "..but.." - (XNOR).

  • This produces the flow in the monologue.

7.2 Fluent Mode (via ChatGPT)

  • GPT (or similar) generates two sentences from mood-index and HUB-subject.

  • Plays "Connect the Dots" with GPT (or similar) to combine them into a more natural, emotionally-toned output.

  • Can express nuance and variation via prompt design.

  • This produces the flow in the monologue.

8. Decisions

These are some of the ways decisions are made within the system. The answers are stored in the data field of current UNIT. Both versions has a UNIT, that starts the process - either by HUB subject or by setting system state to QUICKDECISION. Decisions are used, fx. when Awesome.AI starts and answers chat conversations.

8.1 Quick

  • Quick decisions are made within an epoch (~500 cycles or less)

  • Activates system state QUICKDECISION

  • Clears UNIT-space and injects a number of QUICKDECISIONUNITs

  • Removes QUICKDECISION UNITs as they are visited

  • Returns a binary Yes/No decision

8.2 Long

  • Run across multiple epochs, with two possible solution paths:

State 1:

  • Solution 1: depending on current UNIT data, return a Yes/No answer

  • Solution 2: depending on current UNIT data, proceed to next state or decline

State 2:

  • Solution 2: if DOWN = No, return current UNIT data. If DOWN = Yes decline.

9. Occupation and UNIT-space

INFO: This section is optional and non-essential to core framework*.*

INFO: the system produces a MyRandom, which is used for this feature.

What has been described so far is the core of the framework. The core is focused on a fixed UNIT-space, but with occupation (-of the mind), UNIT-space is divided into portions of valid UNITs, thereby letting the system have trails of thought.

Occupation defines a named mental activity with a list of HUBs:

interface Occupation {
  name: string; // what is the systems current occupation
  max_epochs: number; // a number indicating how many epochs (max) is spend on this Occupation
  hubs: Hub[]; // a list of HUBs associated with this Occupation
}

9.1 Internal Occupation

  • uses MyRandomto pick a number below max_epochs

  • uses MyRandomto pick a Occupation

  • for a unit to be valid, it checks if current UNIT->HUB is contained in current Occupation values, this determines if the UNIT show up in UNIT-space

9.2 External Occupation

To be valid in UNIT-space, a simple tag/ticket matching feature has been implemented.

  • External objects are decorated with a tag (e.g., like a HUB subject)

  • UNITs are valid in UNIT-space only if their ticket matches the external tag

10. Limitations

  • No consciousness (only dynamics)

  • No memory (or less than seconds)

  • No feeling; but some simulated basic emotion/thoughtpatterns

  • No free will; but the illusion of free will. ie. the heaviest UNITs are LowCutted, the system therefore cannot "recognize" the pattern. Hence the illusion of free will.

  • Prompt output quality - for monologue - is highly sensitive to HUB naming and grouping

11. Implications and Final Thoughts

The biggest problem is..

  • the idea is quite obvious, but hasn’t been tried implemented before

  • what has been holding this idea back, is it was a lowcutted thought

  • should this idea remain hidden?

  • is the idea generel or specific to my thought?

  • the outcome may be, that we define the physical laws of the world and this simulation

  • this setup only needs validation for the idea to be correct?

12. Mentions

12.1 Mechanics Additions

MechanicExtreme Behavior
Mech One / TwoPosition → 0.0 → Perceived experience → ∞ (emotional singularity*)
Mech One / Two (alternative)Position → 0.0 → Perceived experience → 0.0, y0 or ∞ (emotional singularity**)
Mech ThreePosition → Schwarzschild Radius (Rs) → Time Dilation → 0.0

* pain (my speculation); physical or emotional, could be enlightenment or truth

* could be a transition to a new state

** the dependent of position could be defined by the system itself, could serve as a motivation factor

These limits model extreme internal experiences (transcendence, shutdown, insight, recursion collapse). They form the edge cases of mental simulation within this framework.

12.2 Additional

  • what drives the system, is trying to solve the "error" introduced in THE HACK. Also, maybe this "error" can be made more complex and thereby breaking the limited UP/DOWN motion.

  • the system produces a random number, from momentum.

  • maybe the definition for this system is not "a dynamics of the mind, but rather "a dynamics of the will of the mind".

  • this is my subjective vision of how the dynamics of the mind should be modelled.

  • this is a prototype.. and therefore not the final version.

13. Glossary

TermDescription
UNITIndividual data node representing a thought or decision
HUBPersistent container grouping UNITs by theme or problem space
Mech NoiseCore mechanic producing oscillating dynamics (soul/will)
Delta MomentumChange in system movement that guides directional transitions
LowCutFilter removing most "massive" thoughts temporarily
CreditA decay-based score regulating UNIT reuse
Mood IndexValue derived from sine wave + dynamics; guides emotional tone

14. Appendix

Appendix A

  • Pseudocode: Mech Noise
function Peek(curr): 
    Calc(curr, true, -1) 

    peek_norm ← mind.calc.normalize(peek_momentum, m_out_low_p, m_out_high_p, 0.0, 100.0) 

function Calc(curr, peek, cycles): 
    if cycles = 1: 
        Reset() 

    deltaT ← 0.02 
    m ← 500.0 
    N ← m * CONST.GRAVITY 

    F_static ← ApplyStatic() 
    F_dynamic ← ApplyDynamic(curr) 
    u ← Friction(curr.credits, -2.0) 

    F_friction ← u * N 
    F_net ← -F_static + F_dynamic + (F_friction * -Sign(-F_static + F_dynamic)) 

    delta_vel ← (F_net * deltaT) / m 

    if peek: 
        peek_momentum ← p_prev + (m * 2) * delta_vel 
    else: 
        d_prev ← d_curr 
        d_curr ← (m * 2) * delta_vel 
        p_prev ← p_curr 
        p_curr ← p_curr + d_curr 

    if peek_momentum ≤ m_out_low_p: m_out_low_p ← peek_momentum 
    if peek_momentum > m_out_high_p: m_out_high_p ← peek_momentum 

    if p_curr ≤ m_out_low_n: m_out_low_n ← p_curr 
    if p_curr > m_out_high_n: m_out_high_n ← p_curr 

    if d_curr ≤ d_out_low: d_out_low ← d_curr 
    if d_curr > d_out_high: d_out_high ← d_curr 

function ApplyStatic(): 
    acc ← CONST.MAX / 10 
    m ← 500.0 

    F_applied ← m * acc 
    if F_applied ≤ 0.0: 
        F_applied ← 0.0 

    return F_applied 

function ApplyDynamic(curr): 
    if curr.is_null(): 
        throw "ApplyDynamic" 

    max ← CONST.MAX 
    val ← curr.variable 
    acc ← (max - val) / 10.0 
    m ← 500.0 

    if acc ≤ 0.0: 
        acc ← 0.0 

    F_applied ← m * acc 
    if F_applied ≤ 0.0: 
        F_applied ← 0.0 

    return F_applied 

function Friction(credits, shift): 
    c ← 10.0 - credits 
    x ← 5.0 - c + shift 
    friction ← mind.calc.logistic(x) 
    return friction

Appendix B

  • Pseudocode: Mech One
variable velocity ← 0.0
variable position_x ← 5.0

function Calc(pattern, cycles):
    if cycles = 1:
        Reset()

    Fmax ← 5000.0, omega ← 2 * π * 0.5, eta ← 0.5, m1 ← 300.0, m2 ← 300.0, total_mass ← m1 + m2, dt ← 0.1

    mu ← 0.1
    g ← CONST.GRAVITY
    friction_force ← mu * total_mass * g

    t ← cycles * dt

    F_static ← ApplyStatic(Fmax)
    F_dynamic ← ApplyDynamic(pattern, Fmax, t, omega, eta)
    friction ← friction_force * Sign(velocity)    // Opposes motion
    F_net ← -F_static + F_dynamic - friction

    if |F_net| < friction_force AND |velocity| < 0.01:
        F_net ← 0
        velocity ← 0

    acceleration ← F_net / total_mass
    velocity ← velocity + acceleration * dt
    position_x ← position_x + velocity * dt

    p_prev ← p_curr
    p_curr ← total_mass * velocity
    d_curr ← p_curr - p_prev

    if p_curr ≤ m_out_low_c: m_out_low_c ← p_curr
    if p_curr > m_out_high_c: m_out_high_c ← p_curr
    if d_curr ≤ d_out_low: d_out_low ← d_curr
    if d_curr > d_out_high: d_out_high ← d_curr


function GetRandomNoise():
    curr_unit ← mind.unit_noise
    if curr_unit = null:
        throw "TugOfWar, GetRandomNoise"

    var ← curr_unit.variable
    return Normalize(var, 0.0, 100.0, -1.0, 1.0)


function Sine(pattern, t, omega):
    switch pattern:
        case PATTERN.MOODGENERAL:
            return (sin(omega * t) + 1.0) / 2.0
        case PATTERN.MOODGOOD:
            return 0.6 + ((sin(omega * t) + 1.0) / 2.0 * 0.4)
        case PATTERN.MOODBAD:
            return ((sin(omega * t) + 1.0) / 2.0 * 0.4)
        default:
            throw "TugOfWar, Sine"


function ApplyStatic(Fmax):
    F_applied ← Fmax * CONST.BASE_REDUCTION
    return F_applied


function ApplyDynamic(pattern, Fmax, t, omega, eta):
    if mind.goodbye.is_yes():
        return 0.0

    F_applied ← Fmax * (Sine(pattern, t, omega) + eta * GetRandomNoise())
    return F_applied

Appendix C

  • Pseudocode: Mech Two
variable velocity ← 0.0
variable position_x ← CONST.STARTXY

function Calc(pattern, cycles):
    if cycles = 1:
        Reset()

    a ← 0.1                    // Hill steepness (parabola coefficient)
    g ← CONST.GRAVITY          // Gravity
    F0 ← 5.0                   // Wind force amplitude
    omega ← π                  // Wind frequency
    beta ← 0.02                // Friction coefficient
    dt ← 0.1                   // Time step
    eta ← 0.5                  // Random noise amplitude
    m ← 0.35                   // Ball mass

    t ← cycles * dt

    Fx ← ApplyDynamic(pattern, omega, t, F0, eta)      // Wind force
    F_gravity ← ApplyStatic(m, g, a, position_x)       // Gravity along slope
    F_friction ← -beta * velocity                      // Friction opposes motion

    a_tangent ← (F_gravity + Fx + F_friction) / m      // Net acceleration along slope

    velocity ← velocity + a_tangent * dt
    position_x ← position_x + velocity * dt

    p_prev ← p_curr
    p_curr ← m * velocity
    d_curr ← p_curr - p_prev

    if p_curr ≤ m_out_low_c: m_out_low_c ← p_curr
    if p_curr > m_out_high_c: m_out_high_c ← p_curr

    if d_curr ≤ d_out_low: d_out_low ← d_curr
    if d_curr > d_out_high: d_out_high ← d_curr


function GetRandomNoise(noise_amplitude):
    curr_unit ← mind.unit_noise
    if curr_unit = null:
        throw "ApplyDynamic"

    var ← curr_unit.variable
    rand ← Normalize(var, 0.0, 100.0, -1.0, 1.0)
    return rand * noise_amplitude    // Range [-amplitude, amplitude]


function Sine(pattern, t, omega):
    switch pattern:
        case PATTERN.MOODGENERAL:
            return (sin(omega * t) + 1.0) / 2.0
        case PATTERN.MOODGOOD:
            return 0.5 + ((sin(omega * t) + 1.0) / 2.0 * 0.5)
        case PATTERN.MOODBAD:
            return ((sin(omega * t) + 1.0) / 2.0 * 0.5)
        default:
            throw "BallOnHill, Sine"


function ApplyStatic(m, g, a, x):
    slope ← 2 * a * x
    sin_theta ← slope / sqrt(1 + slope²)
    F_gravity ← -(m * g) * sin_theta
    return F_gravity


function ApplyDynamic(pattern, omega, t, F0, eta):
    if mind.goodbye.is_yes():
        return 0.0

    Fx ← F0 * Sine(pattern, t, omega) + GetRandomNoise(eta)
    if Fx < 0.0:
        Fx ← 0.0
    return Fx

Appendix D

  • Pseudocode: UpdateCredit
function UpdateCredit(): 
    if mind.z_current ≠ "z_noise": 
        return 

    if mind.unit_current.is_quick_decision(): 
        return 

    list ← mind.mem.units_all() 

    for each u in list: 
        if u.is_quick_decision(): 
            continue 

        if u.root = mind.unit_current.root: 
            continue 

        cred ← mind.parms_current.update_cred 
        u.credits ← u.credits + cred 

        if u.credits > CONST.MAX_CREDIT: 
            u.credits ← CONST.MAX_CREDIT 

    mind.unit_current.credits ← mind.unit_current.credits - 1.0 
    if mind.unit_current.credits < CONST.LOW_CREDIT: 
        mind.unit_current.credits ← CONST.LOW_CREDIT

Appendix E

  • Pseudocode: Internal State
function UpdateUnit(current_unit, unit_above, unit_below): 
    if unit_above = null OR unit_below = null: 
        return 

    // Only noise can update unit 
    if mind.z_current ≠ "z_noise": 
        return 

    parm ← mind.parms_current 

    idx_sign ← (parm.high_at_zero ? -1 : 1) 
    add_sign ← (current_unit = unit_below ? 1 : -1) 
    near ← NearPercent() 
    dist ← DistAbsolute(current_unit, near) 

    Update(current_unit, idx_sign, add_sign, near, dist)

function Map(unit): 
    mech ← mind.mech["z_noise"] 

    mech.peek(unit) 

    norm ← mech.peek_norm 
    return norm 

function NearPercent(): 
    norm ← 100.0 - mind.mech_current.p_100 
    return norm 

function DistAbsolute(unit, near): 
    var ← Map(unit) 
    res ← |var - near| 
    return res 

function Update(unit, idx_sign, add_sign, near, dist): 
    if NOT CONST.ACTIVATOR.random_sample(mind): 
        return 

    if Add(unit, near, dist): 
        return 

    Remove(unit, near) 
    Adjust(unit, idx_sign, add_sign, dist) 

function Add(unit, near, dist): 
    count ← unit.HUB.units.count 
    max ← unit.HUB.max_num_units 
    avg ← 100.0 / count 

    if count > max: 
        return false 
    if dist < avg: 
        return false 

    low ← (near - CONST.ALPHA ≤ CONST.MIN ? CONST.MIN : near - CONST.ALPHA) 
    high ← (near + CONST.ALPHA ≥ CONST.MAX ? CONST.MAX : near + CONST.ALPHA) 

    mind.mem.units_add(unit, low, high) 
    return true 

function Remove(unit, near): 
    low ← near - CONST.ALPHA 
    high ← near + CONST.ALPHA 
    mind.mem.units_rem(unit, low, high) 

function Adjust(unit, idx_sign, add_sign, dist): 
    if dist < CONST.ALPHA: 
        return 

    rand ← mind.rand.my_random_double 

    unit.index ← Index + (rand * CONST.ETA * add_sign * idx_sign) 

    if unit.index ≤ CONST.MIN: unit.index ← CONST.MIN 
    if unit.index ≥ CONST.MAX: unit.index ← CONST.MAX

Appendix F

  • Pseudocode: The Hack
agent ← new SimpleAgent()

down1 ← deltaMomentum ≤ 0.0
down2 ← agent.SimulateDown()

switch CONST.Logic:
    case CLASSICAL:
        down1 ← not down1
    case PROBABILITY:
        down1 ← Probability(down1, momentum)
    case QUBIT:
        down1 ← MyQuantumXOR(down1, down2)

return HARDDOWN.YES if down1 else HARDDOWN.NO
0
Subscribe to my newsletter

Read articles from Joakim Jacobsen directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Joakim Jacobsen
Joakim Jacobsen