@johnmorrisdotca/kyogijo-stadiums 0.44.0 → 0.44.2

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/CHANGELOG.md CHANGED
@@ -7,6 +7,41 @@ All notable changes to this project are written here. The format follows
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.44.2] - 2026-10-29
11
+
12
+ ### Changed
13
+
14
+ - **The district page turns faster, with nothing drawn differently where it can be said**: a step of a turn cost the page's own script and the browser's drawing in proportion to the number of planes of the 3D view, and the view had grown from about 1,200 planes (0.40.2, Seattle) to about 2,200. The faces of an opaque building that turn away from the eye are no longer written to or drawn (`Plane.cull`, set on the walls of the district's surrounding blocks and of a solid Kodokan: a closed solid hides its own far faces, and `layoutOf` takes a `cull` argument that leaves them out, off by default so that a still picture and the tests lay out every plane as before); the matrices and numbers of a step are worked out with less allocation (the layout is the same to the last digit). At the opening view of the Seattle district that is 419 of 2,174 planes left out, and the step costs the main thread 20 to 35 percent less in Chromium (Seattle 28.8 to 23.0 ms, Tokyo Dome City with the Kodokan 31.7 to 23.3 ms, on a 1280 by 800 window; 123 to 99 ms and 133 to 100 ms with the CPU slowed four times) and about a quarter less in WebKit; in the default view of the surroundings, where the hidden walls used to be painted over a neighbour here and there, a few of the edge pixels are not the same (under 2.7 percent of the pixels of a district, almost all the thin line where two walls meet).
15
+ - **The demo's pages ask for all their modules at once**: `scripts/module-preload.mjs` writes a `modulepreload` link for every module a page imports, in its head (the district page imports 246 files, seven deep, and the browser used to learn of each only when it had read the one above it). On a connection with a 60 ms delay it opens the district page at 740 ms instead of 1,015, and at 150 ms, 10 Mbit/s and a CPU four times slower at 1,740 ms instead of 2,480.
16
+ - **The district page carries the data of the area it shows and no other**: the surroundings of each place (downtown Vancouver's buildings and streets, its water and trees, Seattle's SoDo, Seattle Center and the city view, Tokyo Dome City's PLATEAU buildings) were 600 kilobytes of script that every district preset loaded, and Seattle's city view alone was 465 of them. Each area is one file now (`src/cityctx.<area>.data.ts`, written by `pnpm cityctx`; `osm.context.data.ts` and `plateau.data.ts` were one area already), and the page reads the files of the venues it shows with a dynamic `import()` (`loadAreasFor`, the new `areas` entry), starting the first view's area beside its modules, and the next preset's when it is chosen. Not preloaded: the head names none of them. A test follows each preset's network log and holds it to its own area's files, and another holds the import graph: no entry but `cityctx`, `osm` and `plateau` reaches an area's data. Nothing is drawn differently: the stage of every preset, in 2D and in 3D, is the same pixels as before, and the planes of each view are the same. The bytes of script a page loads, desk window, Chromium (`scripts/perf-district.mjs`; raw kilobytes, gzip in brackets):
17
+
18
+ | Preset | Before | After | Area file read |
19
+ |---|---|---|---|
20
+ | Rogers Arena alone, BC Place and Rogers Arena (both configurations) | 3,737 (1,040) | 3,246 (925) | Vancouver's buildings and streets 77 (19), its nature 19 (6) |
21
+ | Seattle Center | 3,737 (1,040) | 3,279 (932) | 128 (32) |
22
+ | SoDo | 3,737 (1,040) | 3,248 (926) | 97 (25) |
23
+ | Seattle, three venues | 3,737 (1,040) | 3,362 (948) | the city view 211 (48) |
24
+ | Tokyo Dome City, and with the Kodokan | 3,737 (1,040) | 3,208 (916) | PLATEAU's buildings 57 (15) |
25
+ | The Kodokan alone | 3,737 (1,040) | 3,151 (901) | none |
26
+
27
+ Planes of the 3D view before and after (unchanged): Rogers Arena alone 547, BC Place and Rogers Arena 1,309, with the soccer pitch 1,305, Seattle Center 1,088, SoDo 1,346, Seattle 2,175, Tokyo 2,049, with the Kodokan 2,286, the Kodokan alone 152. A step of a turn and the time to the first draw did not move beyond what the measuring machine's load could hide (load average about 30 while measuring: the same run twice differed by more than the change); the saving is in bytes, and in parsing the data of areas that are not shown. Still loaded by every district page and not part of this change: the venues' own data (`arenas.data`, `parks.data`, `grounds.data`, about 450 kilobytes) and the crowd's walkable mask (`walk.data`, Vancouver's, 90).
28
+
29
+ ### Added
30
+
31
+ - **`@johnmorrisdotca/kyogijo-stadiums/areas`** (new entry): `loadAreasFor(venues)` reads the areas of context that go with some venues, each from its own file, and gives them for a district's `context` option; `loadArea(key)` reads one, `areaKeysFor(venues)` says which without reading anything, and `AREA_KEYS`, `CITYCTX_VENUES` and `TOKYO_VENUES` list them. It chooses what `contextFor` of the `cityctx` entry does (and Tokyo Dome City's, for the Tokyo venues). The `cityctx`, `osm` and `plateau` entries are unchanged and still export every area at once, so nothing that imports them breaks; `CITYCTX_VENUES` is now defined in the `areas` entry's file and exported by `cityctx` as before.
32
+ - `pnpm exec node scripts/perf-district.mjs` measures the district page in Chromium and WebKit, on a desk and a phone window at the device pixel ratios 1, 2 and 3, with the CPU slowed or not: the time to draw, the bytes of script, the elements and planes, the cost of a step of a turn and the memory the layers would take. `e2e/perf.demo.mjs` holds the counts that decide the speed (planes, planes drawn, elements) and the modules' preload to a budget, so that a change that doubles them is found by the test. The slow server of the first-paint tests holds a page's scripts back for at least 1.5 seconds (it was 0.25 to 0.7 for each of seven levels of imports, and with the modules asked for together one pause is all there is).
33
+
34
+ ### Fixed
35
+
36
+ - **The demo's pages no longer throw a SecurityError in Safari when the view is turned over and over.** Every choice a demo page makes is written into its address with `history.replaceState`, and WebKit refuses the hundred-and-first call in ten seconds; a held arrow key or a run of turn buttons made thirty a second, so the Kodokan's district page (and every other demo page) logged an uncaught error for each. The pages now write through `demo/address.js`, which writes at once while there is room (ninety in ten seconds), keeps only the latest address past that and writes it when the window has room, and never throws. A new end-to-end test turns the Kodokan a full turn at three tilts on every floor and in each state of its building, in Chromium and WebKit, and fails on any page error or any matrix that is not a number. (This is the one error the Kodokan's turn could be made to raise; the crash a reader reported on a phone, perhaps the memory of the large layers at a high pixel ratio, has not been reproduced.)
37
+ - **The demo's featured arena keeps its place when the script takes over, in Safari on a Mac.** The venue list in the page's HTML is an empty select, which WebKit on macOS draws shorter than its minimum height, so filling it moved everything under it down by 21 px (the first-paint test failed there, on 0.44.0 and 0.44.1). The row now holds the height of a filled one.
38
+
39
+ ## [0.44.1] - 2026-10-28
40
+
41
+ ### Fixed
42
+
43
+ - **The wheel over a focused 3D view tips it the way a drag does**: the wheel up lifts the near edge of the surface toward the viewer, like a drag up (it did the opposite, so the wheel and the hand disagreed), and the wheel down lowers it. A drag, the arrow keys and the "Tip the surface away" and "Tip the surface toward you" buttons already agreed (up lifts the near edge); a test now holds all four to one direction, and a finger that is let to turn tips it as a mouse does. Left and right are unchanged. The `pitch` of a view, in the address and in `TurnView`, keeps its meaning.
44
+
10
45
  ## [0.44.0] - 2026-10-22
11
46
 
12
47
  ### Added
@@ -0,0 +1,2 @@
1
+ export { AREA_KEYS, CITYCTX_VENUES, TOKYO_VENUES, areaKeysFor, loadArea, loadAreasFor } from "./areas.ts";
2
+ export type { ContextArea } from "./context.ts";
@@ -0,0 +1,2 @@
1
+ // The areas of context one file each, read when asked for: which go with a choice of venues (`areaKeysFor`) and the data of one or of those (`loadArea`, `loadAreasFor`), as data a district takes in its `context` option. The entries `osm`, `cityctx` and `plateau` export the same data all at once.
2
+ export { AREA_KEYS, CITYCTX_VENUES, TOKYO_VENUES, areaKeysFor, loadArea, loadAreasFor } from "./areas.js";
@@ -0,0 +1,46 @@
1
+ import type { ContextArea } from "./context.ts";
2
+ /** The venues each area of Vancouver and Seattle is drawn round, by their ids (the part of a district's key before the colon). */
3
+ export declare const CITYCTX_VENUES: Readonly<{
4
+ vancouver: string[];
5
+ sodo: string[];
6
+ center: string[];
7
+ }>;
8
+ /** The venues of Tokyo Dome City, round which Project PLATEAU's buildings are drawn (the Kodokan stands a little way off, inside the area). */
9
+ export declare const TOKYO_VENUES: readonly string[];
10
+ /** The key of every area there is data for, in the order a district draws them: each is one file. */
11
+ export declare const AREA_KEYS: readonly string[];
12
+ /**
13
+ * The keys of the areas that go with some venues, in the order to draw them: downtown Vancouver's buildings and streets and its water, green and trees for BC Place and Rogers Arena; Seattle's SoDo for Lumen Field and
14
+ * T-Mobile Park, Seattle Center's for Climate Pledge Arena, and the city's own for a district that holds venues of both (they are three kilometres apart, so the detail of either would be a speck); Tokyo Dome City's for
15
+ * Tokyo Dome, Korakuen Hall and the Kodokan, which take it before any other. A venue of none of these has no area.
16
+ * @example
17
+ * ```ts
18
+ * import { areaKeysFor } from "@johnmorrisdotca/kyogijo-stadiums/areas";
19
+ * console.log(areaKeysFor(["bc-place:cfl", "rogers-arena"]));
20
+ * // [ 'vancouver', 'vancouver-nature' ]
21
+ * ```
22
+ */
23
+ export declare function areaKeysFor(venues: readonly string[]): string[];
24
+ /**
25
+ * One area by its key (`AREA_KEYS`), read from its own file the first time and kept after: asking again gives the same promise. A key that is not an area rejects, naming the keys there are.
26
+ * @example
27
+ * ```ts
28
+ * import { loadArea } from "@johnmorrisdotca/kyogijo-stadiums/areas";
29
+ * const area = await loadArea("seattle-sodo");
30
+ * console.log(area.key, area.radius);
31
+ * ```
32
+ */
33
+ export declare function loadArea(key: string): Promise<ContextArea>;
34
+ /**
35
+ * The areas that go with some venues, for a district's `context` option, each read from its own file and no other: what `contextFor` of the `cityctx` entry gives, and Tokyo Dome City's too, without carrying every area's data to
36
+ * get them.
37
+ * @example
38
+ * ```ts
39
+ * import { loadAreasFor } from "@johnmorrisdotca/kyogijo-stadiums/areas";
40
+ * import { planDistrict } from "@johnmorrisdotca/kyogijo-stadiums/district";
41
+ * const venues = ["bc-place:cfl", "rogers-arena"];
42
+ * const plan = planDistrict({ venues, context: await loadAreasFor(venues) });
43
+ * console.log(plan.context?.attribution.en.length);
44
+ * ```
45
+ */
46
+ export declare function loadAreasFor(venues: readonly string[]): Promise<ContextArea[]>;
package/dist/areas.js ADDED
@@ -0,0 +1,87 @@
1
+ /** The venues each area of Vancouver and Seattle is drawn round, by their ids (the part of a district's key before the colon). */
2
+ export const CITYCTX_VENUES = Object.freeze({
3
+ vancouver: ["bc-place", "rogers-arena"],
4
+ sodo: ["lumen-field", "t-mobile-park"],
5
+ center: ["climate-pledge-arena"],
6
+ });
7
+ /** The venues of Tokyo Dome City, round which Project PLATEAU's buildings are drawn (the Kodokan stands a little way off, inside the area). */
8
+ export const TOKYO_VENUES = Object.freeze(["tokyo-dome", "korakuen-hall", "kodokan"]);
9
+ /** The key of every area there is data for, in the order a district draws them: each is one file. */
10
+ export const AREA_KEYS = Object.freeze(["vancouver", "vancouver-nature", "seattle-sodo", "seattle-center", "seattle-city", "tokyo-dome-city"]);
11
+ /** Each area's loader: the one `import()` of the one file that holds it. */
12
+ const LOADERS = {
13
+ "vancouver": async () => (await import("./osm.context.data.js")).VANCOUVER_CENTRE,
14
+ "vancouver-nature": async () => (await import("./cityctx.vancouver-nature.data.js")).VANCOUVER_NATURE,
15
+ "seattle-sodo": async () => (await import("./cityctx.seattle-sodo.data.js")).SEATTLE_SODO,
16
+ "seattle-center": async () => (await import("./cityctx.seattle-center.data.js")).SEATTLE_CENTER,
17
+ "seattle-city": async () => (await import("./cityctx.seattle-city.data.js")).SEATTLE_CITY,
18
+ "tokyo-dome-city": async () => (await import("./plateau.data.js")).TOKYO_DOME_CITY,
19
+ };
20
+ /**
21
+ * The keys of the areas that go with some venues, in the order to draw them: downtown Vancouver's buildings and streets and its water, green and trees for BC Place and Rogers Arena; Seattle's SoDo for Lumen Field and
22
+ * T-Mobile Park, Seattle Center's for Climate Pledge Arena, and the city's own for a district that holds venues of both (they are three kilometres apart, so the detail of either would be a speck); Tokyo Dome City's for
23
+ * Tokyo Dome, Korakuen Hall and the Kodokan, which take it before any other. A venue of none of these has no area.
24
+ * @example
25
+ * ```ts
26
+ * import { areaKeysFor } from "@johnmorrisdotca/kyogijo-stadiums/areas";
27
+ * console.log(areaKeysFor(["bc-place:cfl", "rogers-arena"]));
28
+ * // [ 'vancouver', 'vancouver-nature' ]
29
+ * ```
30
+ */
31
+ export function areaKeysFor(venues) {
32
+ const ids = new Set(venues.map((key) => key.split(":")[0]));
33
+ const has = (list) => list.some((id) => ids.has(id));
34
+ if (has(TOKYO_VENUES))
35
+ return ["tokyo-dome-city"];
36
+ const keys = [];
37
+ if (has(CITYCTX_VENUES.vancouver))
38
+ keys.push("vancouver", "vancouver-nature");
39
+ const [sodo, center] = [has(CITYCTX_VENUES.sodo), has(CITYCTX_VENUES.center)];
40
+ if (sodo && center)
41
+ keys.push("seattle-city");
42
+ else if (sodo)
43
+ keys.push("seattle-sodo");
44
+ else if (center)
45
+ keys.push("seattle-center");
46
+ return keys;
47
+ }
48
+ const loaded = new Map();
49
+ /**
50
+ * One area by its key (`AREA_KEYS`), read from its own file the first time and kept after: asking again gives the same promise. A key that is not an area rejects, naming the keys there are.
51
+ * @example
52
+ * ```ts
53
+ * import { loadArea } from "@johnmorrisdotca/kyogijo-stadiums/areas";
54
+ * const area = await loadArea("seattle-sodo");
55
+ * console.log(area.key, area.radius);
56
+ * ```
57
+ */
58
+ export function loadArea(key) {
59
+ const kept = loaded.get(key);
60
+ if (kept !== undefined)
61
+ return kept;
62
+ const load = Object.hasOwn(LOADERS, key) ? LOADERS[key] : undefined;
63
+ if (load === undefined)
64
+ return Promise.reject(new Error(`no area of context ${JSON.stringify(key)}: the areas are ${AREA_KEYS.join(", ")}`));
65
+ const promise = load().catch((error) => {
66
+ // A file that did not come (the network) is asked for again next time, not remembered as failed.
67
+ loaded.delete(key);
68
+ throw error;
69
+ });
70
+ loaded.set(key, promise);
71
+ return promise;
72
+ }
73
+ /**
74
+ * The areas that go with some venues, for a district's `context` option, each read from its own file and no other: what `contextFor` of the `cityctx` entry gives, and Tokyo Dome City's too, without carrying every area's data to
75
+ * get them.
76
+ * @example
77
+ * ```ts
78
+ * import { loadAreasFor } from "@johnmorrisdotca/kyogijo-stadiums/areas";
79
+ * import { planDistrict } from "@johnmorrisdotca/kyogijo-stadiums/district";
80
+ * const venues = ["bc-place:cfl", "rogers-arena"];
81
+ * const plan = planDistrict({ venues, context: await loadAreasFor(venues) });
82
+ * console.log(plan.context?.attribution.en.length);
83
+ * ```
84
+ */
85
+ export function loadAreasFor(venues) {
86
+ return Promise.all(areaKeysFor(venues).map(loadArea));
87
+ }
package/dist/cityctx.d.ts CHANGED
@@ -1,14 +1,11 @@
1
1
  import type { ContextArea } from "./context.ts";
2
+ import { CITYCTX_VENUES } from "./areas.ts";
2
3
  /** The venues each area is drawn round, by their ids (the part of a district's key before the colon). */
3
- export declare const CITYCTX_VENUES: Readonly<{
4
- vancouver: string[];
5
- sodo: string[];
6
- center: string[];
7
- }>;
4
+ export { CITYCTX_VENUES };
8
5
  /**
9
6
  * The areas of context that go with some venues, for a district's `context` option: downtown Vancouver's buildings and streets (`VANCOUVER_CENTRE`, of `./osm`) with its water, green and trees (`VANCOUVER_NATURE`) for BC Place and
10
7
  * Rogers Arena; Seattle's SoDo for Lumen Field and T-Mobile Park, Seattle Center's for Climate Pledge Arena, and the city's own, with streets and the buildings that matter from far, for a district that holds venues of both (they are
11
- * three kilometres apart, so the detail of either would be a speck). A venue of neither city has none.
8
+ * three kilometres apart, so the detail of either would be a speck). A venue of neither city has none. This one carries every area's data; `loadAreasFor` of the `areas` entry reads only the ones asked for.
12
9
  * @example
13
10
  * ```ts
14
11
  * import { contextFor } from "@johnmorrisdotca/kyogijo-stadiums/cityctx";
@@ -1,11 +1,8 @@
1
1
  import type { ContextArea } from "./context.ts";
2
- /** Vancouver: water, green and trees round BC Place and Rogers Arena: 0 buildings, 0 street lines, 28 land cover outlines and 1370 trees within 450 metres of the middle, read 2026-10-22. */
3
- export declare const VANCOUVER_NATURE: ContextArea;
4
- /** Seattle: SoDo, Lumen Field and T-Mobile Park: 230 buildings, 1134 street lines, 8 land cover outlines and 1385 trees within 650 metres of the middle, read 2026-10-22. */
5
- export declare const SEATTLE_SODO: ContextArea;
6
- /** Seattle: Seattle Center and Climate Pledge Arena: 449 buildings, 1340 street lines, 47 land cover outlines and 1400 trees within 550 metres of the middle, read 2026-10-22. */
7
- export declare const SEATTLE_CENTER: ContextArea;
8
- /** Seattle: the city between the three venues: 1300 buildings, 2392 street lines, 49 land cover outlines and 0 trees within 3000 metres of the middle, read 2026-10-22. */
9
- export declare const SEATTLE_CITY: ContextArea;
2
+ import { VANCOUVER_NATURE } from "./cityctx.vancouver-nature.data.ts";
3
+ import { SEATTLE_SODO } from "./cityctx.seattle-sodo.data.ts";
4
+ import { SEATTLE_CENTER } from "./cityctx.seattle-center.data.ts";
5
+ import { SEATTLE_CITY } from "./cityctx.seattle-city.data.ts";
6
+ export { VANCOUVER_NATURE, SEATTLE_SODO, SEATTLE_CENTER, SEATTLE_CITY };
10
7
  /** Every area, by key. */
11
8
  export declare const CITYCTX_AREAS: Readonly<Record<string, ContextArea>>;