the technical sheet
DOCS
everything behind the poster, in one place: where the atlas comes from, what the model solves, how a block and a second of trades become a pulse, what the animal reads, what every experiment measured, and how to point an agent at the live state. what is measured and what is drawn is written out in full, with the dates.
01
WHAT APEBRAIN IS
apebrain is the real map of a chimpanzee brain, running live in your browser as a model. the map is chimpbna: 244 regions drawn from the diffusion mri of 46 chimpanzees (wang et al. 2025, casia). every region you see on the screen is one of theirs, with their number, their name and their place on the cortical surface. the wiring between them is the matrix those 46 brains produced, not a picture of one.
the model has two live senses. every block of bnb chain hits one region: the block's own hash picks which one, the number of transactions in it sets how hard, and the gas it burned sets how long. every second of the public binance bnb/usdt trade stream sends one pulse to the insula: the left one when buys outweigh sells, the right one when they do not, with the amplitude set by how many trades that second carried. the price is printed on the page for you to read and is never fed to the model.
and there is the enclosure, where the whole animal acts on the same second the insula received. he has eleven behaviours, written down before the first run and printed here in the order they are read. he cannot trade, he cannot lose, and nothing he does travels back to the market.
the live board is at /pulse, the scrolled piece and the enclosure at /lab, the book of offline results at /experiments. the code is at github.com/0x-Keezy/apebrain.
02
THE ATLAS
chimpbna is a parcellation of the chimpanzee brain built from tractography: for every point on the cortex the authors measured where its fibres go, and drew the borders where that pattern changes. it gives 244 regions, and those 244 are what the poster numbers.
| what | value | where it comes from |
|---|---|---|
| 244 regions | 200 cortical and 44 subcortical | site/data/atlas.json, one entry per region |
| 2 hemispheres | ids 1 to 100 left cortex, 101 to 200 right cortex, 201 to 222 left subcortex, 223 to 244 right subcortex | site/js/atlas.js, the id map |
| 64,984 vertices | 129,960 triangles of cortical surface (chimpyerkes29 midthickness) | mesh.midthickness.pos.f32, mesh.idx.u32 |
| 244 x 244 matrix | structural connectivity from the diffusion mri of 46 chimpanzees | chimp46_connectome_wb.mat, rebuilt into sc.f32 |
| 5,878 links kept | the strongest 20 percent, everything above a weight of 0.000842 | atlas.json threshold, built by data/build.py |
| 600 cables drawn | the 600 strongest links, weight 0.079505 and up, the arcs on the index | edges.bin, site/js/cables.js |
what measured means here
it means the numbers were published by the people who made the atlas and we rebuilt from their files, not from a screenshot of their figures. site/data/atlas.json records the sha256 of all thirteen raw files it was built from, plus the commit of the repository they came from, so any of it can be checked against the source. the surfaces, the parcel labels, the subcortical volumes and the connectome are all theirs. our part is the build script and the binaries it writes.
source: FANLabCASIA/ChimpBNA at commit de5a36b · paper: Wang et al. 2025, The Innovation 6(2):100755, doi:10.1016/j.xinn.2024.100755 · white-matter tracts on the index: Bryant, Li & Mars 2020, CC BY-SA 4.0.
03
THE MODEL
one equation per region. this is a neural mass model: each of the 244 regions is a single oscillator standing in for the average activity of everything inside it, coupled to the others by the measured matrix. the oscillator is a hopf unit, the standard choice for this kind of whole-brain work, and the signal that travels between two regions arrives late by the time light would take at 2 m/s over the distance between their centroids.
beside it, on exactly the same stimuli, runs a scrambled copy: the same regions, the same number of links on each one, rewired at random with a fixed seed. it is the control. if something on the screen is coming from the real wiring and not from the shape of the equation, the copy will not show it.
the parameters, all of them
| parameter | value | what it sets |
|---|---|---|
dt | 0.001 | the integration step, in seconds |
a | -1.5 | the bifurcation parameter: negative, so each region sits below its own oscillation and only rings when something reaches it |
freqHz | 3 | the natural frequency of a region, in hz |
G | 0.8 | global coupling: how much of a neighbour's state reaches a region |
velocity | 2000 | conduction speed in mm/s, that is 2 m/s, which is what sets the delay on every link |
noise | 0.004 | the noise each region gets per step, so the brain is never perfectly still |
pulseGain | 12 | how hard a stimulus of amplitude 1 pushes the region it lands on |
window | 0.5 | the seconds a block is watched after it lands, before it is scored |
respThresh | 0.012 | how far a region has to move from its baseline to count as having answered |
seed | 20260919 | the seed of the random generator, so the same page gives the same run |
these ten numbers live in one line of engine/engine.js (DEFAULTS) and were chosen by a person before any run. site/js/engine.js is generated from that file, so the browser and the service can never drift apart; a test fails if they do.
what the greek letter on the board is
when a block lands on a region, the model watches the next half second and writes down, for every other region, the moment it first moved past the response threshold. that gives 243 latencies. it also knows, from the matrix, how many links away each of those regions is from the one that was hit. rho is the spearman correlation between those two lists: how well the order in which regions answered follows the order of their distance in the wiring.
if the wiring is carrying the signal, close regions answer first and rho is positive. in the scrambled copy there is no reason for it to be, and that is the whole comparison. the two numbers are printed side by side on /pulse, block after block.
the pass rule, written before the run
LIVE UNTIL 100 BLOCKS ARE MEASURED. THEN PASSED IF THE MEDIAN RHO OF THE MEASURED WIRING IS POSITIVE WITH P BELOW 0.01 ON A SIGN TEST AND THE SCRAMBLED COPY STAYS INSIDE PLUS OR MINUS 0.05. OTHERWISE IT PRINTS FAILED.
one function, stampFor in site/js/pulse.js, decides that stamp for the page and for the service, so the board and the api can never disagree.
the stimulated region itself is left out of both the correlation and the count of responders. it receives the full pulse gain and crosses the threshold every single time, so counting it would hand the measured brain a free point on every line. that is why the tally on the board reads out of 243 and not out of 244.
04
THE SENSES
the chain
the public json-rpc of bnb chain, read straight from the browser, with no key and no account. the page asks for the latest block a little faster than blocks arrive, emits each new one exactly once and in order, and if a slow answer made it skip some, it fetches up to the three most recent it missed and spaces them 220 ms apart so a burst reads as a sequence instead of a pile. four public endpoints take turns if one stops answering.
the header of the block is all it takes. the rule below was written before any run, and it is deterministic: the same block always produces the same pulse.
| from the block | rule | range |
|---|---|---|
| region | the first two bytes of the hash, modulo 244, plus one | 1 to 244 |
| amplitude | 0.25 + 0.75 x min(1, transactions / 200) | 0.25 to 1.00 |
| duration | 20 ms + 180 ms x min(1, gasUsed / gasLimit) | 20 to 200 ms |
| palette | the third byte of the hash, modulo 4 | 0 to 3 |
blockToPulse in site/js/atlas.js; the feed itself in site/js/chain.js. when the feed goes quiet the page says so and the brain keeps running: only the stimuli stop.
the insula
the second sense is the public bnb/usdt aggregate trade stream of binance, also with no key and no account, also read straight from the browser. every second the page adds up what went through, and if there was anything at all it sends one pulse to the insula.
| from the second | rule | value |
|---|---|---|
| region | buys outweigh sells, by quantity, in that second | 88 L.INS.v, left insular gyrus, ventral part |
| region | they do not | 188 R.INS.v, the same region on the right |
| amplitude | 0.25 + 0.75 x min(1, trades / 40) | 0.25 to 1.00, so 0.70 is 24 trades |
| duration | fixed | 60 ms |
a trade whose buyer was the maker was taken by a seller, so it counts as a sell. a second with no trades sends no pulse at all: that is the rule, not a fault. and the price, which the page prints and animates, never enters any of this. the pulse carries a side, a count and an amplitude, and that is all it carries.
senseFromSecond in site/js/senses.js. the same function runs in the browser and in the service, and a test walks the whole public state looking for anything that smells like a price.
the order book, and the funding
the third sense is the public market data of binance: the order book of bnb/usdt and the funding rate of its perpetual, both open endpoints, with no key and no account. the service reads them, not the page: it asks for the fifty best levels of the book every five seconds and for the funding rate every sixty, which together spend well under a hundred of the 6,000 request weight a minute binance gives an anonymous caller. the page reads the result out of /state and feels the same two pulses in its own copy of the brain.
the book is read from data-api.binance.vision, the public market data mirror, which answers from every region; api.binance.com is the second host, and the reader rotates to it if the mirror goes quiet. the funding rate lives on the futures host, which has no mirror and does answer some regions and not others. when it does not answer, mood reads the taker flow instead: the share of the last closed one minute candle that the takers bought, off the same mirror, into the same amygdala. the page says which one it is printing, and senses.market carries funding or takers, never a number dressed up as the other.
two more regions, picked by name from the atlas and not by a number. the order book goes to the orbital gyrus, the parcel that sits over the olfactory tract, where smell and value arrive together in a primate: call it what the book smells like. the funding rate goes to the amygdala, the fear and the greed of the animal.
| from the market | rule | value |
|---|---|---|
| imbalance | (bids - asks) / (bids + asks) over the best 20 levels of each side, in bnb | -1.00 to +1.00 |
| region | the buyers lean (imbalance above 0) | 20 L.OrG.m, orbital gyrus, medial part |
| region | the sellers lean | 120 R.OrG.m, the same parcel on the right |
| amplitude | 0.25 + 0.75 x min(1, |imbalance| / 0.3) | 0.25 to 1.00, every 5 s, 400 ms |
| region | the funding rate is above 0, so the longs pay | 201 L.Amyg.rl, amygdala, rostrolateral part |
| region | the shorts pay | 223 R.Amyg.rl, the same parcel on the right |
| amplitude | 0.25 + 0.75 x min(1, |rate| / 0.0005) | 0.25 to 1.00, every 60 s, 400 ms |
| taker flow | when the funding host does not answer: takerBuyBaseAssetVolume / volume of the last closed one minute candle | 0.00 to 1.00, 0.50 is even |
| region | the ratio is above 0.5, so the takers were buying | 201 L.Amyg.rl, the same parcel the funding uses |
| region | the takers were selling | 223 R.Amyg.rl, the same on the right |
| amplitude | 0.25 + 0.75 x min(1, |ratio - 0.5| / 0.15) | 0.25 to 1.00, every 60 s, 400 ms |
the book arrives with a price on every level of it and the rule reads the quantity column only, so here too the price is shown and never fed. a candle arrives with four prices in it and the rule reads the two volume columns. the funding rate is a rate, and it is the one number of the three feeds that says what the market is paying to stay where it is; the taker share says the same thing from the other side, which is why it stands in for it and goes to the same region.
bookImbalance, takerRatio, senseFromBook, senseFromFunding and senseFromTakers in server/market.mjs, with the hosts, the rotation and the backoff written at the top of the file. what the service saw last prints on the placard of the front page and under senses.market in the json below.
the keeper's log
once an hour, on the hour, the service writes one page of prose about what happened: the blocks and the region that took the most of them, the seconds the insula fired and who won them, where the book leaned on average and at its furthest, the funding rate, the score, and what the animal did. the last part runs the eleven enclosure rules of section 05 second by second over the same insula pulse the engine got, so the sentence counts real behaviour instead of describing it. it is at /log, and at GET /log on the service.
server/keeper.mjs, a ring of seven days. the service keeps it in memory and on its own disk; that disk is wiped on every deploy, which is why the first entry after one says since restart. there is no price in an entry and nothing from anybody's account: the same test that walks /state walks the log.
05
THE ENCLOSURE
the same second that lit the insula also picks one behaviour for the whole animal. the list is read from the top and the first rule that applies wins; a rule higher up can interrupt him, and the same rule or a lower one waits for his clip to finish. amp below is the insula pulse of that second, so 0.70 means 24 trades.
| # | the second | what he does |
|---|---|---|
| 1 | no trades for 12 seconds | sits down and dozes off |
| 2 | the first trade after dozing | wakes up and looks up |
| 3 | buys win and the second is full (40+ trades) | jumps |
| 4 | buys win, amp 0.70 or more (24+ trades) | pounds his chest |
| 5 | buys win five seconds in a row | throws both hands up |
| 6 | sells win, amp 0.70 or more | stomps the ground |
| 7 | sells win five seconds in a row | crouches |
| 8 | a single trade in the whole second | shrugs |
| 9 | buys win three or four seconds in a row | rubs his hands |
| 10 | sells win, amp under 0.70 | scratches his head |
| 11 | anything else | stands and breathes |
rule eleven cycles through five idles, one per loop of the clip, so he never repeats the same one twice in a row: stands and breathes, breathes and looks around, looks around puzzled, shifts his weight, stands and waits. a single clip is cut off after 7 seconds so one long animation cannot cover the market. the list is in site/js/enclosure-rule.js, it was written on 2026-09-20 before the first run, and the tests drive it second by second.
what you can give him, and what lights up
the five buttons under the animal are the other door into the same plate. each one is a sense, and each sense lights the region of the real atlas a chimpanzee would use for it, taken from the atlas labels themselves.
| you | he | sense | region lit on the atlas |
|---|---|---|---|
| give him a banana | picks it up and eats it | taste | 84 L.INS.vr, insular gyrus, ventrorostral part |
| call him | waves back | hearing | 37 L.STG.ci, superior temporal gyrus, caudointermediate part |
| poke him | startles | touch | 81 L.PoG.v, postcentral gyrus, ventral part |
| show him the atlas | bends over and inspects it | sight | 94 L.MVOcC.cd, medioventral occipital cortex, caudodorsal part |
| give him a bar | grabs the bar and holds on | grip | 30 L.PrG.i, precentral gyrus, intermediate part |
all five are left hemisphere, like the buys, and none of them is the ventral insula itself (84 is its neighbour, not it), so what a reader gives him enters the same plate as the market through a different door and never pretends to be a trade. arriving on the page makes him wave and lights nothing: arriving is not a sense.
the body: drawn, rigged, honest about it
the animal is an illustration, and the page says so under him: a model, not a scan. the reference image was generated from the low-poly views of the head; the mesh came out of tripo image-to-3d at 36,258 triangles; the skeleton and the movement came from a stock animation library, 22 clips, fused into a single file with gltf-transform. nothing about his shape or his motion was measured from a chimpanzee by us.
what is measured is the atlas he is standing next to, and the second he is reacting to. the animal is the storyteller.
the whole build is written down, step by step and with its costs, in brand/body/README.md, so it can be redone without guessing.
06
THE EXPERIMENTS
each experiment had its rule written down before it ran, and whatever came out was printed. every number on /experiments is copied out of a results file with a date in its name, never from a live run, and the files ship with the site so they can be read directly.
e2 · resting state
PASS RULE WRITTEN BEFORE THE RUN: G CHOSEN ON HALF A OF EDGES; R REPORTED ON HALF B; PASS IF R TEST BEATS THE 95TH PERCENTILE OF 1000 DEGREE PRESERVING REWIRINGS.
the question: does the wiring on its own predict which regions move together? the model runs with no stimulus at all, and the correlation of every pair of regions is compared against the measured matrix, and against a scrambled copy of it, on a half of the pairs that was never used to pick the coupling.
| what | value |
|---|---|
| run | 120 s simulated, first 20 s discarded, sampled at 100 hz, coupling 1.6 picked on half A over a grid of eight values |
| r on half B, the measured wiring | +0.038 |
| r on half B, the scrambled copy | -0.001 |
| 95th percentile of 1000 rewirings | 0.019, mean 0.006 |
| p | 0 of 1000 rewirings reached it |
| verdict | PASSED |
| files | site/data/results/2026-09-19-e2.json, with the two matrices beside it as .f32 |
the stand-in for the bold signal is declared in the file itself: the model's own variable, low-passed at 0.5 hz with a second order butterworth. it is a filter, not a haemodynamic model, and the file says so in the field named bold_proxy.
live and certified, side by side
on /experiments that same model also runs at rest in your browser while you read, and the plate develops out of noise in front of you. two things are printed at once, and they are different on purpose.
- THE PLATE
- develops from the raw co-fluctuation at 20 hz, because a plate is something you look at and the raw signal has structure you can see: the diagonal band, the clusters, the subcortical block. the scrambled half beside it stays flat.
- THE NUMBER
- comes from the same signal put through the same 0.5 hz low pass the archived run used, because a number is something you compare. after about 45 seconds it reads near +0.039, against the certified +0.038 of the file.
they are printed together, each labelled for what it is, so the picture that is easy to read and the figure that is fair to compare never get mistaken for one another. raw and filtered are two views of one run, not two runs.
e3 · lesion
PASS RULE WRITTEN BEFORE THE RUN: HUBS, THE REGIONS WITH THE HIGHEST STRENGTH, PRODUCE THE LARGEST CHANGE IN FUNCTIONAL CONNECTIVITY; PASS IF SPEARMAN OF STRENGTH AGAINST THAT CHANGE IS ABOVE 0.3.
the question: take one region out of the network and what does the rest lose? it was run 244 times, once per region, 60 seconds each.
| what | value |
|---|---|
| run | 244 lesions, 60 s each, coupling 0.8, 472 s of compute |
| global efficiency, intact | 0.5665 |
| strength against change in connectivity | +0.639, the prediction |
| degree against change in efficiency | +0.978 |
| verdict | PASSED |
| file | site/data/results/2026-09-19-e3.json, with the 244 lesions listed one by one |
the rest of the book
01 block pulse is the live one, running at /pulse right now · 04 divergence is shown, not simulated: it is the atlas authors' own chimp to human map, painted on the surface, because the parcels are not homologous one to one and a subtraction would be an invention · 05 learning lives here only: a model of regions has no synapses to learn with, so the poster does not list it · 06 body is stamped drawn, which is what section 05 above explains.
07
THE API AND THE MCP TOOL
the same brain also runs outside the browser: one node process, the same engine file, the same block rule and the same insula rule, holding one continuous run with its own rho. what it publishes is a json, and that json is also offered to agents as a model context protocol tool called apebrain.state.
service url: deploying
the three routes
| route | what it answers |
|---|---|
GET /state | the public state below. no cache, cors open to everyone |
GET /log | { since, entries: [{ at, text, partial, facts }] }. one written entry an hour, the hour in progress first |
GET /health | { ok, uptime_s, block, rpc, market, mood }. ok turns true once the first block has landed, market once either market sense has a reading, and mood names the one that answered |
POST /mcp | model context protocol over streamable http, stateless. one tool, apebrain.state, no arguments, read only |
what /state says
{
"ts": "2026-09-21T02:10:00.000Z",
"uptime_s": 123,
"block": {
"number": 123159694,
"hash": "0xfe76...5d42",
"region": { "id": 239, "hemi": "R", "abbr": "Tha.MDvl", "name": "thalamus, medial dorsal nucleus, ventrolateral part" },
"amp": 0.87,
"dur_ms": 79,
"at": "2026-09-21T02:09:59.000Z"
},
"insula": { "side": "L", "region": 88, "trades": 11, "amp": 0.46, "at": "2026-09-21T02:09:59.500Z" },
"senses": {
"market": {
"source": "binance public market data",
"depth": {
"imbalance": 0.1416, "bids": 80.64, "asks": 60.62,
"at": "2026-09-21T02:09:58.322Z",
"region": { "id": 20, "hemi": "L", "abbr": "OrG.m", "name": "Orbital gyrus, medial part" },
"side": "L"
},
"funding": {
"rate": 0.000109,
"at": "2026-09-21T02:09:51.817Z",
"region": { "id": 201, "hemi": "L", "abbr": "Amyg.rl", "name": "Amygdala, rostrolateral part" },
"side": "L"
},
"takers": null
}
},
"rho": {
"real": 0.10,
"scrambled": -0.03,
"blocks": 19,
"rule": "response order follows graph distance (rho > 0 over 100 blocks) and the scramble does not",
"passed": null
},
"model": { "regions": 244, "atlas": "ChimpBNA", "G": 0.8, "note": "a neural-mass model on the measured 244x244 matrix. the price is shown, never fed. nothing here is a neuron." },
"version": "<git sha>"
}block is null until the first one lands · insula is the trade sense, and it is null when the last second carried no trades, which is the rule and not a fault · senses.market is the third sense of section 04: depth and funding are null until the first reading of each lands, side is the hemisphere the pulse went to, and takers is what mood prints when the funding host does not answer this region, in which case funding stays null and the two are never both filled · rho.passed is null while the run is still LIVE, and then true or false by the same pre-registered rule the board uses · version is the short git sha of the running code. there is no price anywhere in it, and a test walks the whole object to keep it that way.
giving the tool to an agent
the tool is a readout: no arguments, no writes, no keys. any client that speaks the protocol adds it in one line.
claude mcp add apebrain --transport http deploying
binance's own agent os server sits next to it, as a second server in the same agent:
claude mcp add binance-mcp-server --transport http https://agent.binance.com/mcp/agentic
that is the shape of the integration that is real today, and it is worth being exact about why. the public documentation of binance agent os documents that server: how a client such as claude, claude code, codex, chatgpt, cursor or vs code connects to it, with the user's own oauth, so an agent can work on that user's own account. it does not publish a way to register a third party server, so we do not describe one. and we do not consume binance's mcp ourselves: it would need a human's oauth, and the public trade data we use already arrives on the open stream. two servers, side by side in one agent, each answering what it knows.
binance agent os documentation: overview · mcp server · agentic. our side: server/README.md in the repo, with the routes, the deploy and the tests.
one honest detail about running it: each instance starts from noise and gathers its own blocks, so each instance is its own brain with its own rho. there is one, on purpose.
08
WHAT IS MEASURED AND WHAT IS DRAWN
the whole point of the poster is that the line between these three is drawn in public, and that it is drawn in the same place on every page. here is all of it at once.
- MEASURED
- the atlas: 244 regions, their borders, their names, their centroids and the cortical surface they sit on. the 244 by 244 matrix and the 5,878 links kept from it. the blocks of bnb chain, exactly as the public nodes serve them. the trades of the public binance stream, and the order book and funding rate of its public market data. the latencies the model produces, and the rho computed from them. every figure in the results files, with its date.
- MODELED
- the activity itself. 244 oscillators stand in for 244 regions, which is the level the published data supports. the delays are estimated from the distance between centroids at 2 m/s. the stand-in for a bold signal in e2 is declared as a 0.5 hz low pass and named as such in the file. modeled means computed from measured inputs by rules that are written down here.
- DRAWN
- the body of the animal and his 22 clips, which come from a stock library. the eleven enclosure rules, which we wrote, before the run, and which read the market rather than predict it. and the poster itself: the halftone, the two papers, the grain, the type.
what is not in public science yet
a neuron level connectome of a chimpanzee has not been published. nobody has one. so the honest thing to build at the level the data does support is exactly this: regions, with the connections between them that 46 brains produced. that is what the model is, and calling it that is the whole reason the scrambled copy runs beside it in every view.
there is also no public chimpanzee resting state fmri, which is why e2 compares the model against the measured wiring and against 1000 rewirings of it, and not yet against a recorded brain. the results file carries that sentence in its own note field. the day either of those exists, it goes on this page with its date and the comparison gets run.
one line about where the market senses come from, so it is on the record next to everything else: the market senses read the public market data of Binance. Binance Agent OS admits only its own supported agents as clients, so the service reads the public feed and agents on Agent OS read the ape through the MCP tool. that is the direction the integration runs, and section 07 has the two lines that set it up.
everything above is checkable: the atlas hashes, the results files, the engine, the rules and the tests are all in the repository, and the pass rules were written before their runs.
$APEBRAIN
CA WILL BE PRINTED HERE AT LAUNCH.| chain | BNB Chain, chain id 56 |
|---|---|
| launchpad | Flap |
| ticker | $APEBRAIN |
the contract address is read from site/config.json and printed here and on the index the moment it exists. nothing on this page is financial advice, and none of the brain, the model or the enclosure depends on the token.
10
COLOPHON
- TYPE
- League Gothic for display, Special Elite for the typewriter, Courier Prime for the machine. three families, and the tests keep it at three.
- THE TWO SHEETS
- light is cream paper with ink and brass, and the binance yellow printed as a flat area. dark flips the sheet to black, the ink becomes bone, and the yellow carries type. the switch is in the corner of the nav and it remembers what you chose. every text on both sheets is measured against the wcag contrast threshold by
test/contrast_audit.py, on the real dom, element by element. - THE PRESS
- flat screens only: no gradient, no shadow, no blur. the grain is a dot pattern multiplied into the paper, the hot screen sits a hair off register on purpose, and every colour on the page and inside the webgl views is read from the same custom properties.
- DATES
- atlas built and experiments e2 and e3 run on 2026-09-19 · the engine, the block rule and the insula rule written before those runs · the eleven enclosure rules and the body on 2026-09-20 · the public state, the mcp tool and this page on 2026-09-21.
- CODE
- MIT, in the open, at github.com/0x-Keezy/apebrain. built by hand with no framework: plain modules, one worker, three.js for the views.