@jgengine/core 0.21.0 → 0.22.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/CHANGELOG.md CHANGED
@@ -26,6 +26,24 @@ At publish, rename this heading to the new version and mirror the entries into
26
26
  `packages/core/src/meta/changelog.ts` (the typed `CHANGELOG` export).
27
27
  -->
28
28
 
29
+ ## 0.22.0
30
+
31
+ ### Migrate
32
+
33
+ - **Bump lockstep SDK packages to `^0.22.0`:** `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.19.0`; `@jgengine/github` is `0.9.0`.
34
+
35
+ ### Added
36
+
37
+ - Editor-authored grass exposes blade width and bend ranges, tuft radius and normal lift, using the same bounded grass renderer as environment fields. Existing scene documents keep their previous shape and lighting defaults.
38
+ - The full `jgengine skills` install includes `game-improvement`, with an ordered implementation workflow and checks for meaningful decisions, authored terrain and grass, and proven play quality.
39
+
40
+ ### Fixed
41
+
42
+ - Grass volumes sample terrain at their authored world position and subtract their placement height once, keeping translated patches rooted on slopes instead of buried underground.
43
+
44
+ - Authored grass respects spawn, objective and road/path clearances with feathered regrowth outside the usable footing. Diagonal paths keep their own corridor clear without clearing the entire surrounding rectangle.
45
+ - Runtime environment ground now renders editor-authored terrain paint surfaces, layer tints, opacity and weighted blends through the same sampler as the editor. Legacy unpainted terrain keeps descriptor shading. Layer texture tiling, roughness and triplanar controls remain serialized authoring metadata; this fix does not add layered PBR texture rendering.
46
+
29
47
  ## 0.21.0
30
48
 
31
49
  ### Migrate
@@ -124,3 +124,4 @@ export type { AsciiGridExportOptions } from "./gridAdapters.js";
124
124
  export type { CsvGridImportOptions } from "./gridAdapters.js";
125
125
  /** Options for exporting a grid layer as CSV. */
126
126
  export type { CsvGridExportOptions } from "./gridAdapters.js";
127
+ export { TERRAIN_MATERIALS, TERRAIN_MATERIAL_COLORS, type TerrainMaterial } from "./terrainMaterials.js";
@@ -15,3 +15,4 @@ export { applyDocumentPatch, applyRuntimeStateDelta, createDocumentLiveSync, get
15
15
  export { consumeRuntimePlayStep, createRuntimePlayControl, getRuntimeInspectorValue, planRuntimeInspectorSet, runtimeEntityMetaWriteBackCommand, summarizeRuntimeInspector, } from "./runtimeInspector.js";
16
16
  export { CURRENT_GRID_SCHEMA_VERSION, cloneGridLayer, createGridLayer, eraseGridCell, eyedropGridCell, fillGridRect, findGridPaletteEntry, floodFillGrid, forEachGridCell, getGridCell, getGridCellAtWorld, gridCellCount, gridCellEntries, gridCellKey, gridCellToWorld, gridCellsOfValue, gridEmptyValue, gridGlyphMap, inGridBounds, migrateGridLayer, paintGridCells, parseGridCellKey, resizeGridLayer, setGridCell, worldToGridCell, } from "./grid.js";
17
17
  export { exportAsciiGrid, exportCsvGrid, importAsciiGrid, importCsvGrid, } from "./gridAdapters.js";
18
+ export { TERRAIN_MATERIALS, TERRAIN_MATERIAL_COLORS } from "./terrainMaterials.js";
@@ -0,0 +1,10 @@
1
+ /** A paintable terrain material layer — a surface id plus the color it renders as. */
2
+ export interface TerrainMaterial {
3
+ id: string;
4
+ label: string;
5
+ color: string;
6
+ }
7
+ /** The default terrain paint palette (surface id → color) shared by the panel and the mesh. */
8
+ export declare const TERRAIN_MATERIALS: readonly TerrainMaterial[];
9
+ /** Maps every default material id to its render color, for the sculpt mesh's per-cell surface tint. */
10
+ export declare const TERRAIN_MATERIAL_COLORS: Record<string, string>;
@@ -0,0 +1,13 @@
1
+ /** The default terrain paint palette (surface id → color) shared by the panel and the mesh. */
2
+ export const TERRAIN_MATERIALS = [
3
+ { id: "grass", label: "Grass", color: "#4b7f3f" },
4
+ { id: "dirt", label: "Dirt", color: "#6b4f34" },
5
+ { id: "rock", label: "Rock", color: "#7c7f86" },
6
+ { id: "sand", label: "Sand", color: "#c2b283" },
7
+ { id: "mud", label: "Mud", color: "#574433" },
8
+ { id: "snow", label: "Snow", color: "#eef3f7" },
9
+ { id: "road", label: "Road", color: "#3a3a3d" },
10
+ { id: "gravel", label: "Gravel", color: "#8d8d84" },
11
+ ];
12
+ /** Maps every default material id to its render color, for the sculpt mesh's per-cell surface tint. */
13
+ export const TERRAIN_MATERIAL_COLORS = Object.fromEntries(TERRAIN_MATERIALS.map((material) => [material.id, material.color]));
@@ -1,5 +1,5 @@
1
1
  /** Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. */
2
- export declare const VERSION = "0.21.0";
2
+ export declare const VERSION = "0.22.0";
3
3
  /** One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md). */
4
4
  export interface ChangelogEntry {
5
5
  migrate: readonly string[];
@@ -1,7 +1,22 @@
1
1
  /** Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. */
2
- export const VERSION = "0.21.0";
2
+ export const VERSION = "0.22.0";
3
3
  /** Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`). */
4
4
  export const CHANGELOG = {
5
+ "0.22.0": {
6
+ migrate: [
7
+ "Bump lockstep SDK packages to `^0.22.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.19.0`; `@jgengine/github` is `0.9.0`.",
8
+ ],
9
+ added: [
10
+ "Editor-authored grass exposes blade width and bend ranges, tuft radius and normal lift, using the same bounded grass renderer as environment fields. Existing scene documents keep their previous shape and lighting defaults.",
11
+ "The full `jgengine skills` install includes `game-improvement`, with an ordered implementation workflow and checks for meaningful decisions, authored terrain and grass, and proven play quality.",
12
+ ],
13
+ changed: [
14
+ "Grass volumes sample terrain at their authored world position and subtract their placement height once, keeping translated patches rooted on slopes instead of buried underground.",
15
+ "Authored grass respects spawn, objective and road/path clearances with feathered regrowth outside the usable footing. Diagonal paths keep their own corridor clear without clearing the entire surrounding rectangle.",
16
+ "Runtime environment ground now renders editor-authored terrain paint surfaces, layer tints, opacity and weighted blends through the same sampler as the editor. Legacy unpainted terrain keeps descriptor shading. Layer texture tiling, roughness and triplanar controls remain serialized authoring metadata; this fix does not add layered PBR texture rendering.",
17
+ ],
18
+ removed: [],
19
+ },
5
20
  "0.21.0": {
6
21
  migrate: [
7
22
  "Bump lockstep SDK packages to `^0.21.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.18.0`; `@jgengine/github` is `0.8.0`.",
@@ -16,6 +16,16 @@ export interface GrassRules {
16
16
  density: number;
17
17
  /** Blade height in meters. */
18
18
  bladeHeight: number;
19
+ /** Blade-width range in metres; endpoints are normalized on read. */
20
+ bladeWidthMin: number;
21
+ bladeWidthMax: number;
22
+ /** Bend range, 0..1. */
23
+ bladeBendMin: number;
24
+ bladeBendMax: number;
25
+ /** Spread of blades around each tuft root, in metres. */
26
+ tuftRadius: number;
27
+ /** Normal lift toward the sky, 0..1; controls the card lighting response. */
28
+ normalLift: number;
19
29
  /** Base (root) color, hex. */
20
30
  colorBase: string;
21
31
  /** Tip color, hex. */
@@ -15,6 +15,12 @@ export const GRASS_FIELD_KIND = "grass_field";
15
15
  export const GRASS_DEFAULTS = {
16
16
  density: 12,
17
17
  bladeHeight: 0.6,
18
+ bladeWidthMin: 0.05,
19
+ bladeWidthMax: 0.1,
20
+ bladeBendMin: 0.12,
21
+ bladeBendMax: 0.42,
22
+ tuftRadius: 0.16,
23
+ normalLift: 0.6,
18
24
  colorBase: "#3f6f37",
19
25
  colorTip: "#9ac25a",
20
26
  colorVariation: 0.35,
@@ -29,6 +35,12 @@ export const GRASS_SCHEMA = {
29
35
  fields: [
30
36
  { type: "range", key: "density", label: "density", min: 0.5, max: 160, step: 0.5, default: GRASS_DEFAULTS.density, unit: "/m²" },
31
37
  { type: "range", key: "bladeHeight", label: "blade height", min: 0.1, max: 2, step: 0.05, default: GRASS_DEFAULTS.bladeHeight, unit: "m" },
38
+ { type: "range", key: "bladeWidthMin", label: "minimum blade width", min: 0.005, max: 0.3, step: 0.005, default: GRASS_DEFAULTS.bladeWidthMin, unit: "m" },
39
+ { type: "range", key: "bladeWidthMax", label: "maximum blade width", min: 0.005, max: 0.3, step: 0.005, default: GRASS_DEFAULTS.bladeWidthMax, unit: "m" },
40
+ { type: "range", key: "bladeBendMin", label: "minimum bend", min: 0, max: 1, step: 0.01, default: GRASS_DEFAULTS.bladeBendMin },
41
+ { type: "range", key: "bladeBendMax", label: "maximum bend", min: 0, max: 1, step: 0.01, default: GRASS_DEFAULTS.bladeBendMax },
42
+ { type: "range", key: "tuftRadius", label: "tuft radius", min: 0, max: 0.6, step: 0.01, default: GRASS_DEFAULTS.tuftRadius, unit: "m" },
43
+ { type: "range", key: "normalLift", label: "normal lift", min: 0, max: 1, step: 0.01, default: GRASS_DEFAULTS.normalLift },
32
44
  { type: "color", key: "colorBase", label: "base color", default: GRASS_DEFAULTS.colorBase },
33
45
  { type: "color", key: "colorTip", label: "tip color", default: GRASS_DEFAULTS.colorTip },
34
46
  { type: "range", key: "colorVariation", label: "color variation", min: 0, max: 1, step: 0.02, default: GRASS_DEFAULTS.colorVariation },
@@ -49,9 +61,19 @@ function metaString(meta, key, fallback) {
49
61
  }
50
62
  /** Parse a volume's `meta` into fully-defaulted grass rules. @internal */
51
63
  export function readGrassRules(meta) {
64
+ const widthMin = Math.max(0.005, metaNumber(meta, "bladeWidthMin", GRASS_DEFAULTS.bladeWidthMin));
65
+ const widthMax = Math.max(0.005, metaNumber(meta, "bladeWidthMax", GRASS_DEFAULTS.bladeWidthMax));
66
+ const bendMin = Math.max(0, Math.min(1, metaNumber(meta, "bladeBendMin", GRASS_DEFAULTS.bladeBendMin)));
67
+ const bendMax = Math.max(0, Math.min(1, metaNumber(meta, "bladeBendMax", GRASS_DEFAULTS.bladeBendMax)));
52
68
  return {
53
69
  density: Math.max(0, metaNumber(meta, "density", GRASS_DEFAULTS.density)),
54
70
  bladeHeight: Math.max(0.05, metaNumber(meta, "bladeHeight", GRASS_DEFAULTS.bladeHeight)),
71
+ bladeWidthMin: Math.min(widthMin, widthMax),
72
+ bladeWidthMax: Math.max(widthMin, widthMax),
73
+ bladeBendMin: Math.min(bendMin, bendMax),
74
+ bladeBendMax: Math.max(bendMin, bendMax),
75
+ tuftRadius: Math.max(0, metaNumber(meta, "tuftRadius", GRASS_DEFAULTS.tuftRadius)),
76
+ normalLift: Math.max(0, Math.min(1, metaNumber(meta, "normalLift", GRASS_DEFAULTS.normalLift))),
55
77
  colorBase: metaString(meta, "colorBase", GRASS_DEFAULTS.colorBase),
56
78
  colorTip: metaString(meta, "colorTip", GRASS_DEFAULTS.colorTip),
57
79
  colorVariation: metaNumber(meta, "colorVariation", GRASS_DEFAULTS.colorVariation),
@@ -53,7 +53,7 @@ export interface TerrainMaterialLayer {
53
53
  tiling?: number;
54
54
  /** Project the material on all three axes to hide stretching on cliffs. */
55
55
  triplanar?: boolean;
56
- /** Hex color multiplied over the surface color. */
56
+ /** Authored hex color replacing the default surface palette color in editor and runtime. */
57
57
  tint?: string;
58
58
  /** Layer master opacity 0..1 — scales this layer's blend weight everywhere. */
59
59
  opacity?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jgengine/core",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "jgengine core — pure-TypeScript game framework SDK (npm @jgengine/core). Entity stores, commands, combat, inventory, multiplayer seams. Zero dependencies. Not an ECS. Agent skills ship in the tarball. https://jgengine.com",
5
5
  "keywords": [
6
6
  "jgengine",
@@ -11,6 +11,8 @@ Own the intended player experience and the rules that produce it: fantasy, pilla
11
11
 
12
12
  Use `level-design` for spatial realization; route implementation to `jgengine-gameplay`, `jgengine-world`, `jgengine-combat`, `jgengine-ui`, `jgengine-multiplayer` once intent is coherent.
13
13
 
14
+ For a substantial existing-game quality pass spanning concept, depth, world and presentation, use `game-improvement` to sequence these owners and keep a per-game implementation plan and acceptance evidence.
15
+
14
16
  For a greenfield build, read [references/application-playbook.md](references/application-playbook.md) and produce its greenfield contract (promise, pillars, anti-pillars, core decision, and falsifier) before writing code. Audits may read only the playbook's audit sections. Read [references/field-guide.md](references/field-guide.md) when the task needs deeper loop, economy, progression, genre, accessibility, or research guidance.
15
17
 
16
18
  ## Choose the mode
@@ -53,6 +53,7 @@ Load only rows the target needs. Do not preload every domain.
53
53
  | Need | Skill | Owner |
54
54
  | --- | --- | --- |
55
55
  | player promise, pillars, verbs, loops, economy, progression, balance | `game-design` | experience and rule intent |
56
+ | substantial existing-game improvement across depth, world and presentation | `game-improvement` | ordered improvement plan and per-game completion |
56
57
  | metrics, topology, encounters, navigation, spatial pacing | `level-design` | playable space intent |
57
58
  | scene placement, terrain painting, paths, asset authoring | `jgengine-editor` | editor document and authoring tools |
58
59
  | runtime world, movement, input, interaction, AI, navigation, audio | `jgengine-world` | spatial simulation and world runtime |
@@ -6,7 +6,7 @@
6
6
 
7
7
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
8
8
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
9
- - `VERSION` (const): const VERSION: "0.21.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
9
+ - `VERSION` (const): const VERSION: "0.22.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
10
10
 
11
11
  ## @jgengine/core/authoring
12
12
 
@@ -232,7 +232,7 @@
232
232
 
233
233
  - `CHANGELOG` (const): const CHANGELOG: Record<string, ChangelogEntry> — Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`).
234
234
  - `ChangelogEntry` (interface): interface ChangelogEntry — One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md).
235
- - `VERSION` (const): const VERSION: "0.21.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
235
+ - `VERSION` (const): const VERSION: "0.22.0" — Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating.
236
236
 
237
237
  ## @jgengine/core/runtime/adapter
238
238
 
@@ -3403,7 +3403,7 @@
3403
3403
  - `TerrainFlattenMask` (interface): interface TerrainFlattenMask { center: EnvironmentVec2; radius: number; height?: number; falloff?: number } — ⚠ undocumented · used by `padFlattenMasks`: Derives implicit `TerrainFlattenMask`s carving each pad's footprint into the terrain beneath it.
3404
3404
  - `TerrainIslandConfig` (interface): interface TerrainIslandConfig extends TerrainEnvironmentConfig — A bounded terrain patch floating at its own altitude — sky islands, arena platforms, split landmasses with void between.
3405
3405
  - `TerrainIslandDescriptor` (type): type TerrainIslandDescriptor = Omit<TerrainEnvironmentDescriptor, "kind"> & { kind: "island"; origin: EnvironmentVec2; } — ⚠ undocumented
3406
- - `TerrainMaterial` (type): type TerrainMaterial = "grass" | "sand" | "snow" | "rock" | "ash" | "highland" | "slate" — ⚠ undocumented · used by `TERRAIN_MATERIALS` (@jgengine/editor): The default terrain paint palette (surface id → color) shared by the panel and the mesh.
3406
+ - `TerrainMaterial` (type): type TerrainMaterial = "grass" | "sand" | "snow" | "rock" | "ash" | "highland" | "slate" — ⚠ undocumented · used by `TERRAIN_MATERIALS` (@jgengine/core/editor): The default terrain paint palette (surface id → color) shared by the panel and the mesh.
3407
3407
  - `TerrainMaterialMaps` (interface): interface TerrainMaterialMaps — PBR map URLs for a real ground texture — the same shape `buildMaterialCatalog({ basePath }).resolve(id)!.maps` from `@jgengine/assets` returns. Kept dependency-free here so `core` never imports the assets package; any URLs (pulled maps, a CDN, a data URI) satisfy it. Omitted roughness keeps the terrain scalar; omitted AO skips occlusion sampling.
3408
3408
  - `TerrainMaterialRegion` (type): type TerrainMaterialRegion = TerrainCircleRegion | TerrainPolylineRegion | TerrainRectRegion — A palette zone painted over the base terrain palette. Circle (the default when no `shape` is given), `polyline` ribbons for roads/rivers, and rotatable `rect` districts all paint fully inside their core and blend back across `falloff`; later regions in the list win overlaps.
3409
3409
  - `TerrainPolylineRegion` (interface): interface TerrainPolylineRegion extends TerrainRegionStyle — A ribbon palette zone following a centerline — roads and rivers, instead of chaining overlapping circles.
@@ -28,6 +28,21 @@ export const overworld = world({
28
28
 
29
29
  ## Legacy world features
30
30
 
31
+ Authored terrain paint carried by `environmentContentFromDocument(document).sculpt` renders on
32
+ the normal environment ground. Single surface IDs use `TERRAIN_MATERIAL_COLORS` from
33
+ `@jgengine/core/editor/terrainMaterials`; a layer's `tint` replaces that default color. Layer
34
+ weights are multiplied by `opacity`, normalized, and blended in linear color space, matching
35
+ the editor. Unpainted cells retain the descriptor palette. Grass roots and soil patch borders
36
+ sample the same authored colors. Paint changes color, never ground height or collision.
37
+ Descriptor-wide terrain detail still overlays the painted ground. Per-layer `roughness`,
38
+ `tiling` and `triplanar` remain serialized authoring metadata; they do not select or configure
39
+ separate runtime materials.
40
+
41
+ Custom ground consumers can use `createTerrainPaintColorSampler` from `@jgengine/shell/terrain`
42
+ with a `TerrainPaintSource` (an editable terrain's layer/surface/weight channels). The returned
43
+ `TerrainPaintColorSampler` writes a linear `THREE.Color` and returns whether the point is painted;
44
+ false leaves the caller's fallback color unchanged. The normal ground wires this automatically.
45
+
31
46
  Descriptors from `@jgengine/core/world/features` — legacy code-declared world shapes kept for
32
47
  existing games. Do not start new worlds here; `environment()` is now the consumption target for
33
48
  editor/preset-written scene data (`environmentContentFromDocument`), not a world definition: