@codincod/ornament 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/census.ts ADDED
@@ -0,0 +1,262 @@
1
+ /**
2
+ * How much of each animal a sea holds.
3
+ *
4
+ * The other half of `sizes.ts`, and it is here for the same fault. That file
5
+ * was written because every drawing had picked a size that looked right on its
6
+ * own and a boat had come out the size of a turtle. This one is the same story
7
+ * told about how many: every renderer picked a density that read well where it
8
+ * was written, and the water ended up with more crabs on the sand than fish in
9
+ * the column. A crab at eleven per thousand pixels of bed is defensible where
10
+ * it stands. A fish at one per sixty thousand square pixels is defensible where
11
+ * it stands. What lies between them is a sea nobody has ever swum in, and
12
+ * nothing catches it, because the two numbers are never read side by side.
13
+ *
14
+ * So every animal says how many of it a stretch of reef holds, in the unit a
15
+ * survey would report it in, and this turns that into how many to draw.
16
+ *
17
+ * ## Why the numbers are not used as they stand
18
+ *
19
+ * A reef carries upwards of a hundred fish over every hundred square metres of
20
+ * it, and most of them are small ones hanging above the coral. Drawn as
21
+ * counted, that is a screen of fish with a sea somewhere behind them, and
22
+ * everything else in the water is lost among them. Thin the whole table until
23
+ * the fish are countable and the crab, the octopus and the starfish all round
24
+ * to none, which leaves a bare floor under a busy column.
25
+ *
26
+ * The root is the compression that settles it, and it is the same bargain
27
+ * `sizes.ts` strikes over length. Something commoner is still drawn commoner,
28
+ * so the order is never wrong; the ends are pulled in far enough that the rare
29
+ * animal is still in the water somewhere. What is kept is which is commoner,
30
+ * rather than by how much.
31
+ *
32
+ * ## The mix comes out of the same table
33
+ *
34
+ * How common a moon jelly is against a comb jelly is the question of how common
35
+ * a jellyfish is against a fish, asked inside one family. A file that answered
36
+ * it in its own column would answer it a second way by the end of the year, so
37
+ * the shares a family draws its next animal from are these numbers normalised.
38
+ * A family after a count and a family after a mix read one statement.
39
+ *
40
+ * ## A day of its own
41
+ *
42
+ * `plenty.ts` decides how good a year the whole sea is having. This decides
43
+ * which animal is having it. Every kind swings from one day to the next on its
44
+ * own draw, and how far it swings is its own business: blooming is what a
45
+ * jellyfish does and a grouper does not. So `swings` is a column, and a week of
46
+ * water is a comb jelly week and then a moon jelly one.
47
+ *
48
+ * ## What it does not decide
49
+ *
50
+ * Where anything goes, when it turns up, or whether a renderer can afford it.
51
+ * A shark and a manta are in here for their share against each other and for
52
+ * nothing else, because what actually puts one in the water is a rest between
53
+ * crossings (`visitors.ts`); a bed with a thousand plants on it is capped by
54
+ * whoever has to draw them. This is what the sea holds, and the appetite is
55
+ * still `plenty.ts`.
56
+ */
57
+
58
+ import { stir } from "./perlin.ts"
59
+ import { thriving } from "./plenty.ts"
60
+
61
+ export type Kind =
62
+ | "barrel"
63
+ | "box"
64
+ | "comb"
65
+ | "crab"
66
+ | "crown"
67
+ | "cruiser"
68
+ | "darter"
69
+ | "dolphin"
70
+ | "drifter"
71
+ | "eel"
72
+ | "escort"
73
+ | "mane"
74
+ | "manowar"
75
+ | "manta"
76
+ | "mermaid"
77
+ | "moon"
78
+ | "octopus"
79
+ | "puffer"
80
+ | "shark"
81
+ | "squid"
82
+ | "starfish"
83
+ | "stinger"
84
+ | "swordfish"
85
+ | "turtle"
86
+
87
+ interface Held {
88
+ /**
89
+ * How many of it live over a hundred square metres of reef.
90
+ *
91
+ * The unit a transect is counted in, so a number here can be argued with by
92
+ * somebody who has swum one. It is what stands in the water at a given
93
+ * moment rather than what passes through in a season: a shark is scarce here
94
+ * because a shark is scarce at any one moment, and it is scarce in the
95
+ * picture for the same reason.
96
+ */
97
+ readonly holds: number
98
+ /**
99
+ * How hard a day is allowed to swing it, as a power of the day's own draw.
100
+ *
101
+ * At 0 a kind is as common today as it was yesterday, which is what holding a
102
+ * territory looks like from outside. At 1 it rises and falls with the year
103
+ * `plenty.ts` decided. Past that it blooms: the draw is weighted low, so a
104
+ * high power leaves most weeks ordinary or thin and now and then fills the
105
+ * water with one animal, which is the only shape a bloom comes in.
106
+ */
107
+ readonly swings: number
108
+ }
109
+
110
+ /**
111
+ * What a reef holds, animal by animal.
112
+ *
113
+ * Read down the `holds` column rather than across a row: the column is the
114
+ * whole point of the file, and every number in it was chosen against its
115
+ * neighbours rather than against how much of the picture the animal deserves.
116
+ * Where a figure is a range in the wild it is the ordinary end of that range,
117
+ * since `swings` is what carries the rest.
118
+ */
119
+ const SEA: Readonly<Record<Kind, Held>> = {
120
+ /** A dustbin lid of a jelly, in on a season rather than resident. */
121
+ barrel: { holds: 0.09, swings: 1.8 },
122
+ /** Small, scarce, and inshore for part of the year. */
123
+ box: { holds: 0.025, swings: 1.6 },
124
+ /** Comb jellies outnumber everything else adrift in the water. */
125
+ comb: { holds: 0.66, swings: 2 },
126
+ /** Most crabs on a reef are under it. This is the few that are out. */
127
+ crab: { holds: 3, swings: 0.4 },
128
+ /** Deep water, and up here only where cold water is. */
129
+ crown: { holds: 0.03, swings: 1.4 },
130
+ /** Snapper, surgeon, parrot: the fish a reef reads as at a glance. */
131
+ cruiser: { holds: 24, swings: 0.35 },
132
+ /** Damsels and anthias over the coral, which is most of what a reef holds. */
133
+ darter: { holds: 90, swings: 0.5 },
134
+ /** They pass along a coast rather than live on one stretch of it. */
135
+ dolphin: { holds: 0.04, swings: 0.5 },
136
+ /** A grouper holds a hole and there are only so many holes. */
137
+ drifter: { holds: 1.1, swings: 0.15 },
138
+ /** Where there is a bed of them there are hundreds, and where there is not
139
+ * there are none. The colony's own arithmetic thins it to what fits. */
140
+ eel: { holds: 140, swings: 1.2 },
141
+ /** Butterflyfish, which go about in twos and are never far apart. */
142
+ escort: { holds: 6, swings: 0.35 },
143
+ /** A cold water animal, and in warm water a rarity worth the day. */
144
+ mane: { holds: 0.06, swings: 1.8 },
145
+ /** It goes where the wind puts it, so it arrives in a fleet or not at all. */
146
+ manowar: { holds: 0.015, swings: 2.6 },
147
+ /** Scarcest of the large ones, and tied to the few places they gather. */
148
+ manta: { holds: 0.012, swings: 0.7 },
149
+ /** Not an animal and not on a density; `visitors.ts` keeps her calendar. */
150
+ mermaid: { holds: 0, swings: 0 },
151
+ /** The jelly anybody can name, and it is common enough to earn that. */
152
+ moon: { holds: 0.45, swings: 2 },
153
+ /** One to a den, and a den is a find. */
154
+ octopus: { holds: 0.5, swings: 0.25 },
155
+ /** Solitary, territorial, and one to a stretch of reef this size. */
156
+ puffer: { holds: 0.4, swings: 0.4 },
157
+ /** A reef shark works a stretch of reef that is larger than this one. */
158
+ shark: { holds: 0.09, swings: 0.4 },
159
+ /** Reef squid keep company, so where there is one there are a few. */
160
+ squid: { holds: 0.6, swings: 0.8 },
161
+ /** Slow, obvious, and lying in the open, which is why they read as common. */
162
+ starfish: { holds: 4, swings: 0.5 },
163
+ /** The sea nettle, in on the season the way the barrel is. */
164
+ stinger: { holds: 0.18, swings: 1.8 },
165
+ /** A billfish over a reef is a visitor from the water beyond it. */
166
+ swordfish: { holds: 0.05, swings: 0.6 },
167
+ /** They graze a reef and sleep under its ledges, so one is about. */
168
+ turtle: { holds: 0.12, swings: 0.35 },
169
+ }
170
+
171
+ /**
172
+ * How hard the range is pulled in.
173
+ *
174
+ * The root `sizes.ts` takes of a length, taken here of a density, and picked
175
+ * the same way: at this it is the small fish that carry the water and the crab
176
+ * and the octopus are still in it. Nearer 1 and the darters bury everything
177
+ * else; nearer 0 and every animal in the sea is about as common as every other,
178
+ * which is the picture this file exists to prevent.
179
+ */
180
+ const ROOT = 0.55
181
+
182
+ /**
183
+ * How wide a stretch of sea the box is a window on, in metres.
184
+ *
185
+ * It follows from `sizes.ts` rather than being chosen here. A fish of a third
186
+ * of a metre swimming at eight comes out at a fortieth of the box, and a window
187
+ * that fits forty of those across it is a window on about this much water. It
188
+ * is worth knowing that the answer is a room and not an ocean: everything in
189
+ * this scene is close enough to touch, which is why a shark crossing it is an
190
+ * event.
191
+ */
192
+ const ACROSS = 13
193
+
194
+ /**
195
+ * How many the sea is drawn with, against how many it holds.
196
+ *
197
+ * The one number the file is tuned by, and it was fixed by the shoal: the
198
+ * desktop's water held about forty fish before any of this was written down, so
199
+ * it holds about forty now, and every other animal moved to meet them.
200
+ */
201
+ const SCALE = 2.1
202
+
203
+ /** Square metres of reef in view, which a taller window sees more of. */
204
+ function seaIn(width: number, height: number): number {
205
+ return ACROSS * ACROSS * (height / Math.max(1, width))
206
+ }
207
+
208
+ /**
209
+ * A number of this kind's own, so no two kinds have the same day.
210
+ *
211
+ * Off the name rather than off a position in the table, because a position
212
+ * moves when something is added above it, and a sea that changed everywhere
213
+ * because a jellyfish was added is a sea nobody can reason about.
214
+ */
215
+ function own(kind: Kind): number {
216
+ let run = 0
217
+ for (let at = 0; at < kind.length; at++) run = stir(run + kind.charCodeAt(at))
218
+ return run & 0xffff
219
+ }
220
+
221
+ /**
222
+ * How common this kind is drawn today: compressed, and then given its day.
223
+ *
224
+ * That order is the whole of it. Compressed first and swung afterwards, a bloom
225
+ * is a bloom; swung first and compressed afterwards, the root takes most of the
226
+ * day back off it again and a week of water is a week of the same water.
227
+ */
228
+ function drawnHolds(kind: Kind, seed: number): number {
229
+ const held = SEA[kind]
230
+ if (held.holds <= 0) return 0
231
+ return held.holds ** ROOT * thriving(seed, own(kind)) ** held.swings
232
+ }
233
+
234
+ /**
235
+ * How many of these kinds to draw in a box this size, on this day.
236
+ *
237
+ * Comes back as it falls out rather than rounded, since a caller with a floor
238
+ * and a ceiling of its own wants the raw appetite to apply them to.
239
+ */
240
+ export function manyIn(
241
+ kinds: readonly Kind[],
242
+ width: number,
243
+ height: number,
244
+ seed: number,
245
+ ): number {
246
+ let sum = 0
247
+ for (const kind of kinds) sum += drawnHolds(kind, seed)
248
+ return (SCALE * sum * seaIn(width, height)) / 100
249
+ }
250
+
251
+ /**
252
+ * The shares to draw one of these kinds from, on this day.
253
+ *
254
+ * Weights against each other rather than chances, which is the shape every
255
+ * family in this ornament already wanted. They are not normalised, because
256
+ * nothing that draws from them needs them to be.
257
+ */
258
+ export function mixOf<K extends Kind>(kinds: readonly K[], seed: number): Record<K, number> {
259
+ const mix = {} as Record<K, number>
260
+ for (const kind of kinds) mix[kind] = drawnHolds(kind, seed)
261
+ return mix
262
+ }