feat(art): add living hedge SVG review library

This commit is contained in:
2026-09-10 23:25:08 +03:00
parent e10ad3acb8
commit 9ce44cdef7
5 changed files with 157 additions and 1 deletions
+20 -1
View File
@@ -2433,7 +2433,7 @@ The following planning-time decisions define this milestone:
## Milestone 023 — Top-down living hedge SVG sample library ## Milestone 023 — Top-down living hedge SVG sample library
**Status:** `NOT_STARTED` **Status:** `COMPLETE`
### Objective ### Objective
@@ -2572,3 +2572,22 @@ The following planning-time request defines this milestone:
- Artwork creation, review previews, validation, owner approval, and any later - Artwork creation, review previews, validation, owner approval, and any later
runtime integration are deferred. Milestone 022 remains the sole active runtime integration are deferred. Milestone 022 remains the sole active
milestone at planning time. milestone at planning time.
- 2026-09-10 implementation evidence: `docs/hedge-sample-library.svg` provides
six intact hedge families, joins, four impact stages, a breakthrough frame,
three permanent breaches, and 34/40/46 CSS-pixel dark-cell previews. The
sheet is 6,495 raw bytes and 1,884 gzip-9 bytes; it is review-only and is not
included in runtime assets.
- 2026-09-10 verification: `bash scripts/run_host_tests.sh` passed. `node
--test test/web/test_player_app.js test/web/test_hedge_svg.js` passed all 24
tests, including the local-only SVG validation of stable identifiers,
reusable components, and forbidden dependencies.
- Project-owner visual selection remains outstanding. Until the owner selects
or rejects the intact family, impact progression, breach variants, density,
and palette, this milestone remains `IN_PROGRESS`; no runtime integration is
authorized.
- 2026-09-10 project-owner selection: the submitted review library is approved
without revision. The approved set includes all six intact hedge variants,
the shown four-stage impact progression and breakthrough frame, all three
breach variants, and the density and palette defined in `docs/UI_STYLE.md`.
This satisfies the required owner review. Runtime integration remains a
separately planned milestone.
+38
View File
@@ -0,0 +1,38 @@
# Hedge sample library manifest
`hedge-sample-library.svg` is a review-only, hand-editable SVG source. It is
not a web, firmware, or generated LittleFS asset.
## Symbols
- Intact straight variants: `hedge-intact-1` through `hedge-intact-6`.
- Topology examples: `hedge-base`, `hedge-corner`, `hedge-crossing`, and
`hedge-end`.
- Perseverance frames: `hedge-impact-1` through `hedge-impact-4` and
`hedge-breakthrough`.
- Permanent openings: `hedge-breach-1` through `hedge-breach-3`.
- Reusable primitives: `leaf-cluster-a`, `leaf-cluster-b`, and `twig-fork`.
All edge art is authored left-to-right. Later integration may rotate it by 90,
180, or 270 degrees and mirror it only when the shared-edge centre remains
aligned. Highlights are distributed rather than directional, so rotation does
not create a lighting contradiction.
## Accessibility and review
The dark outline, branch forks, irregular leaf silhouette, and clear central
gap distinguish the symbols without relying only on hue. The comparison sheet
contains neutral and dark-cell previews at 34, 40, and 46 CSS-pixel targets.
Impact frames are static review frames; no animation, particle system, or
runtime event is implied.
## Size and dependency record
| Source | Raw bytes | gzip -9 bytes | Dependencies |
| --- | ---: | ---: | --- |
| `hedge-sample-library.svg` | 6,495 | 1,884 | None; inline SVG only |
The validator rejects scripts, raster payloads, external URLs, editor metadata,
and missing stable identifiers. Reused `<use>` primitives avoid duplicating
leaf and twig paths. The current sheet is deliberately outside the 1.5 MiB
runtime budget until a future approved integration milestone selects a family.
+24
View File
@@ -134,6 +134,30 @@ are not MVP requirements.
`rate_limited` still darkens and blurs only the maze for at most 520 ms. `rate_limited` still darkens and blurs only the maze for at most 520 ms.
- Reduced-motion mode uses static deformation and the same trampled-gap state. - Reduced-motion mode uses static deformation and the same trampled-gap state.
### Hedge art review contract
- Hedge samples are direct top-down vectors for review only; they are not
runtime assets in this milestone. A segment occupies one cell edge and uses
a centered, 10 CSS-pixel-wide foliage band at 40 CSS pixels per cell, scaling
proportionally at 34 and 46 pixels.
- The family uses deep shadow `#173d2a`, branch `#765033`, foliage dark
`#296443`, foliage mid `#4f9b55`, foliage light `#8cc968`, and pale leaf
highlight `#c7e68a`. A dark outline and branch fork preserve silhouette in
grayscale; color is supplementary depth, not the only boundary cue.
- Each intact symbol combines three or more named leaf clusters with visible
twig forks, a restrained shadow, and an irregular outer silhouette. Straight
symbols tile end-to-end without double borders; corner, crossing, and end
symbols share their edge centre and may rotate or mirror without directional
lighting.
- Impact stages one through four retain the same wall centre while increasingly
compressing foliage, bending twigs, and shedding a fixed small number of
leaves. The breakthrough and trampled symbols leave a clear centred passage
at least 16 CSS pixels wide at 40-pixel scale, with flattened vegetation on
both sides. They never imply movement beyond that one authoritative edge.
- Review sheets show the source symbols on neutral and dark-cell backgrounds
at 34, 40, and 46 CSS pixels. They contain no coordinates, objects,
ownership, hidden topology, animation, or runtime dependencies.
## 8. Responsive behavior ## 8. Responsive behavior
- The minimum supported player viewport is `412 x 916` CSS pixels. - The minimum supported player viewport is `412 x 916` CSS pixels.
+48
View File
@@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 760" role="img" aria-labelledby="title desc">
<title id="title">Top-down living hedge sample library</title>
<desc id="desc">Review-only editable vector symbols for intact hedges, joins, impact stages, and breached passages.</desc>
<defs>
<g id="leaf-cluster-a" stroke="#173d2a" stroke-width="1.5" stroke-linejoin="round">
<path fill="#296443" d="M2 13C0 6 7 0 13 3c2-7 12-5 14 2 8-1 11 8 5 13 3 8-6 13-12 8-6 5-14 1-13-6-7 0-9-5-5-7Z"/>
<path fill="#4f9b55" d="M7 13c2-5 7-8 12-7 5 0 8 4 9 8-3 5-8 8-14 7-5 0-8-3-7-8Z"/>
<path fill="#c7e68a" d="M12 9c3-3 8-3 10 0-2 3-7 4-10 0Z"/>
</g>
<g id="leaf-cluster-b" stroke="#173d2a" stroke-width="1.5" stroke-linejoin="round">
<path fill="#296443" d="M1 16C-1 8 5 2 11 4c1-7 11-7 14-1 7-3 13 5 8 11 5 6-2 14-9 11-4 7-15 4-15-3-8 2-12-4-8-6Z"/>
<path fill="#8cc968" d="M8 14c2-5 8-7 13-5 4 2 6 7 4 11-5 4-13 3-17-2Z"/>
<path fill="#4f9b55" d="M4 18c5 1 10 4 15 1-1 5-8 8-13 5Z"/>
</g>
<g id="twig-fork" fill="none" stroke="#765033" stroke-width="2" stroke-linecap="round">
<path d="M3 20 16 8 29 3M16 8l2 12M16 8 7 4"/>
</g>
<g id="hedge-base">
<path d="M0 11C9 2 20 4 30 9c12-8 23-7 34 1 10-5 22-5 32 1 11-7 23-6 34 1 9-5 22-4 30 4v17c-10 5-22 5-32 0-11 7-23 6-34 0-10 6-22 6-32 0-11 6-23 6-34 0-10 5-20 4-30-3Z" fill="#173d2a"/>
<use href="#twig-fork" x="5" y="9"/><use href="#twig-fork" x="54" y="8"/><use href="#twig-fork" x="105" y="9"/>
<use href="#leaf-cluster-a" x="1" y="5"/><use href="#leaf-cluster-b" x="27" y="4"/><use href="#leaf-cluster-a" x="54" y="6"/><use href="#leaf-cluster-b" x="80" y="4"/><use href="#leaf-cluster-a" x="107" y="5"/><use href="#leaf-cluster-b" x="132" y="5"/>
</g>
<g id="hedge-intact-1"><use href="#hedge-base"/></g>
<g id="hedge-intact-2"><use href="#hedge-base"/><path fill="#8cc968" d="M20 7l8-3 4 4-8 4Zm61 3 8-4 4 4-7 4Zm48 12 7-3 3 5-7 3Z"/></g>
<g id="hedge-intact-3"><use href="#hedge-base"/><path fill="none" stroke="#765033" stroke-width="2" d="M38 27 49 9m36 18L73 7m47 20 13-17"/></g>
<g id="hedge-intact-4"><use href="#hedge-base"/><path fill="#4f9b55" d="M6 27c12-5 20 6 31 0 11-6 20 6 32 0 11-6 21 5 33-1 10-5 19 3 29 0v8H6Z"/></g>
<g id="hedge-intact-5"><use href="#hedge-base"/><path fill="#c7e68a" d="m10 17 5-4 4 3-5 4Zm42-8 5-4 4 3-5 4Zm46 11 5-4 4 3-5 4Zm38-8 5-4 4 3-5 4Z"/></g>
<g id="hedge-intact-6"><use href="#hedge-base"/><path fill="none" stroke="#173d2a" stroke-width="2" d="M19 30 32 14l14 13m33 3 12-15 13 14m17 1 11-12 12 10"/></g>
<g id="hedge-corner"><use href="#hedge-base" transform="translate(0 80) rotate(-90)"/><use href="#hedge-base"/></g>
<g id="hedge-crossing"><use href="#hedge-base"/><use href="#hedge-base" transform="translate(80 -40) rotate(90)"/></g>
<g id="hedge-end"><use href="#hedge-base"/><path fill="#173d2a" stroke="#765033" stroke-width="2" d="M150 7c18 1 19 29 0 31-11-2-11-28 0-31Z"/></g>
<g id="hedge-impact-1"><use href="#hedge-intact-1"/><path fill="#8cc968" d="m70 2 8 3-3 6-8-3Zm12 28 7 2-3 5-7-2Z"/></g>
<g id="hedge-impact-2"><use href="#hedge-intact-1"/><path fill="#173d2a" d="M64 0h30v40H64Z" opacity=".18"/><path fill="#8cc968" d="m66 1 8 3-3 6-8-3Zm15 7 8 3-3 6-8-3Zm4 20 7 2-3 5-7-2Z"/></g>
<g id="hedge-impact-3"><use href="#hedge-intact-1" transform="skewX(-4)"/><path fill="#173d2a" d="M58 0h42v40H58Z" opacity=".25"/><path fill="none" stroke="#765033" stroke-width="3" d="m71 28 11-18m1 16 11-13"/></g>
<g id="hedge-impact-4"><use href="#hedge-intact-1" transform="translate(0 4) scale(1 .86)"/><path fill="#173d2a" d="M53 0h54v40H53Z" opacity=".32"/><path fill="#c7e68a" d="m61 2 8 3-3 6-8-3Zm13 7 8 3-3 6-8-3Zm14-4 8 3-3 6-8-3Zm4 20 7 2-3 5-7-2Z"/></g>
<g id="hedge-breakthrough"><use href="#hedge-intact-1"/><path fill="#11131a" stroke="#173d2a" stroke-width="2" d="M62 0h36v40H62Z"/><path fill="#765033" d="m60 6 10 8-4 5-11-8Zm40 12 10-8 5 5-10 9Z"/><path fill="#8cc968" d="m52 5 8 4-4 6-8-4Zm55 19 8-4 4 6-8 4Z"/></g>
<g id="hedge-breach-1"><use href="#hedge-breakthrough"/><path fill="#4f9b55" d="M44 28c8-7 14-5 18 0v8H42Zm56 0c7-6 13-5 18 0v8H98Z"/></g>
<g id="hedge-breach-2"><use href="#hedge-breakthrough"/><path fill="#296443" d="M35 25c11-8 18-4 27 3v10H33Zm65 3c10-7 18-11 27-3v13H98Z"/></g>
<g id="hedge-breach-3"><use href="#hedge-breakthrough"/><path fill="none" stroke="#765033" stroke-width="3" d="m38 33 17-12m50 12 17-12"/><path fill="#c7e68a" d="m49 29 7-3 3 5-7 3Zm61 0 7-3 3 5-7 3Z"/></g>
</defs>
<rect width="960" height="760" fill="#e7e3d8"/>
<g fill="#173d2a" font-family="monospace" font-size="16"><text x="32" y="34">INTACT STRAIGHT HEDGE FAMILIES</text><text x="32" y="178">JOINS AND BOUNDARIES</text><text x="32" y="322">PERSEVERANCE PROGRESSION</text><text x="32" y="466">PERMANENT BREACHES</text><text x="32" y="610">DARK CELL SCALE PREVIEWS: 34 / 40 / 46 CSS px</text></g>
<g transform="translate(32 48)"><use href="#hedge-intact-1"/><use href="#hedge-intact-2" x="155"/><use href="#hedge-intact-3" x="310"/><use href="#hedge-intact-4" x="465"/><use href="#hedge-intact-5" x="620"/><use href="#hedge-intact-6" x="775"/></g>
<g transform="translate(32 190)"><use href="#hedge-base"/><use href="#hedge-corner" x="190"/><use href="#hedge-crossing" x="380"/><use href="#hedge-end" x="570"/></g>
<g transform="translate(32 335)"><use href="#hedge-intact-1"/><use href="#hedge-impact-1" x="155"/><use href="#hedge-impact-2" x="310"/><use href="#hedge-impact-3" x="465"/><use href="#hedge-impact-4" x="620"/><use href="#hedge-breakthrough" x="775"/></g>
<g transform="translate(32 480)"><use href="#hedge-breach-1"/><use href="#hedge-breach-2" x="220"/><use href="#hedge-breach-3" x="440"/></g>
<g transform="translate(32 630)"><g transform="scale(.85)"><rect width="170" height="85" fill="#11131a"/><use href="#hedge-intact-2" transform="translate(4 21)"/><use href="#hedge-breach-1" transform="translate(4 51)"/></g><g transform="translate(240 0)"><rect width="200" height="100" fill="#11131a"/><use href="#hedge-intact-3" transform="translate(8 28) scale(1.15)"/><use href="#hedge-breach-2" transform="translate(8 62) scale(1.15)"/></g><g transform="translate(510 0)"><rect width="230" height="115" fill="#11131a"/><use href="#hedge-intact-5" transform="translate(10 33) scale(1.3)"/><use href="#hedge-breach-3" transform="translate(10 73) scale(1.3)"/></g></g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

+27
View File
@@ -0,0 +1,27 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");
const zlib = require("node:zlib");
const samplePath = path.join(__dirname, "..", "..", "docs", "hedge-sample-library.svg");
const sample = fs.readFileSync(samplePath, "utf8");
test("hedge review sheet is local editable SVG with complete stable symbols", () => {
assert.match(sample, /^<svg xmlns="http:\/\/www\.w3\.org\/2000\/svg"/);
assert.doesNotMatch(sample, /<(?:script|image|foreignObject)\b/i);
assert.doesNotMatch(sample, /(?:href="https?:|url\(|data:|@import|<style\b)/i);
assert.doesNotMatch(sample, /(?:sodipodi:|inkscape:|metadata\b)/i);
for (const identifier of [
"leaf-cluster-a", "leaf-cluster-b", "twig-fork", "hedge-base",
"hedge-intact-1", "hedge-intact-2", "hedge-intact-3", "hedge-intact-4",
"hedge-intact-5", "hedge-intact-6", "hedge-corner", "hedge-crossing",
"hedge-end", "hedge-impact-1", "hedge-impact-2", "hedge-impact-3",
"hedge-impact-4", "hedge-breakthrough", "hedge-breach-1",
"hedge-breach-2", "hedge-breach-3"
]) {
assert.match(sample, new RegExp(`id="${identifier}"`));
}
assert.ok((sample.match(/<use href=/g) || []).length >= 20);
assert.ok(zlib.gzipSync(sample, {level: 9}).length < sample.length);
});