# Tri-Dexel Engine: Yön Hatası + Performans

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** TripleDexelEngine'de takım yönüne göre doğru boşaltma (3 grid desteği) ve performans iyileştirmesi

**Architecture:** Mevcut XY-only dexel grid'i gerçek triple-dexel'e genişlet: dominant axis tespit → XY/YZ/XZ grid seçimi. Performans: dirty-region smoothing/AO, index caching, material singleton.

**Tech Stack:** TypeScript, Three.js, WebGPU (WGSL), Web Workers

---

## Dosya Haritası

| Dosya | Sorumluluk | Değişiklik |
|-------|-----------|------------|
| `TripleDexelEngine.ts` | CPU dexel motoru | YZ/XZ grid ekleme, processToolPosition yön fix |
| `dexel.worker.ts` | Worker thread motoru | processToolPosition yön fix, typed array perf |
| `DexelGPUEngine.ts` | WebGPU compute shader | I/J/K uniform ekle, shader yön fix |
| `fiko-similasyon-v1.component.ts` | Angular bileşen | GPU sample I/J geçirme |

Tüm dosyalar: `src/service/fikoyt-ui/src/lib/fiko-three-js/IFikoThreeJS/fiko-similasyon-v1/`

---

### Task 1: TripleDexelEngine — YZ/XZ Grid Altyapısı

**Files:**
- Modify: `TripleDexelEngine.ts:48-114` (class properties + constructor)
- Modify: `TripleDexelEngine.ts:116-125` (initializeStock)
- Modify: `TripleDexelEngine.ts:127-132` (reset)

- [ ] **Step 1: YZ/XZ grid property'lerini ekle**

`TripleDexelEngine.ts` satır 57-59 sonrasına:

```typescript
// YZ grid (resY × resZ cells, X-depth spans)
private yzData: Float32Array;
private yzSpanCounts: Uint8Array;

// XZ grid (resX × resZ cells, Y-depth spans)
private xzData: Float32Array;
private xzSpanCounts: Uint8Array;
```

- [ ] **Step 2: Constructor'da 3 grid allocate et**

`TripleDexelEngine.ts` satır 107-113:

```typescript
const totalCellsXY = this.resX * this.resY;
const totalCellsYZ = this.resY * this.resZ;
const totalCellsXZ = this.resX * this.resZ;

this.xyData = new Float32Array(totalCellsXY * MAX_SPANS * SPAN_SIZE);
this.xySpanCounts = new Uint8Array(totalCellsXY);
this.yzData = new Float32Array(totalCellsYZ * MAX_SPANS * SPAN_SIZE);
this.yzSpanCounts = new Uint8Array(totalCellsYZ);
this.xzData = new Float32Array(totalCellsXZ * MAX_SPANS * SPAN_SIZE);
this.xzSpanCounts = new Uint8Array(totalCellsXZ);

this.dirtyMask = new Uint8Array(totalCellsXY);
this.toolIdMap = new Uint16Array(totalCellsXY);
```

- [ ] **Step 3: initializeStock 3 grid**

```typescript
private initializeStock(): void {
  const { minX, maxX, minY, maxY, minZ, maxZ } = this.bbox;

  // XY grid: Z spans [minZ, maxZ]
  const xyCells = this.resX * this.resY;
  for (let i = 0; i < xyCells; i++) {
    const off = i * MAX_SPANS * SPAN_SIZE;
    this.xyData[off] = minZ;
    this.xyData[off + 1] = maxZ;
    this.xySpanCounts[i] = 1;
  }

  // YZ grid: X spans [minX, maxX]
  const yzCells = this.resY * this.resZ;
  for (let i = 0; i < yzCells; i++) {
    const off = i * MAX_SPANS * SPAN_SIZE;
    this.yzData[off] = minX;
    this.yzData[off + 1] = maxX;
    this.yzSpanCounts[i] = 1;
  }

  // XZ grid: Y spans [minY, maxY]
  const xzCells = this.resX * this.resZ;
  for (let i = 0; i < xzCells; i++) {
    const off = i * MAX_SPANS * SPAN_SIZE;
    this.xzData[off] = minY;
    this.xzData[off + 1] = maxY;
    this.xzSpanCounts[i] = 1;
  }
}
```

- [ ] **Step 4: reset, dispose, getStats, snapshot/restore güncelle**

reset(): 3 grid reset. dispose(): 3 grid temizle. getStats(): 3 grid bellek. snapshot/restore: 3 grid dahil.

---

### Task 2: TripleDexelEngine — processToolPosition Yön Düzeltmesi

**Files:**
- Modify: `TripleDexelEngine.ts:281-369` (processToolPosition)
- Modify: `TripleDexelEngine.ts:371-459` (processToolPositionBatch)

- [ ] **Step 1: processToolPosition → dominant axis tespit + 3 grid dispatch**

Mevcut kod sadece XY grid'e yazıyor. Yeni mantık:

```typescript
processToolPosition(sample: ToolSample, tool: DexelToolGeometry): void {
  let { x, y, z, i: axisI, j: axisJ, k: axisK } = sample;

  // NaN / zero-vector guard (mevcut)
  if (isNaN(axisI) || isNaN(axisJ) || isNaN(axisK) ||
      (Math.abs(axisI) + Math.abs(axisJ) + Math.abs(axisK)) < 0.001) {
    axisI = 0; axisJ = 0; axisK = 1;
  }

  const absI = Math.abs(axisI);
  const absJ = Math.abs(axisJ);
  const absK = Math.abs(axisK);

  // HER ZAMAN XY grid'e yaz (heightmap uyumluluğu)
  this._processOnXYGrid(x, y, z, axisI, axisJ, axisK, absK, tool);

  // X-dominant → YZ grid'e de yaz
  if (absI >= 0.3 && absI >= absJ && absI >= absK) {
    this._processOnYZGrid(x, y, z, axisI, tool);
  }
  // Y-dominant → XZ grid'e de yaz
  else if (absJ >= 0.3 && absJ >= absI && absJ >= absK) {
    this._processOnXZGrid(x, y, z, axisJ, tool);
  }

  this.processedSamples++;
}
```

- [ ] **Step 2: _processOnXYGrid — mevcut kodu extract et**

Mevcut processToolPosition'daki XY grid loop'unu private metoda taşı. Tek fark: `processedSamples++` çağrısı kaldır (üst metod yapacak).

- [ ] **Step 3: _processOnYZGrid — X-dominant kesim**

```typescript
private _processOnYZGrid(
  x: number, y: number, z: number,
  axisI: number, tool: DexelToolGeometry
): void {
  const R = tool.diameter / 2;
  const R2 = R * R;
  const cellSize = this.cellSize;
  const cellSizeInv = 1 / cellSize;
  const depthMinX = this.bbox.minX;
  const depthMaxX = this.bbox.maxX;
  const planeMinY = this.bbox.minY;
  const planeMinZ = this.bbox.minZ;
  const resY = this.resY;
  const resZ = this.resZ;
  const toolLen = tool.length || tool.diameter * 3;
  const isBall = tool.type === 'ball';
  const isBull = tool.type === 'bull';
  const cornerR = tool.cornerRadius || 0;
  const flatR = R - cornerR;
  const flatR2 = flatR * flatR;
  const cornerR2 = cornerR * cornerR;
  const absI = Math.abs(axisI);

  const ivMin = Math.max(0, Math.floor((y - R - planeMinY) * cellSizeInv));
  const ivMax = Math.min(resY - 1, Math.ceil((y + R - planeMinY) * cellSizeInv));
  const iwMin = Math.max(0, Math.floor((z - R - planeMinZ) * cellSizeInv));
  const iwMax = Math.min(resZ - 1, Math.ceil((z + R - planeMinZ) * cellSizeInv));

  for (let iw = iwMin; iw <= iwMax; iw++) {
    const worldZ = planeMinZ + (iw + 0.5) * cellSize;
    const dw = worldZ - z;
    const dwSq = dw * dw;

    for (let iv = ivMin; iv <= ivMax; iv++) {
      const worldY = planeMinY + (iv + 0.5) * cellSize;
      const dv = worldY - y;
      const dist2Sq = dv * dv + dwSq;
      if (dist2Sq > R2) continue;

      // Tool tip X at this YZ position
      let tipX = x;
      if (isBall) {
        tipX = x + R - Math.sqrt(Math.max(0, R2 - dist2Sq));
      } else if (isBull && cornerR > 0 && dist2Sq > flatR2) {
        const dist2D = Math.sqrt(dist2Sq);
        if (dist2D < R) {
          const dfc = dist2D - flatR;
          if (dfc < cornerR) {
            tipX = x + cornerR - Math.sqrt(cornerR2 - dfc * dfc);
          }
        }
      }

      let cutMin: number, cutMax: number;
      if (absI >= 0.3) {
        if (axisI >= 0) {
          if (tipX >= depthMaxX) continue;
          cutMin = Math.max(tipX, depthMinX);
          cutMax = depthMaxX;
        } else {
          if (tipX <= depthMinX) continue;
          cutMin = depthMinX;
          cutMax = Math.min(tipX, depthMaxX);
        }
      } else {
        cutMin = Math.max(tipX - toolLen * 0.5, depthMinX);
        cutMax = Math.min(tipX + toolLen * 0.5, depthMaxX);
      }

      if (cutMin >= cutMax) continue;

      const colIdx = iw * resY + iv;
      this.subtractSpanFromColumn(this.yzData, this.yzSpanCounts, colIdx, cutMin, cutMax);
      // XY dirty de işaretle (heightmap etkilenebilir)
      this.markDirty(
        Math.max(0, Math.min(this.resX - 1, Math.floor((x - this.bbox.minX) * cellSizeInv))),
        iv
      );
    }
  }
}
```

- [ ] **Step 4: _processOnXZGrid — Y-dominant kesim (YZ grid mirror)**

XZ grid: (ix, iz) hücreleri, Y-depth spans. Mantık YZ grid'in ayna görüntüsü.

- [ ] **Step 5: processToolPositionBatch aynı 3-grid mantığını ekle**

Batch loop içinde dominant axis tespit → _processOnXYGrid + _processOnYZGrid/_processOnXZGrid çağır.

---

### Task 3: TripleDexelEngine — isPointInMaterial 3-Grid

**Files:**
- Modify: `TripleDexelEngine.ts:1313-1324` (isPointInMaterial)

- [ ] **Step 1: 3 grid AND kontrolü**

```typescript
isPointInMaterial(x: number, y: number, z: number): boolean {
  const ix = Math.floor((x - this.bbox.minX) / this.cellSize);
  const iy = Math.floor((y - this.bbox.minY) / this.cellSize);
  const iz = Math.floor((z - this.bbox.minZ) / this.cellSize);

  // XY grid: Z span kontrolü (mevcut)
  if (ix >= 0 && ix < this.resX && iy >= 0 && iy < this.resY) {
    const idxXY = iy * this.resX + ix;
    const scXY = this.xySpanCounts[idxXY];
    const offXY = idxXY * MAX_SPANS * SPAN_SIZE;
    let hasZ = false;
    for (let i = 0; i < scXY; i++) {
      if (z >= this.xyData[offXY + i * SPAN_SIZE] && z <= this.xyData[offXY + i * SPAN_SIZE + 1]) {
        hasZ = true; break;
      }
    }
    if (!hasZ) return false;
  } else return false;

  // YZ grid: X span kontrolü
  if (iy >= 0 && iy < this.resY && iz >= 0 && iz < this.resZ) {
    const idxYZ = iz * this.resY + iy;
    const scYZ = this.yzSpanCounts[idxYZ];
    if (scYZ > 0) {
      const offYZ = idxYZ * MAX_SPANS * SPAN_SIZE;
      let hasX = false;
      for (let i = 0; i < scYZ; i++) {
        if (x >= this.yzData[offYZ + i * SPAN_SIZE] && x <= this.yzData[offYZ + i * SPAN_SIZE + 1]) {
          hasX = true; break;
        }
      }
      if (!hasX) return false;
    }
  }

  // XZ grid: Y span kontrolü
  if (ix >= 0 && ix < this.resX && iz >= 0 && iz < this.resZ) {
    const idxXZ = iz * this.resX + ix;
    const scXZ = this.xzSpanCounts[idxXZ];
    if (scXZ > 0) {
      const offXZ = idxXZ * MAX_SPANS * SPAN_SIZE;
      let hasY = false;
      for (let i = 0; i < scXZ; i++) {
        if (y >= this.xzData[offXZ + i * SPAN_SIZE] && y <= this.xzData[offXZ + i * SPAN_SIZE + 1]) {
          hasY = true; break;
        }
      }
      if (!hasY) return false;
    }
  }

  return true;
}
```

---

### Task 4: dexel.worker.ts — Yön Düzeltmesi + Typed Array Perf

**Files:**
- Modify: `dexel.worker.ts:285-383` (processToolPosition)
- Modify: `dexel.worker.ts:534-578` (extractDirtyHeights)

- [ ] **Step 1: Worker processToolPosition'a dominant axis + YZ/XZ grid yazma**

Worker zaten yzData/xzData allocate ediyor (satır 150-155). processToolPosition'a aynı 3-grid mantığını ekle.

- [ ] **Step 2: extractDirtyHeights → typed array**

```typescript
function extractDirtyHeights(): { indices: Int32Array; heights: Float32Array; bottoms: Float32Array } {
  const capacity = dirtyCount || 1;
  const indices = new Int32Array(capacity);
  const heights = new Float32Array(capacity);
  const bottoms = new Float32Array(capacity);
  let wi = 0;

  const totalCells = resX * resY;
  for (let idx = 0; idx < totalCells; idx++) {
    if (dirtyMask[idx] === 0) continue;
    indices[wi] = idx;
    // ... span height logic (mevcut)
    wi++;
  }

  dirtyMask!.fill(0);
  dirtyCount = 0;
  dirtyBounds = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity };

  return { indices: indices.subarray(0, wi), heights: heights.subarray(0, wi), bottoms: bottoms.subarray(0, wi) };
}
```

- [ ] **Step 3: Transferable buffer ile gönderim**

handleProcess'te: `sendResult(id, { ... }, [indices.buffer, heights.buffer, bottoms.buffer])`

---

### Task 5: DexelGPUEngine — I/J/K Shader Desteği

**Files:**
- Modify: `DexelGPUEngine.ts:14-35` (interfaces)
- Modify: `DexelGPUEngine.ts:41-226` (WGSL shader)
- Modify: `DexelGPUEngine.ts:471-498` (writeUniformsForSample)

- [ ] **Step 1: GPUToolSample'a toolAxisI/toolAxisJ ekle**

```typescript
export interface GPUToolSample {
  x: number; y: number; z: number;
  toolRadius: number;
  toolType: number;
  cornerRadius: number;
  toolAxisI: number;   // YENİ
  toolAxisJ: number;   // YENİ
  toolAxisK: number;
  toolLength: number;
}
```

- [ ] **Step 2: Uniform struct genişlet + WGSL shader güncelle**

Shader'a `toolAxisI`, `toolAxisJ` ekle. Kesim mantığında dominant axis → doğru cut range.

- [ ] **Step 3: writeUniformsForSample güncelle**

---

### Task 6: Performans — Dirty-Region Smoothing/AO + Caching

**Files:**
- Modify: `TripleDexelEngine.ts:632-698` (smoothHeightMap)
- Modify: `TripleDexelEngine.ts:768-833` (computeVertexAO)
- Modify: `TripleDexelEngine.ts:839-853` (getMeshLayout)
- Modify: `TripleDexelEngine.ts:855-1026` (createProfessionalStockMesh)

- [ ] **Step 1: smoothHeightMapDirty — dirtyBounds parametresi**

DirtyBounds varsa sadece o bölge + 2px margin üzerinde smooth.

- [ ] **Step 2: computeVertexAO — dirty-only mode**

Dirty indices listesi gelirse sadece o hücrelerin AO'sunu hesapla, geri kalanı önceki buffer'dan koru.

- [ ] **Step 3: Index buffer caching**

```typescript
private _indexCache: Uint32Array | null = null;
```

`createProfessionalStockMesh`'te indices hesabı sadece ilk çağrıda, sonraki çağrılarda cache'den.

- [ ] **Step 4: Material singleton**

```typescript
private static _defaultMaterial: THREE.MeshPhysicalMaterial | null = null;
```

Her mesh oluşturmada new material yerine singleton kullan.
