From cc8ec5f8dc93e6ac8d36268d8259831f411a8028 Mon Sep 17 00:00:00 2001 From: PedroAugusto08 Date: Mon, 14 Sep 2026 09:48:49 -0300 Subject: [PATCH] =?UTF-8?q?fix:=20alinhar=20contrato=20de=20integra=C3=A7?= =?UTF-8?q?=C3=A3o=20com=20provider=20de=20streaming?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- docs/EQUIPE_B_ARCHITECTURE.md | 14 +- docs/INTEGRATION_SPEC.md | 319 +++++++--------------------------- src/integration/README.md | 21 ++- src/integration/voxel_grid.py | 15 +- src/interaction/README.md | 2 +- src/interactive/README.md | 15 +- src/world/README.md | 7 +- tests/test_integration.py | 24 ++- 9 files changed, 140 insertions(+), 281 deletions(-) diff --git a/README.md b/README.md index 7687f0e..bacae26 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,8 @@ O **Isometricon** foi planejado com interfaces limpas no módulo `src/integratio | Módulo | Equipe A (Motor do Mundo) | Equipe B (Motor Interativo) | | :--- | :--- | :--- | | **Responsabilidade** | Chunks, VoxelGrid, Terreno, Shaders, Otimização | Raycasting, Grid, Miniaturas, UI | -| **Fornece** | `VoxelGridProvider`, `CameraStateProvider` | Comandos de seleção e movimento | -| **Consome** | Coordenadas de foco e seleção da Equipe B | `VoxelGrid`, `ViewMatrix`, `ProjectionMatrix` | +| **Fornece** | `VoxelGridProvider` e estado de frame no loop principal | Raycasting, grid, miniaturas e destaques | +| **Consome** | Dados de terreno por `VoxelGridProvider` | View, Projection, Model e viewport passados diretamente | > Consulte [docs/INTEGRATION_SPEC.md](docs/INTEGRATION_SPEC.md) para a especificação completa do contrato. diff --git a/docs/EQUIPE_B_ARCHITECTURE.md b/docs/EQUIPE_B_ARCHITECTURE.md index 49dc1da..31858de 100644 --- a/docs/EQUIPE_B_ARCHITECTURE.md +++ b/docs/EQUIPE_B_ARCHITECTURE.md @@ -25,7 +25,7 @@ O **Motor Interativo (Interactive Engine)** é o subprojeto da **Equipe B** resp +-----------------------------+ +-------------------------------+ | | +----------- src/integration/ ----------+ - VoxelGridProvider / Bridge + VoxelGridProvider ``` --- @@ -40,11 +40,9 @@ src/ │ ├── grid_overlay.py # Renderização do grid quadriculado │ ├── token_manager.py # Gerenciamento de miniaturas/tokens │ └── ui_renderer.py # Overlay de UI (fichas de RPG) -├── integration/ # 🔌 Ponte Equipe A <-> Equipe B +├── integration/ # 🔌 Consulta compartilhada A <-> B │ ├── __init__.py -│ ├── voxel_provider.py # VoxelGridProvider (implementação) -│ ├── highlight_bridge.py # HighlightBridge (ativa hover shader) -│ └── camera_provider.py # CameraStateProvider (matrizes) +│ └── voxel_grid.py # VoxelGridProvider (implementação) assets/ ├── shaders/ │ ├── highlight.vert # 🟦 Shader de destaque/hover (vertex) @@ -306,9 +304,9 @@ Consulte o documento completo em [docs/INTEGRATION_SPEC.md](INTEGRATION_SPEC.md) | Interface | Provedor | Consumidor | Finalidade | |-----------|----------|------------|------------| -| `VoxelGridProvider` | Equipe A | Equipe B | Consulta blocos e colisão | -| `HighlightBridge` | Equipe A | Equipe B | Ativa hover shader | -| `CameraStateProvider` | Equipe A | Equipe B | Matrizes View/Proj para raycasting | +| `VoxelGridProvider` | Motor do Mundo | Motor Interativo | Consulta blocos carregados | +| Matrizes de frame | `src.main` | Raycast e renderizadores | View, Projection, Model e viewport passados diretamente | +| Renderizadores de overlay | Motor Interativo | Loop principal | Controlam hover e visibilidade da grade diretamente | --- diff --git a/docs/INTEGRATION_SPEC.md b/docs/INTEGRATION_SPEC.md index e515aff..09ee589 100644 --- a/docs/INTEGRATION_SPEC.md +++ b/docs/INTEGRATION_SPEC.md @@ -1,283 +1,94 @@ -# 🔌 Especificação Completa do Contrato de Integração (Equipe A ↔ Equipe B) +# Contrato de Integração Atual (Motor do Mundo ↔ Motor Interativo) -
+## Visão geral -[![Equipe A](https://img.shields.io/badge/Equipe%20A-Motor%20do%20Mundo-228B22?style=flat-square)](ARCHITECTURE.md) -[![Equipe B](https://img.shields.io/badge/Equipe%20B-Motor%20Interativo-0066CC?style=flat-square)](EQUIPE_B_ARCHITECTURE.md) - -
- ---- - -## 1. Visão Geral - -Para garantir total independência de desenvolvimento durante os 30 dias e uma integração final simples e sem atritos, definem-se os seguintes contratos de dados e serviços entre a **Equipe A (Motor do Mundo)** e a **Equipe B (Motor Interativo)**. - -O módulo de ponte está em `src/integration/` e é mantido pela **Equipe A**, mas consumido pela **Equipe B**. - ---- - -## 2. Diagrama de Comunicação +O Isometricon executa o Motor do Mundo e o Motor Interativo no mesmo processo e +no mesmo loop de frame. O contrato de dados entre as camadas é +`src.integration.VoxelGridProvider`; não há bridges de câmera ou de destaque. ``` -┌───────────────────────────────────┐ ┌───────────────────────────────────┐ -│ EQUIPE A (Motor do Mundo) │ │ EQUIPE B (Motor Interativo) │ -│ │ │ │ -│ IsometricCamera ──────────────────────► CameraStateProvider │ -│ │ │ (view, proj, viewport) │ -│ │ │ │ │ -│ VoxelWorld / Chunks ──────────────────► VoxelGridProvider │ -│ │ │ (getBlockAt, isSolid, getTop) │ -│ │ │ │ │ -│ HighlightRenderer ◄───────────────────── HighlightBridge │ -│ (shader ativado pela Equipe A) │ │ (setHighlight, clearHighlight) │ -│ │ │ │ │ -│ GridOverlay ◄─────────────────────────── setGridOverlayVisible │ -└───────────────────────────────────┘ └───────────────────────────────────┘ -``` - ---- - -## 3. Contrato 1 — Acesso a Dados do Terreno (`VoxelGridProvider`) - -A Equipe A expõe métodos de consulta rápida para que a Equipe B possa realizar Raycasting 3D e validar posicionamento de miniaturas. - -### Interface Python - -```python -# src/integration/voxel_provider.py +WorldManager ──neighbor_at──► VoxelGridProvider ──► raycast_voxels + │ │ + ├── chunks carregados ──► GridOverlayRenderer ▼ + └── terreno renderizado ◄── BlockHighlightRenderer / PlayerToken -class VoxelGridProvider: - """ - Interface de consulta espacial do VoxelGrid para uso da Equipe B. - Implementada pela Equipe A, consumida pela Equipe B. - """ - - def get_block_at(self, world_x: int, world_y: int, world_z: int) -> int: - """ - Retorna o tipo do bloco (BlockType) na coordenada do mundo. - Retorna 0 (AIR) para coordenadas fora dos limites do mundo. - """ - ... - - def is_solid(self, world_x: int, world_y: int, world_z: int) -> bool: - """ - Retorna True se o bloco em (x, y, z) é sólido (não-ar). - Usado pela Equipe B para validar movimento de tokens. - """ - ... - - def get_top_solid_block(self, world_x: int, world_z: int) -> int: - """ - Retorna a coordenada Y do bloco sólido mais alto na coluna (x, z). - Usado para posicionar tokens sobre o terreno. - """ - ... - - def get_block_bounding_box(self, world_x: int, world_y: int, world_z: int) -> AABB: - """ - Retorna a AABB (Axis-Aligned Bounding Box) do bloco em (x, y, z). - Usado pelo algoritmo de ray-AABB intersection. - """ - ... - - def get_world_bounds(self) -> tuple[int, int, int]: - """ - Retorna (width, height, depth) do mundo em blocos. - """ - ... +IsometricCamera ──► View, Projection, Model, viewport ──► raycasting e renderizadores ``` -### Tipos de Bloco (`BlockType`) - -| ID | Nome | Sólido | -|:--- |:--- |:---: | -| `0` | `AIR` | ❌ | -| `1` | `DIRT` | ✅ | -| `2` | `GRASS` | ✅ | -| `3` | `STONE` | ✅ | -| `4` | `WATER` | ❌ | -| `5` | `WOOD` | ✅ | -| `6` | `LEAVES` | ❌ | - ---- - -## 4. Contrato 2 — Estado da Câmera (`CameraStateProvider`) - -Para que a Equipe B consiga converter coordenadas de tela $(x_{mouse}, y_{mouse})$ em um raio 3D, a Equipe A expõe as matrizes ativas da câmera isométrica por frame. - -### Interface Python +## Consulta de terreno: `VoxelGridProvider` ```python -# src/integration/camera_provider.py - -class CameraStateProvider: - """ - Exporta estado atual da câmera isométrica para uso do raycasting da Equipe B. - Deve ser atualizada a cada frame antes do loop de renderização da Equipe B. - """ - - def get_view_matrix(self) -> np.ndarray: - """Retorna a matriz View 4x4 (float32) da câmera isométrica.""" - ... - - def get_projection_matrix(self) -> np.ndarray: - """Retorna a matriz Projection 4x4 ortográfica (float32).""" - ... +from src.integration import VoxelGridProvider - def get_viewport(self) -> tuple[int, int, int, int]: - """Retorna (x, y, width, height) da viewport OpenGL atual.""" - ... - - def get_camera_target(self) -> np.ndarray: - """Retorna o ponto de foco da câmera (vec3) no espaço do mundo.""" - ... +provider = VoxelGridProvider(block_lookup=world_manager.neighbor_at) ``` -### Matemática do Raycasting (Projeção Ortográfica) - -Para projeção ortográfica, pixels diferentes produzem origens diferentes e -direções paralelas. O raycast desprojeta os dois planos de recorte. Como o -renderer gira o tabuleiro com uma matriz Model animada, usa-se a mesma matriz -na composição $\mathbf{Q}=\mathbf{P}\mathbf{V}\mathbf{M}$: - -$$x_{NDC} = \frac{2 \cdot x_{mouse}}{W} - 1, \qquad y_{NDC} = 1 - \frac{2 \cdot y_{mouse}}{H}$$ - -$$\mathbf{p}_{near}=\operatorname{dehom}\left(\mathbf{Q}^{-1} -(x_{NDC},y_{NDC},-1,1)^T\right)$$ +O provider recebe e retorna coordenadas globais `(x, y, z)`. Para testes ou +mundos estáticos, pode receber um mapeamento de `Chunk3D`; para o mundo em +produção, `block_lookup=WorldManager.neighbor_at` consulta somente os chunks +atualmente carregados. -$$\mathbf{p}_{far}=\operatorname{dehom}\left(\mathbf{Q}^{-1} -(x_{NDC},y_{NDC},1,1)^T\right)$$ +| Método | Contrato | +|---|---| +| `get_block_at(x, y, z)` | Retorna `BlockType`; regiões/chunks ausentes retornam `AIR`. | +| `is_solid(x, y, z)` | Retorna se a célula está ocupada, isto é, se é diferente de `AIR`. | +| `get_top_solid_block(x, z)` | Retorna o maior Y não-`AIR` na coluna carregada, ou `-1` se ela não existir. No streaming atual, a busca cobre a coluna vertical carregada `y=0` (0–15). | +| `get_block_bounding_box(x, y, z)` | Retorna a AABB unitária `[x,x+1] × [y,y+1] × [z,z+1]`. | -$$\mathbf{o}=\mathbf{p}_{near}$$ +As conversões para chunk/local usam divisão inteira e módulo, preservando +coordenadas negativas. As consultas não iniciam carregamento, não aguardam a +worker e não alteram o estado de streaming. O mundo é streamado e não possui +bounds globais fixos. -$$\mathbf{d}=\operatorname{normalize}(\mathbf{p}_{far}-\mathbf{p}_{near})$$ +### Ocupação não é superfície tática -O resultado fica no espaço lógico dos voxels, coerente com as coordenadas -globais do `VoxelGridProvider` durante pan, zoom, resize e rotação Q/E. +Para o provider e o raycasting padrão, todo bloco diferente de `AIR` é +ocupado/atingível, incluindo `WATER` e `LEAVES`. A política de uma célula +capaz de sustentar tokens e grade está em `src.interaction.surface`: +`DIRT`, `GRASS`, `STONE` e `WOOD` são superfícies táticas; água e folhas não. ---- +## Estado de câmera e frame -## 5. Contrato 3 — Renderização de Destaque (`HighlightBridge`) - -Quando a Equipe B detectar a interseção do raio do mouse com um bloco $(x, y, z)$, ela aciona a renderização de destaque via esta interface. - -### Interface Python +No frame, `src.main` calcula diretamente: ```python -# src/integration/highlight_bridge.py - -class HighlightBridge: - """ - Permite que a Equipe B ative/desative efeitos visuais de seleção - nos shaders de destaque implementados pela Equipe A/B. - """ - - def set_highlighted_block(self, - world_x: int, world_y: int, world_z: int, - color: np.ndarray) -> None: - """ - Define o bloco atualmente sob foco do cursor (hover). - color: vec4 (r, g, b, a) com valores em [0.0, 1.0] - """ - ... - - def set_selected_block(self, - world_x: int, world_y: int, world_z: int, - color: np.ndarray) -> None: - """Define o bloco selecionado (clique confirmado).""" - ... - - def set_movement_range(self, cells: list[tuple[int, int, int]], - color: np.ndarray) -> None: - """ - Define a lista de células alcançáveis no turno atual. - Exibidas com cor diferente para indicar alcance de movimento. - """ - ... - - def clear_highlight(self) -> None: - """Remove todos os destaques ativos.""" - ... - - def set_grid_overlay_visible(self, visible: bool) -> None: - """Ativa ou desativa a renderização do grid quadriculado.""" - ... -``` - ---- - -## 6. Protocolo de Sincronização por Frame - -A ordem de execução por frame quando ambas as equipes estão integradas: - +projection = camera.get_projection_matrix(window.width, window.height) +view = camera.get_view_matrix() +model = camera.get_animated_model_matrix() +mouse_x, mouse_y = window.get_cursor_pos_framebuffer() ``` -┌─── LOOP PRINCIPAL ─────────────────────────────────────────────────────────┐ -│ │ -│ 1. [Input] Coletar eventos de mouse e teclado │ -│ │ -│ 2. [Equipe B] Raycasting → detectar bloco sob cursor │ -│ │ -│ 3. [Bridge] HighlightBridge.set_highlighted_block(x, y, z, color) │ -│ │ -│ 4. [Equipe A] Renderizar mundo (chunks, terreno, shaders de iluminação) │ -│ │ -│ 5. [Equipe B] Renderizar overlays (highlight, grid, tokens, UI) │ -│ ↑ Deve vir DEPOIS da Equipe A para sobrepor corretamente │ -│ │ -│ 6. [Swap] Apresentar framebuffer final │ -└────────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 7. Opção de Integração via Sockets TCP (Avançado) - -Para integração de **processos separados** (cada equipe com seu próprio executável), as equipes podem usar TCP/IP local: - -``` -┌─────────────────────┐ TCP localhost:9999 ┌─────────────────────┐ -│ Equipe A │ ──────────────────────► │ Equipe B │ -│ (Servidor) │ ◄────────────────────── │ (Cliente) │ -│ Porta: 9999 │ │ │ -└─────────────────────┘ └─────────────────────┘ - -Mensagens JSON: - A → B: { "type": "world_state", "chunks": [...], "camera": {...} } - B → A: { "type": "highlight", "block": [x, y, z], "color": [...] } - B → A: { "type": "token_move", "token_id": "...", "target": [x, y, z] } -``` - -### Protocolo de Mensagens -| Direção | Tipo | Payload | Frequência | -|:--- |:--- |:--- |:--- | -| A → B | `camera_state` | `view[16], proj[16], viewport[4]` | Por frame | -| A → B | `world_ready` | `bounds[3], chunk_list` | Uma vez | -| B → A | `highlight_set` | `x, y, z, color[4]` | Por evento | -| B → A | `highlight_clear` | — | Por evento | -| B → A | `grid_toggle` | `visible: bool` | Por evento | +Esses valores são passados diretamente a `screen_to_world_ray`, +`WorldManager.render`, `GridOverlayRenderer.render` e +`BlockHighlightRenderer.render`. O raycasting usa a inversa de +`Projection @ View @ Model`, mantendo o espaço lógico de voxels consistente +durante pan, zoom, resize e rotação Q/E. Uma `CameraStateProvider` duplicaria +esse estado sem adicionar um limite arquitetural útil. ---- +## Highlight e grid -## 8. Checklist de Integração +O loop principal calcula o hover por raycast e chama +`BlockHighlightRenderer.render` diretamente quando há um hit. Sem hit, nenhum +destaque é desenhado; durante pan, o hover é suspenso. O grid é controlado por +`GridOverlayRenderer` e sua visibilidade é alternada diretamente pela tecla +`G`; sua malha é atualizada a partir do snapshot e da revisão dos chunks +carregados. -### Equipe A entrega para Equipe B: -- [ ] `VoxelGridProvider` funcional com mundo gerado -- [ ] `CameraStateProvider` atualizado por frame -- [ ] `HighlightBridge` ativa shader de destaque -- [ ] Documentação de coordenadas do mundo (sistema de eixos) +Portanto, `HighlightBridge` não faz parte do contrato atual. Seleção +persistente, alcance de movimento e click-to-move são funcionalidades futuras +e não são inferidas da existência de raycasting, token ou highlight de hover. -### Equipe B entrega para Equipe A: -- [ ] Shaders `highlight.vert/frag` compiláveis -- [ ] Shaders `grid.vert/frag` compiláveis -- [ ] Interface de teste para demonstrar raycasting +## Ordem por frame -### Critério de Integração Final: -- [ ] Clicar no terreno da Equipe A destaca o bloco com shader da Equipe B -- [ ] Token da Equipe B se move pelo terreno gerado pela Equipe A -- [ ] Grid da Equipe B acompanha a câmera da Equipe A +1. Processar entrada, atualizar token, câmera e streaming. +2. Obter View, Projection, Model, viewport e cursor em pixels de framebuffer. +3. Fazer raycast contra o `VoxelGridProvider` dos chunks carregados. +4. Renderizar o mundo. +5. Renderizar grid, destaques e token com as mesmas matrizes. +6. Apresentar o framebuffer. ---- +## Verificação -*Documento de contrato mantido em conjunto pelas **Equipes A e B** | CEFET-MG — Computação Gráfica* +Os testes de integração cobrem provider estático, limites de chunk, +coordenadas negativas, AABB, consulta dinâmica via `WorldManager` e unload de +chunks. Os testes de raycasting cobrem unprojection, DDA e o provider dinâmico. diff --git a/src/integration/README.md b/src/integration/README.md index 7a077be..2976e6c 100644 --- a/src/integration/README.md +++ b/src/integration/README.md @@ -1,4 +1,4 @@ -# 🔌 Módulo de Integração (Team B Bridge) +# 🔌 Módulo de Integração Ponto de contato entre o Motor do Mundo (Equipe A) e o Motor Interativo (Equipe B). @@ -15,7 +15,11 @@ usando coordenadas globais `(x, y, z)`. Para mundos estáticos, ele recebe um mapeamento de chunks. Para o mundo com streaming, `block_lookup=world_manager.neighbor_at` mantém as consultas ligadas aos chunks atualmente carregados. Uma região ausente continua retornando -`AIR`; a consulta não carrega chunks nem aguarda o worker assíncrono. +`AIR`; nenhuma consulta carrega chunks nem aguarda o worker assíncrono. + +`get_top_solid_block(x, z)` percorre somente a coluna vertical atualmente +usada pelo `WorldManager` (chunk `y=0`) e também retorna `-1` quando a coluna +não está carregada. O mundo streamado não possui bounds globais fixos. ### Consultas @@ -39,6 +43,19 @@ As coordenadas fornecidas à interface são sempre coordenadas globais. A conversão para chunk/local é feita internamente e suporta coordenadas negativas. +### Semântica de ocupação e superfície + +`is_solid()` significa "bloco ocupado": qualquer valor diferente de `AIR`, +inclusive `WATER` e `LEAVES`. A superfície tática válida para tokens e grade +é uma política separada de `src.interaction.surface`. + +### Integração de frame + +O estado de câmera/frame é passado diretamente pelo loop principal ao +raycasting e aos renderizadores. Hover/highlight e visibilidade da grade são +controlados diretamente por `BlockHighlightRenderer` e `GridOverlayRenderer`; +não há `CameraStateProvider` nem `HighlightBridge` no contrato atual. + ### AABB Cada voxel ocupa: diff --git a/src/integration/voxel_grid.py b/src/integration/voxel_grid.py index 018ce72..682a119 100644 --- a/src/integration/voxel_grid.py +++ b/src/integration/voxel_grid.py @@ -129,10 +129,21 @@ def get_top_solid_block( world_x: int, world_z: int, ) -> int: - # Retorna o maior Y sólido existente na coluna X/Z - # A busca utiliza somente chunks carregados, caso não exista nenhum bloco sólido na coluna, retorna -1 + """Retorna o maior Y não-AIR da coluna carregada em ``(x, z)``. + + O ``WorldManager`` atual transmite apenas colunas no chunk vertical + ``y=0``. No modo dinâmico, a busca consulta essa coluna diretamente + pelo ``block_lookup``: regiões descarregadas continuam retornando AIR + e a consulta não interage com as filas de streaming. + """ x = _coordinate(world_x) z = _coordinate(world_z) + if self._block_lookup is not None: + for y in range(Chunk3D.SIZE - 1, -1, -1): + if self.get_block_at(x, y, z) != BlockType.AIR: + return y + return -1 + best_y: int | None = None for chunk in self._chunks.values(): chunk_x_min = chunk.chunk_x * Chunk3D.SIZE diff --git a/src/interaction/README.md b/src/interaction/README.md index 08f1f2c..412936e 100644 --- a/src/interaction/README.md +++ b/src/interaction/README.md @@ -15,7 +15,7 @@ Este pacote contém toda a lógica de interação do usuário com o tabuleiro: ## Dependências -- `src/integration/` — Pontos de contato com a Equipe A (VoxelGridProvider, HighlightBridge, CameraStateProvider) +- `src/integration/` — Consulta de terreno compartilhada (`VoxelGridProvider`) - `src/rendering/` — TexturedMesh para renderização dos tokens - `src/camera/` — IsometricCamera para cálculos de raycasting diff --git a/src/interactive/README.md b/src/interactive/README.md index 41bb86c..2515c7c 100644 --- a/src/interactive/README.md +++ b/src/interactive/README.md @@ -66,17 +66,18 @@ O diretório `src/interactive/` contém todos os módulos da **Equipe B (Motor I ## Arquitetura de Integração ``` -src/integration/ ← Bridge A↔B (mantido pela Equipe A) - voxel_provider.py ← Consulta de blocos do mundo - camera_provider.py ← Estado da câmera (view/proj) - highlight_bridge.py ← Ativa shaders de destaque +src/integration/ + voxel_grid.py ← VoxelGridProvider: consulta de blocos do mundo src/interactive/ ← DOMÍNIO DA EQUIPE B - raycasting.py highlight.py grid_overlay.py token_system.py - ui_overlay.py ``` -Consulte [INTEGRATION_SPEC.md](../../docs/INTEGRATION_SPEC.md) para o contrato completo de interfaces. +O loop principal passa View, Projection, Model e viewport diretamente ao +raycasting e aos renderizadores. Highlight e grid mantêm seu próprio estado; +`CameraStateProvider` e `HighlightBridge` não fazem parte da arquitetura atual. +Seleção persistente, alcance de movimento e click-to-move continuam futuros. + +Consulte [INTEGRATION_SPEC.md](../../docs/INTEGRATION_SPEC.md) para o contrato completo. diff --git a/src/world/README.md b/src/world/README.md index cad33a5..d1ccdb9 100644 --- a/src/world/README.md +++ b/src/world/README.md @@ -43,9 +43,9 @@ Responsável pelo armazenamento, geração e otimização dos dados de blocos do permanecem inteiras. O cubo de demonstração centrado em main.py precisará ser posicionado no centro da célula pela futura renderização. -O contrato VoxelGridProvider permanece futuro: consultas globais, solidez, -topo de coluna e AABB por bloco serão compostas sobre esta fundação. -A representação AABB contém somente dados; raycasting permanece fora deste módulo. +`VoxelGridProvider` já compõe sobre esta fundação consultas globais, ocupação, +topo de coluna e AABB por bloco. A representação AABB contém somente dados; +raycasting permanece fora deste módulo. ## Meshing CPU (Issue #6) @@ -279,4 +279,3 @@ validação da API e geometria/índices do mesher sem contexto gráfico. Fora desta etapa: WorldManager/streaming, save/load, biomas, vegetação, estruturas, greedy meshing, frustum culling, materiais avançados e Equipe B. - diff --git a/tests/test_integration.py b/tests/test_integration.py index 58cebd8..b44c478 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -4,7 +4,7 @@ from src.math.aabb import AABB from src.integration import VoxelGridProvider -from src.world import BlockType, Chunk3D +from src.world import BlockType, Chunk3D, TerrainGenerator, WorldManager def make_chunk( @@ -98,6 +98,28 @@ def test_top_solid_block_across_y_chunks(): assert provider.get_top_solid_block(3, 4) == 16 +def test_top_solid_block_uses_loaded_dynamic_world_chunks(): + generator = TerrainGenerator(seed=27, enable_caves=False) + manager = WorldManager( + generator=generator, + render_distance=0, + create_gl_meshes=False, + async_loading=False, + ) + try: + manager.load_initial_region(-1.0, -1.0) + provider = VoxelGridProvider(block_lookup=manager.neighbor_at) + + expected_y = max(generator.get_height(-1, -1), generator.sea_level) + assert provider.get_top_solid_block(-1, -1) == expected_y + + manager.update(32.0, -1.0) + assert provider.get_block_at(-1, expected_y, -1) is BlockType.AIR + assert provider.get_top_solid_block(-1, -1) == -1 + finally: + manager.delete() + + def test_block_bounding_box(): provider = VoxelGridProvider()