more performance

This commit is contained in:
Chris Amow 2026-08-17 01:03:28 -05:00
parent 90f8a1b567
commit 1ee2a96fc0
4 changed files with 131 additions and 9 deletions

View file

@ -19,12 +19,13 @@ passes after gap slots are inserted. The transparent-point production fix did
not visibly settle the report, so do not add another geometry patch from the
current theory.
Next investigation should compare the painted canvas pixels around the join
against the chart API's expected coordinates in the user's production viewport.
This is the same class of problem where self-consistent API coordinates once
missed a real overlay offset. Capture the asset commit, final three bar times,
their logical/x coordinates, the first two future coordinates, canonical line
prices, and page-pixel samples from the rendered line before changing code.
Painted-pixel diagnostics are now implemented: diagnostic mode samples the
internal canvases around each join by the line's RGB colour and reports painted
historical/future slopes beside canonical and API-coordinate slopes. A browser
regression confirms actual canvas pixels stay continuous in the deterministic
sparse-tail case. The remaining step is a production capture with those painted
values; do not change geometry again until it says whether production pixels or
production coordinates diverge.
### Load bars from the visible window

View file

@ -1075,6 +1075,15 @@ shared scale. The time-scale owner now uses transparent zero-valued points with
`autoscaleInfoProvider` that returns null. They remain invisible and cannot affect
price range, but Lightweight Charts retains their timestamps as real series data.
The next diagnostic step measures painted output rather than trusting the chart
API's coordinate system. In diagnostic mode the readout now samples pixels from
the internal canvases on both sides of the live-edge join, matching each manual
line's configured RGB colour near its expected path. It reports those painted
slopes alongside canonical price changes and API-coordinate slopes. This is
deliberate: a prior overlay bug produced internally consistent coordinates while
the pixels were visibly displaced. A deterministic browser test requires actual
historical and future line pixels to exist and keep the same slope.
### 2026-08-15 — 1m zoom-out died at ~1am because the socket sent 1,000 bars
Compressing the 1m time scale stopped around 01:00, then looked empty.

View file

@ -10,6 +10,7 @@ class ConfluenceChart {
this.currentPriceLine = null;
this.currentPricePulse = null;
this.projectionDiagnostic = null;
this.projectionDiagnosticFrame = null;
this.animateCurrentPrice = true;
this.autoScrollLivePrice = true;
this.lastCurrentPrice = null;
@ -2146,10 +2147,70 @@ class ConfluenceChart {
.find(point => Number(point.time) === future.time)?.value;
return { level, previous, current, next, series, historical: slope(previous, current), future: slope(current, next) };
}).filter(row => row.current.y != null && row.current.y >= 0 && row.current.y <= plotHeight);
this.projectionDiagnostic.textContent = lines.map(row =>
`#${row.level.number} ${row.level.tf} dp ${((row.current.p ?? 0) - (row.previous.p ?? 0)).toFixed(3)}/${((row.next.p ?? 0) - (row.current.p ?? 0)).toFixed(3)} px ${(row.historical ?? 0).toFixed(3)}/${(row.future ?? 0).toFixed(3)} ${row.series == null ? 'MISSING' : 'OK'}`,
).join('\n');
const text = (row, painted = null) =>
`#${row.level.number} ${row.level.tf} dp ${((row.current.p ?? 0) - (row.previous.p ?? 0)).toFixed(3)}/${((row.next.p ?? 0) - (row.current.p ?? 0)).toFixed(3)} px ${(row.historical ?? 0).toFixed(3)}/${(row.future ?? 0).toFixed(3)} paint ${painted ? `${painted.historical?.toFixed(3) ?? '?'}/${painted.future?.toFixed(3) ?? '?'}` : '?/?'} ${row.series == null ? 'MISSING' : 'OK'}`;
this.projectionDiagnostic.textContent = lines.map(row => text(row)).join('\n');
this.projectionDiagnostic.hidden = !lines.length;
if (this.projectionDiagnosticFrame != null) cancelAnimationFrame(this.projectionDiagnosticFrame);
this.projectionDiagnosticFrame = requestAnimationFrame(() => {
this.projectionDiagnosticFrame = null;
this.projectionDiagnostic.textContent = lines.map(row => text(row, {
historical: this.paintedSlopeBetween(row.previous, row.current, ConfluenceChart.levelColor(row.level)),
future: this.paintedSlopeBetween(row.current, row.next, ConfluenceChart.levelColor(row.level)),
})).join('\n');
});
}
paintedSlopeBetween(first, second, color) {
if ([first.x, first.y, second.x, second.y].some(value => value == null)) return null;
const at = fraction => ({
x: first.x + (second.x - first.x) * fraction,
y: first.y + (second.y - first.y) * fraction,
});
const a = at(0.25);
const b = at(0.75);
const y1 = this.paintedYAt(a.x, a.y, color);
const y2 = this.paintedYAt(b.x, b.y, color);
return y1 == null || y2 == null ? null : (y2 - y1) / (b.x - a.x);
}
paintedYAt(x, expectedY, color) {
const match = /^#([0-9a-f]{6})$/i.exec(color || '');
if (!match) return null;
const value = Number.parseInt(match[1], 16);
const target = [(value >> 16) & 255, (value >> 8) & 255, value & 255];
const plot = this.plotCanvas()?.getBoundingClientRect();
if (!plot) return null;
const pageX = plot.left + x;
const candidates = [];
for (const canvas of this.chartEl.querySelectorAll('canvas')) {
const rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height || pageX < rect.left || pageX > rect.right) continue;
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const pixelX = Math.round((pageX - rect.left) * scaleX);
const top = Math.max(0, Math.floor((plot.top + expectedY - 8 - rect.top) * scaleY));
const bottom = Math.min(canvas.height - 1, Math.ceil((plot.top + expectedY + 8 - rect.top) * scaleY));
if (bottom < top) continue;
try {
const image = canvas.getContext('2d').getImageData(
Math.max(0, pixelX - 1), top, Math.min(3, canvas.width - Math.max(0, pixelX - 1)), bottom - top + 1,
);
for (let offset = 0; offset < image.data.length; offset += 4) {
if (image.data[offset + 3] < 80) continue;
const distance = Math.hypot(
image.data[offset] - target[0], image.data[offset + 1] - target[1],
image.data[offset + 2] - target[2],
);
if (distance > 55) continue;
const row = Math.floor((offset / 4) / image.width);
candidates.push(rect.top - plot.top + (top + row) / scaleY);
}
} catch { /* An unreadable internal canvas contributes no measurement. */ }
}
if (!candidates.length) return null;
candidates.sort((a, b) => Math.abs(a - expectedY) - Math.abs(b - expectedY));
return candidates[0];
}
lineData(level) {

View file

@ -216,6 +216,57 @@ test('a short final data gap does not kink a future trendline',
});
});
test('painted canvas pixels keep the same slope through the live edge',
{ timeout: 180000 }, async () => {
await withChart(async page => {
await page.evaluate(() => {
const c = window.__chart;
const minute = 60;
const bars = Array.from({ length: 20 }, (_, index) => ({
tf: '1m', t: 200000 + index * minute, o: 100, h: 101, l: 99, c: 100,
v: 1, closed: true,
}));
bars.push({ ...bars.at(-1), t: bars.at(-1).t + 9 * minute });
const line = {
id: 'painted-tail', kind: 'manual', tf: '30m', side: 'support', label: 'paint',
anchor_t: bars[0].t, anchor_p: 99, last_t: bars[0].t + 30 * minute,
slope: 2 / (30 * minute), cutoff_t: null, hidden: false, color: '#65b7cf',
line_width: 3, provisional: false,
};
c.setBars(bars);
c.setTrendlineGeometry({
mode: 'source_tf',
series: { '30m': { times: Array.from({ length: 4 }, (_, i) => bars[0].t + i * 30 * minute), duration: 30 * minute } },
});
c.syncLevels([line]);
});
await page.waitForTimeout(200);
const result = await page.evaluate(() => {
const c = window.__chart;
const line = c.levels[0];
const last = c.bars.length - 1;
const futureTime = c.bars[last].t + 60;
const point = time => ({
x: c.chart.timeScale().timeToCoordinate(time),
y: c.candles.priceToCoordinate(c.linePriceAtIndex(line, c.indexAt(time))),
});
const previous = point(c.bars[last - 1].t);
const current = point(c.bars[last].t);
const future = point(futureTime);
return {
historical: c.paintedSlopeBetween(previous, current, line.color),
future: c.paintedSlopeBetween(current, future, line.color),
};
});
assert.notEqual(result.historical, null, 'historical canvas line pixels were not found');
assert.notEqual(result.future, null, 'future canvas line pixels were not found');
assert.ok(Math.abs(result.historical - result.future) < 0.08,
`painted line kinked from ${result.historical} to ${result.future}`);
assertNoPageErrors(page, assert);
});
});
test('a manual line series owns its future 5m slots without an SVG projection',
{ timeout: 180000 }, async () => {
await withChart(async page => {