Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/game/public/assets/PROVENANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ worse textures.
Not everything here is generated. These are licensed, and their provenance is
the licence rather than a generator script.

| File | Source | Licence |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------- |
| `models/fighter_swat.glb` | [Quaternius — Ultimate Modular Characters](https://quaternius.com/packs/ultimatemodularcharacters.html) (`Swat`) | CC0 1.0 |
| `models/fighter_worker.glb` | [Quaternius — Ultimate Modular Characters](https://quaternius.com/packs/ultimatemodularcharacters.html) (`Worker`) | CC0 1.0 |
| File | Source | Licence |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------- |
| `models/fighter_swat.glb` | [Quaternius — Ultimate Modular Characters](https://quaternius.com/packs/ultimatemodularcharacters.html) (`Swat`) | CC0 1.0 |
| `models/fighter_adventurer.glb` | [Quaternius — Ultimate Modular Characters](https://quaternius.com/packs/ultimatemodularcharacters.html) (`Adventurer`) | CC0 1.0 |

**CC0 1.0** (https://creativecommons.org/publicdomain/zero/1.0/) is a public
domain dedication: unrestricted commercial use, modification and
Expand Down
14 changes: 8 additions & 6 deletions apps/game/public/assets/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"generator": "tools/art/build-assets.mjs",
"blender": "Blender 4.5.12 LTS",
"commit": "bfc24e463e97c8c28389df2ba795a458561313d5",
"commit": "ad926825e31ded8e6197881e6d68cf00f368ed65",
"license": "Original work, © NIGHTCELL 7. No third-party assets.",
"source": "Generated procedurally from the scripts in tools/art. No asset is downloaded, photographed, traced, or derived from another game.",
"textureSize": 1024,
Expand All @@ -11,8 +11,8 @@
"character.glb",
"container.glb",
"deck.glb",
"fighter_adventurer.glb",
"fighter_swat.glb",
"fighter_worker.glb",
"hardpoint.glb",
"lamp_mast.glb",
"pipe_rack.glb",
Expand Down Expand Up @@ -72,12 +72,14 @@
"music_frost-on-the-oar.mp3",
"music_ironwood-oath.mp3",
"music_runes-on-ice.mp3",
"music_storm-crown-oath.mp3"
"music_storm-crown-oath.mp3",
"music_the-wolf-called-want-part-2.mp3",
"music_the-wolf-called-want.mp3"
],
"bytes": {
"models": 3668192,
"models": 4206012,
"textures": 1971598,
"audio": 326680,
"total": 5966470
"audio": 323166,
"total": 6500776
}
}
Binary file not shown.
Binary file removed apps/game/public/assets/models/fighter_worker.glb
Binary file not shown.
16 changes: 12 additions & 4 deletions apps/game/src/assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,18 @@ const TEXTURES_DIR = join(ASSETS, "textures");
*
* The hard limit is the 15 MB shell download budget (PRD §30,
* `DOWNLOAD_BUDGET_BYTES`), which also has to fit the engine, the code and the
* fonts. 6 MB leaves room for all of that and is roughly double what the
* current set uses, so it catches a runaway without failing on every addition.
* fonts.
*
* Raised from 6 MB to 9 MB when the two licensed characters landed: they are
* 2.8 MB between them and pushed the set to 6.2 MB legitimately, rather than
* through any runaway. 9 MB still leaves real headroom under the hard limit
* and would catch a genuine mistake — an uncompressed texture set or a
* forgotten master would blow straight past it.
*
* Streamed music is deliberately outside this: it is fetched on demand and the
* game is playable before a note arrives.
*/
const ASSET_BUDGET_BYTES = 6 * 1024 * 1024;
const ASSET_BUDGET_BYTES = 9 * 1024 * 1024;

/** Read the JSON chunk out of a GLB container. */
function glbJson(file: string): {
Expand Down Expand Up @@ -70,7 +78,7 @@ describe("generated models", () => {
* apply. Their provenance is a licence recorded in PROVENANCE.md rather than
* a generator script.
*/
const LICENSED = new Set(["fighter_swat", "fighter_worker"]);
const LICENSED = new Set(["fighter_swat", "fighter_adventurer"]);

it("only uses material slots the engine can bind", () => {
// A slot the engine does not know about is not an error at load time: the
Expand Down
2 changes: 1 addition & 1 deletion apps/game/src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const MODELS = [
// animations and materials, so they bypass the material-slot convention the
// generated props follow — see apps/game/public/assets/PROVENANCE.md.
"fighter_swat",
"fighter_worker",
"fighter_adventurer",
] as const;

export type ModelName = (typeof MODELS)[number];
Expand Down
2 changes: 2 additions & 0 deletions apps/game/src/audio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const MUSIC = [
"music_ironwood-oath",
"music_runes-on-ice",
"music_storm-crown-oath",
"music_the-wolf-called-want",
"music_the-wolf-called-want-part-2",
] as const;

type VariedName = keyof typeof VARIED;
Expand Down
9 changes: 6 additions & 3 deletions apps/game/src/opponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,11 @@ export class Opponents {
private readonly deadUntil = new Map<string, number>();

constructor(scene: Scene, assets: AssetSet) {
// One model per faction. The Directorate is equipped and uniformed; the
// Nightcell side are irregulars in civilian clothing. That difference is
// One model per faction.
//
// Nightcell are irregulars: olive drab, boots, a pack — someone fighting
// with what they own. The first attempt used the pack's `Worker`, which is
// a hi-vis construction labourer and read exactly like one. That difference is
// the fastest target ID a player gets, and it is carried by the whole
// silhouette rather than by a colour swatch.
// Back on the generated character for now.
Expand All @@ -153,7 +156,7 @@ export class Opponents {
// licensed ones are diagnosed. Swapping back is a one-line change:
// assets.models.get("fighter_swat") / ("fighter_worker")
const enemyModel = assets.models.get("fighter_swat") ?? assets.models.get("character");
const friendlyModel = assets.models.get("fighter_worker") ?? assets.models.get("character");
const friendlyModel = assets.models.get("fighter_adventurer") ?? assets.models.get("character");
const character = enemyModel;
const carbine = assets.models.get("carbine");
if (!character) throw new Error("no character model loaded");
Expand Down
14 changes: 10 additions & 4 deletions tools/art/build-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ function toWebp(pngDir, outDir) {
*/
function directoryBytes(dir, skip) {
if (!existsSync(dir)) return 0;
const drop = typeof skip === "function" ? skip : (f) => Boolean(skip?.test(f));
return readdirSync(dir)
.filter((f) => !skip || !skip.test(f))
.filter((f) => !skip || !drop(f))
.reduce((sum, f) => sum + statSync(join(dir, f)).size, 0);
}

Expand Down Expand Up @@ -267,10 +268,15 @@ function main() {
const modelBytes = directoryBytes(MODELS_OUT);
const textureBytes = directoryBytes(TEXTURES_OUT);
// Skip streamed music and any video the directory happens to hold.
const audioBytesTotal = directoryBytes(AUDIO_OUT, /^music_|\.mp4$/);
// Allowlist the generated effects rather than trying to pattern-match the
// music. Tracks arrive with whatever names they were given, so an exclusion
// pattern silently under-counts the moment one does not match — which is how
// 22 MB of songs ended up inside a 6 MB budget guard.
const SFX = /^(fire_|step_|impact_|reload\.|ui_|ambience_yard\.)/;
const audioBytesTotal = directoryBytes(AUDIO_OUT, (f) => !SFX.test(f));
const audio = existsSync(AUDIO_OUT)
? readdirSync(AUDIO_OUT)
.filter((f) => f.endsWith(".mp3") && !f.startsWith("music_"))
.filter((f) => f.endsWith(".mp3") && SFX.test(f))
.sort()
: [];

Expand Down Expand Up @@ -301,7 +307,7 @@ function main() {
// Streamed on demand, so outside the shell budget entirely.
music: existsSync(AUDIO_OUT)
? readdirSync(AUDIO_OUT)
.filter((f) => f.startsWith("music_") && f.endsWith(".mp3"))
.filter((f) => f.endsWith(".mp3") && !SFX.test(f))
.sort()
: [],
bytes: {
Expand Down
Loading