restored 4 corner sigils
This commit is contained in:
@@ -58,7 +58,7 @@ describe('buildCybersigil', () => {
|
||||
const dense = buildCybersigil({ count: 9, rng: seeded(5) });
|
||||
const n = (s: string) => (s.match(/<path/g) ?? []).length;
|
||||
expect(n(dense)).toBeGreaterThan(n(sparse));
|
||||
expect(n(dense)).toBeLessThanOrEqual(440); // MAX_PATHS(200) mirrored + ornaments
|
||||
expect(n(dense)).toBeLessThanOrEqual(260); // MAX_PATHS(110) mirrored + ornaments
|
||||
});
|
||||
|
||||
it('ships a non-empty glyph library with valid path data', () => {
|
||||
|
||||
@@ -36,25 +36,11 @@ export interface SigilOptions {
|
||||
count?: number;
|
||||
/** injectable RNG (0..1); default Math.random */
|
||||
rng?: () => number;
|
||||
/**
|
||||
* Spine sinuosity. Default `1` keeps the original gentle one-sided bow
|
||||
* (byte-identical output). Values >1 make the spine weave inward and
|
||||
* back (staying ≥0 so it never crosses the edge clip) with amplitude
|
||||
* ×spineWave — a serpentine vine for tall edge ribbons.
|
||||
*/
|
||||
spineWave?: number;
|
||||
/**
|
||||
* Edge mode: crop the viewBox to the inward half (spine pinned at x=0)
|
||||
* and emit a single half with `preserveAspectRatio="none"`, so the
|
||||
* figure stretches to fill a tall narrow border ribbon instead of
|
||||
* scaling uniformly and overflowing it.
|
||||
*/
|
||||
edge?: boolean;
|
||||
}
|
||||
|
||||
const H = 200; // internal canvas height (viewBox scales it to fit)
|
||||
const PAD = 14;
|
||||
const MAX_PATHS = 200; // safety ceiling; real density is bounded by the params
|
||||
const MAX_PATHS = 110; // safety ceiling; real density is bounded by the params
|
||||
|
||||
type Pt = [number, number];
|
||||
|
||||
@@ -78,7 +64,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
||||
const emit = (d: string, cls: string) => {
|
||||
if (strokeCount >= MAX_PATHS) return;
|
||||
parts.push(
|
||||
`<path class="${cls}" d="${d}" pathLength="1" style="--i:${strokeCount % 40}"/>`,
|
||||
`<path class="${cls}" d="${d}" pathLength="1" style="--i:${strokeCount % 16}"/>`,
|
||||
);
|
||||
strokeCount++;
|
||||
};
|
||||
@@ -129,7 +115,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
||||
track(at[0] + g.w * s);
|
||||
parts.push(
|
||||
`<g transform="translate(${n(at[0])} ${n(at[1])}) rotate(${n(deg)}) scale(${n(s)})">` +
|
||||
`<path class="cs-sig-orn" d="${g.d}" pathLength="1" style="--i:${strokeCount % 40}"/></g>`,
|
||||
`<path class="cs-sig-orn" d="${g.d}" pathLength="1" style="--i:${strokeCount % 16}"/></g>`,
|
||||
);
|
||||
strokeCount++;
|
||||
};
|
||||
@@ -139,7 +125,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
||||
// with a motif.
|
||||
const limb = (ox: number, oy: number, ang: number, scale: number, depth: number) => {
|
||||
if (strokeCount >= MAX_PATHS) return;
|
||||
const L = scale * rnd(50, 92);
|
||||
const L = scale * rnd(34, 64);
|
||||
const dx = Math.cos(ang) * L;
|
||||
const dy = Math.sin(ang) * L;
|
||||
const peak: Pt = [ox + dx, oy + dy];
|
||||
@@ -157,7 +143,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
||||
emit(spline(pts), 'cs-sig-main');
|
||||
|
||||
// terminal spike off the outermost point
|
||||
barb(peak, ang + rnd(-0.5, 0.5), scale * rnd(12, 26));
|
||||
barb(peak, ang + rnd(-0.5, 0.5), scale * rnd(8, 18));
|
||||
|
||||
// filament shadow trailing the main sweep
|
||||
if (rng() < 0.4) {
|
||||
@@ -183,7 +169,7 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
||||
const tt = seg[2] as number;
|
||||
const base: Pt = [a[0] + (b[0] - a[0]) * tt, a[1] + (b[1] - a[1]) * tt];
|
||||
const side = k % 2 ? 1 : -1;
|
||||
barb(base, ang + side * rnd(0.6, 1.3), scale * rnd(9, 22));
|
||||
barb(base, ang + side * rnd(0.6, 1.3), scale * rnd(6, 16));
|
||||
}
|
||||
|
||||
// recurse — one child curls off the mid/peak region
|
||||
@@ -202,39 +188,16 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
||||
if (depth === 0 && rng() < 0.3) ornament(peak, ang);
|
||||
};
|
||||
|
||||
// ── Wavering spine: a curve from top to bottom. Default gently bows in
|
||||
// +x; spineWave>1 makes it weave side-to-side (a serpentine edge vine).
|
||||
const sw = opts.spineWave ?? 1;
|
||||
// ── Wavering spine: a curve from top to bottom, gently bowing in +x.
|
||||
const spineNodes = 5 + Math.floor(rng() * 3);
|
||||
const spinePts: Pt[] = [];
|
||||
for (let i = 0; i <= spineNodes; i++) {
|
||||
const y = (H * i) / spineNodes;
|
||||
const x =
|
||||
i === 0 || i === spineNodes
|
||||
? 0
|
||||
: sw === 1
|
||||
? rnd(0, 11) // unchanged default — center/corner/plate sigils
|
||||
: i % 2
|
||||
? rnd(2, 8) // near the edge
|
||||
: rnd(9, 15) * sw; // inward bulge — one-sided weave, never < 0
|
||||
const x = i === 0 || i === spineNodes ? 0 : rnd(0, 11);
|
||||
spinePts.push([x, y]);
|
||||
}
|
||||
emit(spline(spinePts), 'cs-sig-main');
|
||||
|
||||
// ── Terminal spike crown: long, straight, downward-converging needles
|
||||
// off the spine tail — the dripping barbed point that ends the growth.
|
||||
const tip = spinePts[spinePts.length - 1];
|
||||
const fan = 4 + Math.floor(rng() * 3); // 4–6
|
||||
for (let s = 0; s < fan; s++) {
|
||||
const spread = (s / (fan - 1) - 0.5) * 1.0; // ~±0.5 rad — tighter dagger
|
||||
const a = Math.PI / 2 + spread; // π/2 = +y (down)
|
||||
const len = rnd(64, 104) * (1 - Math.abs(spread) * 0.35); // center longest
|
||||
const base: Pt = [tip[0], tip[1] - rnd(0, 8)];
|
||||
const tt: Pt = [base[0] + Math.cos(a) * len, base[1] + Math.sin(a) * len];
|
||||
emit(`M${n(base[0])} ${n(base[1])}L${n(tt[0])} ${n(tt[1])}`, 'cs-sig-barb');
|
||||
track(tt[0]);
|
||||
}
|
||||
|
||||
// ── Branch nodes ride the spine and throw limbs outward. Nodes are
|
||||
// inset from the very ends and spread the full height so growth flows
|
||||
// down the whole trunk rather than clumping at the top.
|
||||
@@ -249,30 +212,24 @@ export function buildCybersigil(opts: SigilOptions = {}): string {
|
||||
const node: Pt = [a[0] + (b[0] - a[0]) * sf, a[1] + (b[1] - a[1]) * sf];
|
||||
// later nodes lean downward so the lower trunk fills out
|
||||
const bias = -0.25 + tc * 0.7;
|
||||
// spike gradient: lower trunk grows longer, sharper barbs
|
||||
const spike = 1 + tc * 1.4; // 1× top → ~2.3× bottom
|
||||
const limbs = 1 + Math.floor(rng() * 2);
|
||||
for (let l = 0; l < limbs; l++) {
|
||||
const ang = bias + rnd(-0.55, 0.55);
|
||||
limb(node[0], node[1], ang, rnd(0.65, 1.05) * (0.8 + tc * 0.5), 1);
|
||||
limb(node[0], node[1], ang, rnd(0.65, 1.05), 1);
|
||||
}
|
||||
// the odd bare barb straight off the spine keeps the trunk prickly
|
||||
if (rng() < 0.55) barb(node, bias + rnd(-0.3, 0.3), rnd(11, 20) * spike);
|
||||
if (rng() < 0.55) barb(node, bias + rnd(-0.3, 0.3), rnd(7, 14));
|
||||
}
|
||||
|
||||
const half = parts.join('');
|
||||
// Edge mode crops to the inward half (spine at x=0) and lets the ribbon
|
||||
// stretch it (none); normal mode is symmetric and uniformly fitted.
|
||||
const minX = opts.edge ? 0 : -(maxX + PAD);
|
||||
const vbW = opts.edge ? maxX + PAD : 2 * (maxX + PAD);
|
||||
const par = opts.edge ? 'none' : 'xMidYMid meet';
|
||||
const body = opts.edge
|
||||
? `<g class="cs-sig-half">${half}</g>`
|
||||
: `<g class="cs-sig-half">${half}</g>` +
|
||||
`<g class="cs-sig-half" transform="scale(-1 1)">${half}</g>`;
|
||||
const minX = -(maxX + PAD);
|
||||
const vbW = 2 * (maxX + PAD);
|
||||
return (
|
||||
`<svg class="cs-sigil" viewBox="${n(minX)} ${-PAD} ${n(vbW)} ${H + 2 * PAD}" ` +
|
||||
`preserveAspectRatio="${par}" aria-hidden="true" focusable="false" ` +
|
||||
`xmlns="http://www.w3.org/2000/svg">${body}</svg>`
|
||||
`preserveAspectRatio="xMidYMid meet" aria-hidden="true" focusable="false" ` +
|
||||
`xmlns="http://www.w3.org/2000/svg">` +
|
||||
`<g class="cs-sig-half">${half}</g>` +
|
||||
`<g class="cs-sig-half" transform="scale(-1 1)">${half}</g>` +
|
||||
`</svg>`
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user