Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb8705369e | ||
|
|
c3d1443732 | ||
|
|
1967342247 | ||
|
|
89db24d7c2 | ||
|
|
bad2bdb0d1 | ||
|
|
098a5aa1bd |
@@ -1,7 +1,7 @@
|
||||
# Sanctuary
|
||||
|
||||
[Dernière release : beta.295](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.295) ·
|
||||
[Reçu de livraison](docs/release-beta295.md) ·
|
||||
[Dernière release : beta.297](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.297) ·
|
||||
[Reçu de livraison](docs/release-beta297.md) ·
|
||||
[Installation et mises à jour](docs/packwiz.md).
|
||||
|
||||
Les **quatre expansions validées sont disponibles dans Sanctuary normal** :
|
||||
@@ -10,6 +10,20 @@ Créer un monde **Sanctuary** en Petit, Moyen ou Grand, puis activer les ancres
|
||||
correspondantes. Le module Sanctuary Test n’est pas requis.
|
||||
[Intégration et vérification du pack beta.224](docs/four-islands-base224.md).
|
||||
|
||||
## beta.298 — normales de l'ancre (préparation locale)
|
||||
|
||||
[Retrait du petit cadre résiduel dans la passe PBR](docs/media-anchor-normals-beta298.md).
|
||||
Le modèle invisible de l'ancre ne doit plus laisser de reflet sur la vidéo
|
||||
avec les shaders Sanctuary activés.
|
||||
|
||||
## beta.297 — correctifs des cadres disponibles
|
||||
|
||||
[Menu de taille à la pose, lecteur au Maj + clic droit et cadre en volume](docs/media-frame-fixes-beta297.md).
|
||||
Retrait du repère rouge et du contour de sélection du bloc invisible.
|
||||
Le redimensionnement reste accessible par Taille / Size dans le lecteur.
|
||||
[Livraison vérifiée](docs/release-beta297.md) : serveur Beta, site et même
|
||||
instance Prism à jour, sauvegardes et réglages conservés.
|
||||
|
||||
## beta.296 — écrans partagés disponibles pour essai
|
||||
|
||||
[Cadres de redstone, taille libre et menu Dream Displays](docs/media-beta296.md).
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# MEDIA-07 — normales résiduelles de l'ancre
|
||||
|
||||
Retour du 27 octobre 2026 : une bordure légèrement brillante reste sur la
|
||||
vidéo à l'emplacement de l'ancre, dans le coin inférieur gauche.
|
||||
Branche `codex/media-anchor-model`, préparation **beta.298**, Media **0.3.2**.
|
||||
|
||||
## Cause et correction
|
||||
|
||||
Le bloc est invisible pour le rendu Minecraft, mais conserve un ancien modèle
|
||||
de cadre 1 × 1 avec **30 faces**. La passe PBR de Sanctuary collectait aussi
|
||||
les modèles des blocs invisibles. Elle dessinait donc les normales et le
|
||||
matériau de ce petit cadre, alors que sa couleur n'était pas rendue.
|
||||
Les essais graphiques de MEDIA-06 avaient les shaders Sanctuary désactivés ;
|
||||
ils ne couvraient pas cette passe.
|
||||
|
||||
La collecte PBR des modèles opaques et du verre respecte maintenant
|
||||
`RenderShape.MODEL`. Les fluides gardent leur chemin dédié, y compris l'eau
|
||||
contenue dans un bloc. Le modèle de bloc de l'ancre est également vidé ;
|
||||
sa texture de particules reste disponible. L'objet utilise déjà un modèle
|
||||
indépendant et garde son apparence dans l'inventaire et dans la main.
|
||||
Le grand cadre reste dessiné par son maillage aux dimensions de l'écran.
|
||||
|
||||
Aucun identifiant, paquet, format de données ou règle d'interaction ne change.
|
||||
Les ancres existantes bénéficient du correctif au chargement du client.
|
||||
La sauvegarde et les dimensions ne nécessitent aucune migration.
|
||||
|
||||
## Validation
|
||||
|
||||
Reproduction avec le JAR beta.297 publié : deux clients Vulkan, Sodium,
|
||||
Sound Physics et **shaders Sanctuary/PBR actifs**. Le diagnostic constate
|
||||
`INVISIBLE`, 30 faces et admission de l'ancre dans la passe PBR opaque.
|
||||
Le bois et le verre visibles sont admis dans leurs passes respectives, l'eau
|
||||
dans la passe transparente.
|
||||
|
||||
Après correction, les deux clients constatent **zéro face**, ancre refusée
|
||||
par les deux passes de matériau, PBR réellement appliqué et vidéo en mouvement.
|
||||
Les chemins bois, verre et eau restent admis. Pose, taille 9 × 5 et ouverture
|
||||
du lecteur testées dans le laboratoire ; un clic de pose a dû être rejoué
|
||||
après la réception de l'inventaire du test.
|
||||
|
||||
Captures finales du client A inspectées, en gros plan du coin inférieur gauche,
|
||||
de face et en vue rasante : aucune empreinte du cadre 1 × 1 sur la vidéo.
|
||||
La sortie OpenAL est active sur les deux clients. Le JAR Sanctuary testé,
|
||||
avec un seul Media embarqué et sans sonde, a pour SHA-256
|
||||
`e6fdc36a212dcfcc89d31fbb29bc8eeec9d002cc9223ea6dcbfb31b7df17ec23`.
|
||||
|
||||
`check build assemblePack assembleServerPack :sanctuary-test:exportDuoLaunch`
|
||||
réussis en **3 min 6 s**, 162 tâches, **18 GameTests requis** au profil
|
||||
`property270,claims272,claims277,claims278`, atlas ciblé. Contrats Media inclus.
|
||||
Compilation des vérifications client PBR, y compris les nouveaux contrôles
|
||||
des blocs invisibles et des modèles visibles, réussie en **37 s**. La suite
|
||||
graphique PBR historique complète n'est pas rejouée ; ce parcours ciblé
|
||||
exerce la sélection réelle des matériaux du JAR construit.
|
||||
|
||||
Un lancement de client a automatiquement basculé hors Vulkan après une course
|
||||
d'extraction MoltenVK entre les deux processus : il a été rejeté, arrêté et
|
||||
relancé sous Vulkan. Seuls les relevés Vulkan sont retenus. Après les captures
|
||||
et les relevés valides, le client B s'est arrêté sur l'écriture du journal de
|
||||
la sonde (`No space left on device`). Le disque local ne disposait plus que
|
||||
de 136 Mio au contrôle suivant ; ce parcours ne vaut donc pas essai d'endurance.
|
||||
Ce défaut d'espace appartient au laboratoire, la sonde n'est pas distribuée.
|
||||
|
||||
[Relevé structuré](media-anchor-normals-results.json). Pas de nouvelle mesure
|
||||
de synchronisation vidéo/live ou de source publique. Mur graphique orienté sud.
|
||||
Laboratoires isolés sous `build/media-prototype/anchor-model-*`, captures finales
|
||||
dans `anchor-model-after/client-a/screenshots/`. Aucun monde personnel utilisé.
|
||||
Cette préparation ne modifie pas la version beta.297 actuellement installée
|
||||
sur le canal, la Beta et Prism.
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"ticket": "MEDIA-07",
|
||||
"state": "local preparation; beta.297 remains installed",
|
||||
"artifact": {
|
||||
"version": "beta.298",
|
||||
"bytes": 18301295,
|
||||
"sha256": "e6fdc36a212dcfcc89d31fbb29bc8eeec9d002cc9223ea6dcbfb31b7df17ec23",
|
||||
"media_version": "0.3.2",
|
||||
"nested_media_count": 1,
|
||||
"anchor_block_faces": 0,
|
||||
"probe_embedded": false
|
||||
},
|
||||
"backend": "Vulkan",
|
||||
"clients": [
|
||||
{
|
||||
"client": "client-a",
|
||||
"model": {
|
||||
"anchor_model_quads": 0,
|
||||
"anchor_render_shape": "INVISIBLE",
|
||||
"shaders_active": true,
|
||||
"pbr_enabled": true,
|
||||
"pbr_applied": true,
|
||||
"anchor_pbr_opaque": false,
|
||||
"water_pbr_opaque": false,
|
||||
"wood_pbr_opaque": true,
|
||||
"anchor_pbr_transparent": false,
|
||||
"water_pbr_transparent": true,
|
||||
"glass_pbr_transparent": true
|
||||
},
|
||||
"vulkan_video_uploads": 1625
|
||||
},
|
||||
{
|
||||
"client": "client-b",
|
||||
"model": {
|
||||
"anchor_model_quads": 0,
|
||||
"anchor_render_shape": "INVISIBLE",
|
||||
"shaders_active": true,
|
||||
"pbr_enabled": true,
|
||||
"pbr_applied": true,
|
||||
"anchor_pbr_opaque": false,
|
||||
"water_pbr_opaque": false,
|
||||
"wood_pbr_opaque": true,
|
||||
"anchor_pbr_transparent": false,
|
||||
"water_pbr_transparent": true,
|
||||
"glass_pbr_transparent": true
|
||||
},
|
||||
"vulkan_video_uploads": 1448
|
||||
}
|
||||
],
|
||||
"before": {
|
||||
"version": "beta.297",
|
||||
"clients": [
|
||||
{
|
||||
"client": "client-a",
|
||||
"anchor_model_quads": 30,
|
||||
"anchor_render_shape": "INVISIBLE",
|
||||
"shaders_active": true,
|
||||
"pbr_enabled": true,
|
||||
"anchor_pbr_opaque": true,
|
||||
"water_pbr_opaque": false,
|
||||
"wood_pbr_opaque": true,
|
||||
"anchor_pbr_transparent": false,
|
||||
"water_pbr_transparent": true,
|
||||
"glass_pbr_transparent": true
|
||||
},
|
||||
{
|
||||
"client": "client-b",
|
||||
"anchor_model_quads": 30,
|
||||
"anchor_render_shape": "INVISIBLE",
|
||||
"shaders_active": true,
|
||||
"pbr_enabled": true,
|
||||
"anchor_pbr_opaque": true,
|
||||
"water_pbr_opaque": false,
|
||||
"wood_pbr_opaque": true,
|
||||
"anchor_pbr_transparent": false,
|
||||
"water_pbr_transparent": true,
|
||||
"glass_pbr_transparent": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"check_build_assemble": "passed",
|
||||
"duration": "3m06s",
|
||||
"tasks": 162,
|
||||
"required_gametests": 18,
|
||||
"client_checks_compile": "passed, 37s, 6 tasks; historical PBR graphic suite not rerun"
|
||||
},
|
||||
"limits": [
|
||||
"No new live synchronization or public-source validation",
|
||||
"Graphical wall faces south",
|
||||
"Canonical Prism and dedicated Beta unchanged",
|
||||
"One automatic fallback after concurrent MoltenVK extraction was rejected and the client relaunched in Vulkan",
|
||||
"Client B stopped after valid samples and captures because the probe could not write its log: No space left on device; not an endurance test"
|
||||
],
|
||||
"graphics_checks": {
|
||||
"lower_left_front_video": "inspected, no 1x1 frame imprint",
|
||||
"lower_left_grazing_video": "inspected, no 1x1 frame imprint",
|
||||
"placement_resize_reader": "passed; placement replayed after inventory arrival",
|
||||
"final_screenshots": [
|
||||
"anchor-model-shaders-front-settled.png",
|
||||
"anchor-model-shaders-side-settled.png"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
# MEDIA-06 — interaction et épaisseur des cadres
|
||||
|
||||
Retours du 26 octobre 2026 après essai de beta.296. Branche
|
||||
`codex/media-frame-fixes`, **beta.297**, module Media **0.3.1**.
|
||||
[Livraison complète du 27 octobre 2026](release-beta297.md).
|
||||
|
||||
## Comportement demandé
|
||||
|
||||
Le menu largeur/hauteur s'ouvre automatiquement seulement à la pose. Ensuite,
|
||||
clic droit et **Maj + clic droit** ouvrent le lecteur Dream. Le bouton
|
||||
**Taille / Size** de ce lecteur reste le point d'entrée pour redimensionner,
|
||||
avec les mêmes vérifications serveur de proximité et de construction.
|
||||
Appliquer une taille garde le menu ouvert pour l'ajuster ; aucune modification
|
||||
du coût, de la recette ou des tailles autorisées.
|
||||
|
||||
Les trois chemins sont alignés : raycast client, paquet d'ouverture serveur
|
||||
et interaction directe avec le bloc. La pose initiale conserve sa demande
|
||||
explicite du menu de taille.
|
||||
|
||||
## Rendu
|
||||
|
||||
Le pixel rouge était un repère d'ancrage ajouté par Sanctuary. Il est retiré
|
||||
du cadre du monde et de l'aperçu de taille.
|
||||
|
||||
Le cadre précédent utilisait le repère vidéo Dream. Celui-ci applique une
|
||||
échelle Z nulle : c'est un dessin plat, sans faces latérales. Le nouveau
|
||||
maillage est un anneau fermé en unités de blocs : quatre bandes avant,
|
||||
quatre arrière, quatre côtés extérieurs et quatre faces de feuillure.
|
||||
Épaisseur et largeur du bois : **1/16 de bloc**, indépendamment de la taille.
|
||||
Le contour extérieur est exactement celui de l'écran, au contact du mur.
|
||||
La vidéo conserve son plan et ses proportions, en retrait du bois.
|
||||
|
||||
Les sommets sont calculés depuis la géométrie de l'ancre et ramenés dans le
|
||||
repère du renderer Dream sans passer par sa transformation vidéo aplatie.
|
||||
Ils gardent les mêmes positions dans les passes normale et de reprise :
|
||||
aucune deuxième bordure décalée. Le pipeline Vulkan Dream et sa profondeur
|
||||
restent utilisés, sans nouveau shader.
|
||||
|
||||
La vidéo et les sous-titres sont masqués depuis l'arrière du plan, et depuis
|
||||
les côtés lorsque l'observateur est derrière la face avant du bois et hors
|
||||
de l'ouverture. Cela évite un liseré vidéo en vue rasante ; une caméra située
|
||||
dans l'ouverture conserve l'image. Le cadre en volume reste visible.
|
||||
Le son reste actif, et les uploads vidéo
|
||||
sont suspendus comme pour un écran hors champ. Les écrans Dream ordinaires
|
||||
gardent leur rendu.
|
||||
|
||||
L'ancre invisible ne dessine plus son contour de sélection vanilla 1 × 1 à
|
||||
l'intérieur d'un grand écran. Sa forme d'occlusion est explicitement vide ;
|
||||
sa forme de ciblage reste disponible pour casser le bloc. Les validations
|
||||
du mur et le retrait de l'écran côté serveur sont conservés.
|
||||
|
||||
## Données et livraison
|
||||
|
||||
Aucun identifiant, paquet ou format de sauvegarde ne change. Les écrans posés
|
||||
en beta.296 retrouvent le nouveau rendu à partir de leurs données existantes,
|
||||
sans remplacement de blocs ni conversion de monde. Aucun monde personnel
|
||||
ou serveur personnel n'est utilisé pour la validation.
|
||||
|
||||
Le correctif est publié en beta.297 et installé sur la Beta, le site et la
|
||||
même instance Prism. Les essais de développement ont précédé cette livraison.
|
||||
|
||||
## Validation
|
||||
|
||||
- `check build assemblePack assembleServerPack` : **réussi en 4 min 17 s**,
|
||||
158 tâches, **18 GameTests requis** (`property270,claims272,claims277,claims278`).
|
||||
- Contrat Java du maillage : 16 faces non dégénérées, bornes exactes et
|
||||
épaisseur constante sur quatre tailles et les quatre orientations ; vidéo
|
||||
visible dans l'ouverture et masquée derrière le bois depuis les côtés.
|
||||
- **17 contrôles interactifs** réussis : nouvelle pose, réouverture normale,
|
||||
Maj réellement maintenue, bouton Taille et protections/inventaire existants.
|
||||
- Après correction du dernier liseré vidéo : contrats Media et assemblages
|
||||
ciblés réussis en **21 s**, puis nouvel essai avec le JAR final embarqué.
|
||||
- Deux clients **Vulkan**, Sodium et Sound Physics, vidéo synthétique sur
|
||||
écran **9 × 5**. Captures inspectées de face, de droite, de gauche, du haut
|
||||
et de l'arrière : côtés présents, aucun repère rouge ni fente colorée.
|
||||
- Depuis droite/gauche/haut/arrière : uploads de l'image arrêtés et curseur
|
||||
OpenAL toujours en mouvement ; retour devant : uploads repris.
|
||||
- Sept tests du rapport et compilation syntaxique Python réussis.
|
||||
|
||||
Le premier laboratoire `frame-fixes-01` valide l'interaction ; ses premières
|
||||
vues rasantes ont révélé le liseré restant. Seules les captures de
|
||||
`build/media-prototype/frame-fixes-02/` valident son retrait final. Le maillage
|
||||
est testé numériquement sur quatre orientations ; le parcours graphique est
|
||||
orienté sud. Aucune nouvelle validation de live public ou d'écoute subjective.
|
||||
|
||||
[Relevé structuré](media-frame-fixes-results.json). Le
|
||||
[reçu de livraison](release-beta297.md) distingue ces essais du déploiement
|
||||
et de ses vérifications opérationnelles.
|
||||
@@ -0,0 +1,77 @@
|
||||
{
|
||||
"ticket": "MEDIA-06",
|
||||
"version": "beta.297",
|
||||
"media_version": "0.3.1",
|
||||
"state": "published and installed on Beta server, Web distribution and existing Prism instance",
|
||||
"delivery": {
|
||||
"date": "2026-10-27",
|
||||
"receipt": "release-beta297.md",
|
||||
"source_tag": "bad2bdb0d12295a736aee64e08b9f6f5a8ae81d3",
|
||||
"packwiz_commit": "e40b0d372af9fa43a301b842661c131f4e69f7c7",
|
||||
"fresh_check_build_assemble": "passed, 2m57s, 158 tasks, 18 required GameTests; identical JAR to final Vulkan validation"
|
||||
},
|
||||
"branch": "codex/media-frame-fixes",
|
||||
"artifact_sha256": "f9bfffb0198abb8a4eae8a6d76a1ad3f1190edda7ce33f719bd47f0f4da27d9d",
|
||||
"interaction_suite": {
|
||||
"passed": [
|
||||
"one frame consumed on placement",
|
||||
"first placement opens dimensions",
|
||||
"reopening near the anchor opens native player",
|
||||
"Shift-right-click also opens native player",
|
||||
"custom 7x4 and free enlargement",
|
||||
"shrinking does not refund frames",
|
||||
"resizing keeps the same display",
|
||||
"oversize refused",
|
||||
"incomplete wall refused",
|
||||
"stale revision refused",
|
||||
"native delete, overlap, obstacle and unloaded chunks refused",
|
||||
"building permission required",
|
||||
"original Dream reader on remote overlay while holding frame",
|
||||
"forged remote resize refused",
|
||||
"wall blocks normal interaction",
|
||||
"wall blocks forged open packet",
|
||||
"original Dream reader available to second spectator"
|
||||
]
|
||||
},
|
||||
"final_graphics_checks": {
|
||||
"placement_opens_dimensions": true,
|
||||
"ordinary_click_player": true,
|
||||
"shift_click_player": true,
|
||||
"right_video_hidden_audio_continues": true,
|
||||
"left_video_hidden_audio_continues": true,
|
||||
"top_video_hidden_audio_continues": true,
|
||||
"back_video_hidden_audio_continues": true,
|
||||
"front_picture_resumes": true
|
||||
},
|
||||
"environment": {
|
||||
"minecraft": "26.3",
|
||||
"backend": "Vulkan",
|
||||
"clients": 2,
|
||||
"dream": "1.10.4",
|
||||
"sound_physics": "1.5.1+26.3",
|
||||
"screen_blocks": [
|
||||
9,
|
||||
5
|
||||
],
|
||||
"camera_views": [
|
||||
"front",
|
||||
"right grazing",
|
||||
"left grazing",
|
||||
"top grazing",
|
||||
"back",
|
||||
"front after hidden"
|
||||
]
|
||||
},
|
||||
"validation": {
|
||||
"full_check_build_assemble": "passed, 4m17s, 158 tasks, 18 required GameTests",
|
||||
"final_client_visibility_contracts_and_assembly": "passed, 21s, 27 tasks",
|
||||
"image_inspection": "five final views inspected; no red marker, no colored video slit through sides",
|
||||
"python_report_tests": 7
|
||||
},
|
||||
"diagnostic": "frame-fixes-01 grazing images precede the final video visibility fix; final images are frame-fixes-02",
|
||||
"limits": [
|
||||
"No new public streaming or audio listening validation",
|
||||
"Graphical wall faces south; all four orientations checked numerically",
|
||||
"Graphical tests used development instances; deployment to the existing personal installation is documented in the delivery receipt, no personal world opened"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Sanctuary beta.297
|
||||
|
||||
Le menu de dimensions s'ouvre automatiquement à la première pose du cadre.
|
||||
Ensuite, clic droit et Maj + clic droit ouvrent directement le lecteur Dream ;
|
||||
le bouton Taille / Size permet toujours de redimensionner près de l'ancre.
|
||||
|
||||
Le cadre possède maintenant une vraie épaisseur de 1/16 de bloc, avec des
|
||||
faces latérales qui suivent exactement la surface de l'écran. Le repère rouge,
|
||||
le contour de sélection du bloc invisible et les liserés vidéo visibles de
|
||||
côté sont retirés. Le son continue lorsque l'image est masquée.
|
||||
|
||||
Les écrans déjà posés en beta.296 conservent leur source, leurs dimensions et
|
||||
leurs données. Aucun bloc à remplacer et aucun changement de format de monde.
|
||||
Media passe en 0.3.1 ; Minecraft 26.3, Fabric, Dream Displays 1.10.4 et les
|
||||
autres dépendances restent inchangés.
|
||||
|
||||
Correctif vérifié sous Vulkan avec deux clients, 17 contrôles d'interaction
|
||||
et des vues de face, des côtés, du haut et de l'arrière.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Sanctuary beta.298
|
||||
|
||||
Le petit cadre brillant dans le coin inférieur gauche de la vidéo est corrigé
|
||||
avec les shaders Sanctuary activés. Le modèle invisible de l'ancre ne fournit
|
||||
plus de faces à la passe des normales PBR. Le grand cadre conserve sa taille
|
||||
et son épaisseur ; le lecteur et le redimensionnement gardent leurs gestes.
|
||||
|
||||
Les écrans déjà posés conservent leur source, leurs dimensions et leurs données.
|
||||
Aucun bloc à remplacer ni conversion de sauvegarde. Media passe en 0.3.2 ;
|
||||
Minecraft 26.3, Fabric et Dream Displays 1.10.4 restent inchangés.
|
||||
|
||||
Correctif vérifié à deux clients Vulkan avec PBR actif, vidéo en mouvement et
|
||||
gros plans de face et en vue rasante. Le détail des contrôles et leurs limites
|
||||
est dans `docs/media-anchor-normals-beta298.md` ; pas de nouvelle validation
|
||||
des sources publiques ni de l'endurance du lecteur.
|
||||
@@ -0,0 +1,107 @@
|
||||
# beta.297 — correctifs des cadres installés
|
||||
|
||||
Livraison complète du **27 octobre 2026**, branche `codex/media-frame-fixes`.
|
||||
[Comportement et validation graphique](media-frame-fixes-beta297.md),
|
||||
[notes de version](notes-beta297.md). Le socle beta.296 est conservé.
|
||||
|
||||
## Résultat en jeu
|
||||
|
||||
Le menu de dimensions apparaît automatiquement à la première pose. Ensuite,
|
||||
clic droit et **Maj + clic droit** ouvrent le lecteur Dream ; son bouton
|
||||
**Taille / Size** permet de redimensionner. Le repère rouge est retiré.
|
||||
Le cadre possède des faces latérales et suit les dimensions de l'écran ;
|
||||
la vidéo ne laisse plus de liseré coloré à travers le bois en vue rasante.
|
||||
Le son continue lorsque l'image est masquée. Les écrans existants conservent
|
||||
leurs dimensions, source et identité, sans conversion de données.
|
||||
|
||||
## Cibles vérifiées
|
||||
|
||||
| Cible | État |
|
||||
| --- | --- |
|
||||
| Source et tag `beta.297` | `bad2bdb0d12295a736aee64e08b9f6f5a8ae81d3` |
|
||||
| Canal client/serveur | `e40b0d372af9fa43a301b842661c131f4e69f7c7` |
|
||||
| Release | [beta.297](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.297), quatre artefacts publics vérifiés |
|
||||
| CT 350 | Service actif, Sanctuary beta.297, Media 0.3.1 embarqué et Dream Displays 1.10.4 chargé |
|
||||
| CT 320 | Distribution `releases/beta.297/prism`, ancienne beta.296 conservée |
|
||||
| Prism historique | Même instance Sanctuary Beta, neuf JAR, deux synchronisations identiques |
|
||||
|
||||
Minecraft 26.3, Fabric 0.19.5, Java 25 et les dépendances restent inchangés.
|
||||
Un seul JAR Sanctuary inclut JEI et Media ; Dream Displays reste un JAR
|
||||
distinct obligatoire côté client et serveur. Le serveur comporte cinq JAR.
|
||||
Le tag immuable désigne les sources de construction ; le présent reçu est
|
||||
ajouté ensuite sans déplacer ce tag.
|
||||
|
||||
## Vérifications
|
||||
|
||||
- Nouvelle exécution `check build assemblePack assembleServerPack` réussie
|
||||
en **2 min 57 s**, 158 tâches, **18 GameTests requis**, profil
|
||||
`property270,claims272,claims277,claims278`, atlas ciblé. Contrats Media
|
||||
inclus. Le JAR reconstruit a exactement le hash du JAR final testé sous Vulkan.
|
||||
- Avant livraison : **17 contrôles interactifs** et deux clients **Vulkan**,
|
||||
Sodium et Sound Physics, sur un écran 9 × 5. Captures finales de face,
|
||||
droite, gauche, haut et arrière inspectées : aucun repère rouge ni liseré
|
||||
vidéo, volume du cadre visible. Image suspendue hors ouverture, curseur
|
||||
OpenAL actif, uploads repris en revenant devant. Les preuves et limites
|
||||
sont dans [MEDIA-06](media-frame-fixes-beta297.md).
|
||||
- Archives, plateformes, profils client/serveur et dépendances comparés à
|
||||
leurs hashes. Deux synchronisations du véritable installateur dans une
|
||||
instance isolée, puis deux dans l'instance personnelle : second passage
|
||||
identique, aucun doublon Sanctuary, sélections facultatives conservées.
|
||||
- Redémarrage CT 350 terminé (`Done`), versions Sanctuary, Media et Dream
|
||||
confirmées dans le journal. Ping DNS/SRV depuis le LAN : Minecraft 26.3,
|
||||
0/20 lors du contrôle, logo serveur inchangé.
|
||||
- ZIP et MRpack publics : HTTP 200 et SHA-256 attendus. Le bouton du site
|
||||
donne `Sanctuary-Beta.zip` ; le HTML annonce beta.297 et garde le lien direct.
|
||||
- Admission : faux code refusé en HTTP 200 (`invalid`), aucun redeem.
|
||||
Métriques Web fraîches, serveur en marche, même UUID communautaire
|
||||
`cc6f4f5a-e58d-409a-879f-81daecf59461` côté jeu et site.
|
||||
|
||||
Les essais graphiques précèdent cette opération de publication et utilisent
|
||||
le même binaire. Aucune nouvelle séance graphique ni mesure de synchronisation
|
||||
vidéo/live n'est revendiquée pour le déploiement du 27 octobre.
|
||||
|
||||
## Artefacts
|
||||
|
||||
| Artefact | Octets | SHA-256 |
|
||||
| --- | ---: | --- |
|
||||
| `sanctuary-beta.297.jar` | 18301448 | `f9bfffb0198abb8a4eae8a6d76a1ad3f1190edda7ce33f719bd47f0f4da27d9d` |
|
||||
| `Sanctuary-beta.297.mrpack` | 16890964 | `89a031a85015615cfcf68c492bf1a73d32e0b10261c36f68045c985dc78d6886` |
|
||||
| `Sanctuary-Prism-beta.297.zip` | 3928525 | `95c57f5e70627a2847d1cc2033c8d9f50f44ef4a302ca892bc727e0c37ece0a9` |
|
||||
| `Sanctuary-Server-beta.297.zip` | 4036720 | `f31bcf2c83929028a29a51eae53b37eae2bb7fa8253dc124acad1e2ac2846af0` |
|
||||
|
||||
Les ZIP d'amorçage sont identiques à beta.296 : mêmes outils et plateforme,
|
||||
canal stable maintenant en beta.297. Le MRpack contient le nouveau JAR.
|
||||
|
||||
## Conservation
|
||||
|
||||
Serveur vide avant arrêt propre. Sauvegarde complète privée :
|
||||
`/root/sanctuary-beta-backups/before-beta297-20261027T035832Z/instance.tar.gz`,
|
||||
SHA-256 `6b7fc4b5a6569e8cd3a9bab7e138a67b4344fde84a5dfdd453db9fec273ccdc5`.
|
||||
Les **176 fichiers protégés** sont identiques après synchronisation et avant
|
||||
redémarrage. Environnement privé et toutes les clés/valeurs de
|
||||
`server.properties` sont identiques après démarrage. Aucune régénération ni
|
||||
migration de monde.
|
||||
|
||||
Prism : sauvegarde sous la même instance,
|
||||
`sanctuary-backups/before-beta297-20261027T035256Z/instance.tar.gz`, SHA-256
|
||||
`0c95dfd13aa45eb26ea62b16465c87598838a19b2835b7b641d197167c92af09`.
|
||||
Les **933 fichiers personnels contrôlés**, `instance.cfg` et l'installateur
|
||||
habillé Sanctuary sont inchangés. Le lien FFmpeg existant est conservé.
|
||||
Aucun monde personnel ouvert. L'autre instance importée suit le canal au
|
||||
prochain lancement ; elle n'a pas été réimportée ni modifiée manuellement.
|
||||
|
||||
Web : HEAD `eadc30e8c7b6dda50bc810584af057235e7fdba8` et **73 changements
|
||||
préexistants** conservés, empreintes Git avant/après identiques. Seule la
|
||||
distribution atomique est avancée. Alpha, réseau/NAT, comptes et permissions
|
||||
ne sont pas modifiés ; le checkout principal du chantier voisin est préservé.
|
||||
|
||||
## Limites
|
||||
|
||||
Twitch/YouTube publics et l'écoute subjective n'ont pas de nouvelle validation.
|
||||
Les essais graphiques utilisent une vidéo synthétique, un mur orienté sud ;
|
||||
les quatre orientations sont vérifiées numériquement. ANI-CLI, le filtre
|
||||
16 pixels/bloc et les disques blancs ne sont pas inclus. Les dimensions
|
||||
restent entières, dans le budget serveur, sur un mur complet.
|
||||
|
||||
Reçus opérationnels : `build/release297/`, hors Git. Captures graphiques
|
||||
finales : `build/media-prototype/frame-fixes-02/`.
|
||||
+1
-1
@@ -8,7 +8,7 @@ Plateforme épinglée : Minecraft **26.3**, Fabric Loader **0.19.5**, Fabric API
|
||||
## Préparer le kit
|
||||
|
||||
Dans les sources : `./gradlew check build assemblePack assembleServerPack`.
|
||||
Le kit de la livraison courante est `build/Sanctuary-Server-beta.296.zip` ; les manifestes locaux sont
|
||||
Le kit de la livraison courante est `build/Sanctuary-Server-beta.298.zip` ; les manifestes locaux sont
|
||||
dans `build/packwiz-server/`. Le ZIP est un amorçage : il télécharge les mods
|
||||
depuis le canal au premier démarrage, il n'est pas une copie hors ligne du pack.
|
||||
Il n'est utilisable sur le canal public qu'après publication de cette version.
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@ loom_version=1.17.20
|
||||
fabric_api_version=0.160.5+26.3
|
||||
|
||||
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
|
||||
mod_version=beta.296
|
||||
pack_version=beta.296
|
||||
mod_version=beta.298
|
||||
pack_version=beta.298
|
||||
resource_pack_version=beta.121
|
||||
maven_group=fr.koka.sanctuary
|
||||
jei_version=30.32.0-sanctuary.4
|
||||
|
||||
@@ -41,6 +41,10 @@ public final class AnchorProbe {
|
||||
}
|
||||
case "unindex"->{var a=(AnchorBlockEntity)level.getBlockEntity(POS);Anchors.unindex(a);assertions.addProperty("index_removed",Anchors.loaded(level).isEmpty());}
|
||||
case "near"->move(p,-3.5,81,4);
|
||||
case "camera"->{
|
||||
var v=ctl.getAsJsonArray("camera_a");p.setNoGravity(true);
|
||||
move(p,v.get(0).getAsDouble(),v.get(1).getAsDouble(),v.get(2).getAsDouble());
|
||||
}
|
||||
case "wall"->{for(var q:BlockPos.betweenClosed(-6,81,6,6,86,6))level.setBlockAndUpdate(q,Blocks.STONE.defaultBlockState());}
|
||||
case "clear-wall"->{for(var q:BlockPos.betweenClosed(-6,81,6,6,86,6))level.setBlockAndUpdate(q,Blocks.AIR.defaultBlockState());}
|
||||
case "remove-support"->level.setBlockAndUpdate(new BlockPos(2,84,0),Blocks.AIR.defaultBlockState());
|
||||
@@ -66,6 +70,7 @@ public final class AnchorProbe {
|
||||
if(a!=null){row.addProperty("width",a.width);row.addProperty("height",a.height);row.addProperty("revision",a.revision);row.addProperty("display",String.valueOf(a.display));var d=com.dreamdisplays.api.server.DreamDisplaysServer.display(a.display);if(d!=null){row.addProperty("url",d.getUrl());row.addProperty("mode",d.getMode().name());}}
|
||||
row.addProperty("anchor",a!=null);
|
||||
if(p!=null){int n=0;for(int i=0;i<p.getInventory().getContainerSize();i++){var st=p.getInventory().getItem(i);if(st.is(Anchors.ITEM))n+=st.getCount();}row.addProperty("frames_in_inventory",n);}
|
||||
if(p!=null)row.addProperty("crouching",p.isShiftKeyDown());
|
||||
int drops=level.getEntitiesOfClass(ItemEntity.class,new AABB(-6,80,-1,6,87,5)).stream().filter(e->e.getItem().is(Anchors.ITEM)).mapToInt(e->e.getItem().getCount()).sum();row.addProperty("frame_drops",drops);
|
||||
row.addProperty("displays",com.dreamdisplays.api.server.DreamDisplaysServer.isAvailable()?com.dreamdisplays.api.server.DreamDisplaysServer.getDisplays().getSize():-1);
|
||||
Probe.write(Probe.LAB.resolve("anchor-server.jsonl"),row);
|
||||
|
||||
@@ -20,6 +20,11 @@ public final class AnchorProbeClient {
|
||||
public static void tick(Minecraft mc){
|
||||
if(mc.player==null||mc.level==null)return;
|
||||
var ctl=Probe.control();String who=System.getProperty("sanctuary.mediaClient");
|
||||
if(ctl.has("shaders_enabled")&&fr.koka.sanctuary.client.shader.VanillaLight.enabled()!=ctl.get("shaders_enabled").getAsBoolean())
|
||||
fr.koka.sanctuary.client.shader.VanillaLight.setEnabled(ctl.get("shaders_enabled").getAsBoolean());
|
||||
if(ctl.has("pbr_enabled")&&fr.koka.sanctuary.client.shader.VanillaLight.pbr()!=ctl.get("pbr_enabled").getAsBoolean())
|
||||
fr.koka.sanctuary.client.shader.VanillaLight.setPbr(ctl.get("pbr_enabled").getAsBoolean());
|
||||
if(ctl.has("hide_gui")&&mc.gui.hud.isHidden()!=ctl.get("hide_gui").getAsBoolean())mc.gui.hud.toggle();
|
||||
boolean a=who.endsWith("a");
|
||||
Vec3 aim=a?new Vec3(-3.5,81.5,.5):new Vec3(0,83,1);
|
||||
if(ctl.has(a?"aim_a":"aim_b")){var xs=ctl.getAsJsonArray(a?"aim_a":"aim_b");aim=new Vec3(xs.get(0).getAsDouble(),xs.get(1).getAsDouble(),xs.get(2).getAsDouble());}
|
||||
@@ -32,6 +37,7 @@ public final class AnchorProbeClient {
|
||||
net.minecraft.client.Screenshot.grab(mc.gameDirectory,"anchor-"+Probe.phase+".png",mc.gameRenderer.mainRenderTarget(),1,m->{});captured=Probe.phase;
|
||||
}
|
||||
var row=new JsonObject();row.addProperty("step",done);row.addProperty("screen",mc.gui.screen()==null?"":mc.gui.screen().getClass().getSimpleName());
|
||||
if(ctl.has("inspect_anchor_model"))inspectModel(mc,row);
|
||||
var target=Anchors.target(mc.level,mc.player);row.addProperty("target",target!=null);if(target!=null)row.addProperty("distance",target.distance());
|
||||
if(mc.gui.screen() instanceof AnchorScreen screen){var v=screen.snapshot();row.addProperty("settings",v.settings());row.addProperty("editable",v.editable());row.addProperty("message",v.message());row.addProperty("width",v.width());row.addProperty("height",v.height());}
|
||||
else if(mc.gui.screen()!=null&&mc.gui.screen().getClass().getSimpleName().equals("DisplayMenu")){
|
||||
@@ -39,11 +45,34 @@ public final class AnchorProbeClient {
|
||||
}
|
||||
Probe.write(Probe.LAB.resolve("anchor-"+who+".jsonl"),row);
|
||||
}
|
||||
private static void inspectModel(Minecraft mc,JsonObject row){
|
||||
var state=mc.level.getBlockState(AnchorProbe.POS);
|
||||
if(!state.is(Anchors.BLOCK))return;
|
||||
var parts=new java.util.ArrayList<net.minecraft.client.renderer.block.dispatch.BlockStateModelPart>();
|
||||
mc.getModelManager().getBlockStateModelSet().get(state).collectParts(net.minecraft.util.RandomSource.create(0),parts);
|
||||
int quads=0;for(var part:parts)for(int f=0;f<7;f++)quads+=part.getQuads(f==6?null:Direction.values()[f]).size();
|
||||
row.addProperty("anchor_model_quads",quads);row.addProperty("anchor_render_shape",state.getRenderShape().name());
|
||||
row.addProperty("shaders_active",fr.koka.sanctuary.client.shader.VanillaLight.active());
|
||||
row.addProperty("pbr_enabled",fr.koka.sanctuary.client.shader.VanillaLight.pbr());
|
||||
try{
|
||||
var pbr=fr.koka.sanctuary.client.shader.ProceduralPbr.class.getDeclaredMethod("applied");pbr.setAccessible(true);
|
||||
row.addProperty("pbr_applied",(boolean)pbr.invoke(null));
|
||||
var type=Class.forName("fr.koka.sanctuary.client.shader.PbrTerrain");var ctor=type.getDeclaredConstructor(boolean.class);ctor.setAccessible(true);
|
||||
var candidate=type.getDeclaredMethod("candidate",net.minecraft.world.level.block.state.BlockState.class);candidate.setAccessible(true);
|
||||
for(boolean transparent:new boolean[]{false,true})try(var terrain=(AutoCloseable)ctor.newInstance(transparent)){
|
||||
row.addProperty(transparent?"anchor_pbr_transparent":"anchor_pbr_opaque",(boolean)candidate.invoke(terrain,state));
|
||||
row.addProperty(transparent?"water_pbr_transparent":"water_pbr_opaque",(boolean)candidate.invoke(terrain,net.minecraft.world.level.block.Blocks.WATER.defaultBlockState()));
|
||||
row.addProperty(transparent?"glass_pbr_transparent":"wood_pbr_opaque",(boolean)candidate.invoke(terrain,(transparent?net.minecraft.world.level.block.Blocks.GLASS:net.minecraft.world.level.block.Blocks.OAK_PLANKS).defaultBlockState()));
|
||||
}
|
||||
}catch(Exception e){throw new IllegalStateException(e);}
|
||||
}
|
||||
private static boolean perform(Minecraft mc,String step,JsonObject ctl){
|
||||
if(step.equals("place")){
|
||||
mc.gameMode.useItemOn(mc.player,InteractionHand.MAIN_HAND,new BlockHitResult(new Vec3(-3.5,81.5,1),Direction.SOUTH,new BlockPos(-4,81,0),false));return true;
|
||||
}
|
||||
if(step.equals("close")){mc.gui.setScreen(null);return true;}
|
||||
if(step.equals("shift-hold")){mc.gui.setScreen(null);mc.options.keyShift.setDown(true);return true;}
|
||||
if(step.equals("shift-release")){mc.options.keyShift.setDown(false);return true;}
|
||||
if(step.equals("unindex")){for(var a:Anchors.loaded(mc.level))Anchors.unindex(a);return true;}
|
||||
if(step.equals("click")){mc.gui.setScreen(null);Access.privateCall(mc,"startUseItem");return true;}
|
||||
if(step.equals("forged-open")){
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Sanctuary Media — prototypes MEDIA-03/04
|
||||
|
||||
Module **0.3.0**, embarqué dans Sanctuary depuis **beta.296**,
|
||||
Module **0.3.1**, embarqué dans Sanctuary depuis **beta.296**,
|
||||
pour Dream Displays **1.10.4** sur Minecraft **26.3 / Java 25 / Fabric**.
|
||||
Les clients décodent les médias ; le serveur fournit l'heure et garde l'état
|
||||
partagé ainsi que la validation des constructions.
|
||||
@@ -9,9 +9,9 @@ Compatibilité acoustique vérifiée avec Sound Physics Remastered **1.5.1+26.3*
|
||||
L'extension conserve le moteur et le **menu original Dream Displays** pour la
|
||||
recherche, le collage d'un lien, l'aperçu, la lecture, les pistes et les réglages.
|
||||
Elle ajoute le cadre de redstone comme ancre : un objet à la pose, aucun coût
|
||||
d'agrandissement, contour dessiné sur toute la surface. Maj + clic droit près
|
||||
de l'ancre ou bouton Taille du menu natif ouvre le seul écran ajouté : largeur
|
||||
et hauteur, formats rapides facultatifs et aperçu du ratio.
|
||||
d'agrandissement, cadre en volume sur toute la surface. Le menu largeur/hauteur
|
||||
s'ouvre à la pose, puis via le bouton Taille du lecteur. Clic droit et Maj +
|
||||
clic droit ouvrent le lecteur. [Correctif et validation beta.297](../../docs/media-frame-fixes-beta297.md).
|
||||
|
||||
Les dimensions sont libres en blocs entiers. Les budgets du serveur sont dans
|
||||
`config/sanctuary-media-anchors.json` (`maxWidth`, `maxHeight`, `maxArea`) ; une
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
plugins { id 'java-library' }
|
||||
|
||||
group = rootProject.maven_group
|
||||
version = '0.3.0'
|
||||
version = '0.3.2'
|
||||
base { archivesName = 'sanctuary-media' }
|
||||
evaluationDependsOn(':sanctuary')
|
||||
|
||||
|
||||
@@ -28,9 +28,10 @@ public final class AnchorBlock extends BaseEntityBlock {
|
||||
};
|
||||
}
|
||||
@Override protected VoxelShape getCollisionShape(BlockState s,BlockGetter l,BlockPos p,CollisionContext c){return Shapes.empty();}
|
||||
@Override protected VoxelShape getOcclusionShape(BlockState s){return Shapes.empty();}
|
||||
@Override protected InteractionResult useWithoutItem(BlockState s,Level l,BlockPos pos,Player p,BlockHitResult hit){
|
||||
if(p instanceof net.minecraft.server.level.ServerPlayer sp&&l.getBlockEntity(pos) instanceof AnchorBlockEntity a)
|
||||
Anchors.open(sp,a,p.isShiftKeyDown());
|
||||
Anchors.open(sp,a,false);
|
||||
return InteractionResult.SUCCESS;
|
||||
}
|
||||
@Override protected InteractionResult useItemOn(ItemStack stack,BlockState s,Level l,BlockPos pos,Player p,InteractionHand hand,BlockHitResult hit){return useWithoutItem(s,l,pos,p,hit);}
|
||||
|
||||
@@ -29,7 +29,7 @@ public final class AnchorClient {
|
||||
var hit=Anchors.target(mc.level,mc.player);
|
||||
if(hit==null)return false;
|
||||
var a=hit.info();
|
||||
ClientPlayNetworking.send(new AnchorPackets.Action(a.geometry().anchor(),a.display(),mc.player.isShiftKeyDown()?6:0,0,0,a.revision(),"",0));
|
||||
ClientPlayNetworking.send(new AnchorPackets.Action(a.geometry().anchor(),a.display(),0,0,0,a.revision(),"",0));
|
||||
return true;
|
||||
}
|
||||
public static void openReader(UUID id){
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package fr.koka.media.anchor;
|
||||
|
||||
import java.util.*;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
/** A closed wooden ring, in block units, flush to the wall and exactly inside the screen bounds. */
|
||||
public final class AnchorFrameMesh {
|
||||
public static final double THICKNESS=1.0/16;
|
||||
public record Vertex(double u,double v,double depth){}
|
||||
public record Face(Vertex a,Vertex b,Vertex c,Vertex d,int color){
|
||||
public List<Vertex> vertices(){return List.of(a,b,c,d);}
|
||||
}
|
||||
public final int width,height;
|
||||
public final List<Face> faces;
|
||||
public AnchorFrameMesh(int width,int height){
|
||||
if(width<1||height<1)throw new IllegalArgumentException("Invalid frame size");
|
||||
this.width=width;this.height=height;
|
||||
var result=new ArrayList<Face>();double t=THICKNESS,w=width,h=height;
|
||||
for(double z:new double[]{t,0}){
|
||||
boolean back=z==0;
|
||||
rect(result,0,0,w,t,z,0x77512f,back);
|
||||
rect(result,0,h-t,w,h,z,0xa47a48,back);
|
||||
rect(result,0,t,t,h-t,z,0x90653a,back);
|
||||
rect(result,w-t,t,w,h-t,z,0x694426,back);
|
||||
}
|
||||
// Outer sides, with outward winding.
|
||||
side(result,0,0,w,0,0x684628);
|
||||
side(result,w,0,w,h,0x795330);
|
||||
side(result,w,h,0,h,0x9a7141);
|
||||
side(result,0,h,0,0,0x876038);
|
||||
// Inner reveal faces point into the opening around the recessed video.
|
||||
side(result,w-t,t,t,t,0x51351e);
|
||||
side(result,w-t,h-t,w-t,t,0x51351e);
|
||||
side(result,t,h-t,w-t,h-t,0x684628);
|
||||
side(result,t,t,t,h-t,0x684628);
|
||||
faces=List.copyOf(result);
|
||||
}
|
||||
private static void rect(List<Face> out,double x0,double y0,double x1,double y1,double z,int color,boolean back){
|
||||
var a=new Vertex(x0,y0,z);var b=new Vertex(x1,y0,z);var c=new Vertex(x1,y1,z);var d=new Vertex(x0,y1,z);
|
||||
out.add(back?new Face(d,c,b,a,color):new Face(a,b,c,d,color));
|
||||
}
|
||||
private static void side(List<Face> out,double x0,double y0,double x1,double y1,int color){
|
||||
out.add(new Face(new Vertex(x0,y0,0),new Vertex(x1,y1,0),new Vertex(x1,y1,THICKNESS),new Vertex(x0,y0,THICKNESS),color));
|
||||
}
|
||||
public static Vec3 world(AnchorGeometry g,Vertex v){
|
||||
var n=g.facing();var right=g.right();
|
||||
return Vec3.atCenterOf(g.wall()).add(n.getStepX()*(.5+v.depth),v.v-.5,n.getStepZ()*(.5+v.depth))
|
||||
.add(right.getStepX()*(v.u-.5),0,right.getStepZ()*(v.u-.5));
|
||||
}
|
||||
/** Behind the raised rim, an observer outside the opening cannot see any part of the panel. */
|
||||
public static boolean videoVisible(AnchorGeometry g,Vec3 eye,double videoClearance){
|
||||
var n=g.facing();var wall=Vec3.atCenterOf(g.wall());
|
||||
double outward=(eye.x-wall.x)*n.getStepX()+(eye.z-wall.z)*n.getStepZ()-.5;
|
||||
if(outward<videoClearance)return false;
|
||||
if(outward>=THICKNESS)return true;
|
||||
var right=g.right();double u=(eye.x-wall.x)*right.getStepX()+(eye.z-wall.z)*right.getStepZ()+.5;
|
||||
double v=eye.y-wall.y+.5;
|
||||
return u>=THICKNESS&&u<=g.width()-THICKNESS&&v>=THICKNESS&&v<=g.height()-THICKNESS;
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ public final class AnchorScreen extends Screen {
|
||||
g.centeredText(font,title,width/2,top+3,0xffffffff);g.centeredText(font,Anchors.label("one_frame"),width/2,top+18,0xffc3d4e4);
|
||||
g.text(font,Anchors.label("width"),left+10,top+33,0xffffffff);g.text(font,Anchors.label("height"),left+112,top+33,0xffffffff);
|
||||
int w=number(widthBox),h=number(heightBox);boolean valid=w>0&&h>0;
|
||||
if(valid){double scale=Math.min((panelWidth-60.0)/w,42.0/h);int sw=Math.max(1,(int)(w*scale)),sh=Math.max(1,(int)(h*scale));int x=left+(panelWidth-sw)/2,y=top+99+(42-sh)/2;g.fill(x,y,x+sw,y+sh,0xff223a4b);g.outline(x,y,sw,sh,0xff87c8df);g.fill(x,y+sh-4,x+4,y+sh,0xffff3535);}
|
||||
if(valid){double scale=Math.min((panelWidth-60.0)/w,42.0/h);int sw=Math.max(1,(int)(w*scale)),sh=Math.max(1,(int)(h*scale));int x=left+(panelWidth-sw)/2,y=top+99+(42-sh)/2;g.fill(x,y,x+sw,y+sh,0xff223a4b);g.outline(x,y,sw,sh,0xff87c8df);}
|
||||
g.centeredText(font,Anchors.label("free_dimensions"),width/2,top+144,valid?0xffa5b6c7:0xffff7b7b);
|
||||
if(!view.message().isEmpty())g.centeredText(font,Anchors.label(view.message()),width/2,top+180,0xffff8c8c);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public final class Anchors {
|
||||
return;
|
||||
}
|
||||
if(!(p.level().getBlockEntity(m.pos()) instanceof AnchorBlockEntity a)||!Objects.equals(a.display,m.display()))return;
|
||||
if(m.action()==0){open(p,a,p.isShiftKeyDown());return;}
|
||||
if(m.action()==0){open(p,a,false);return;}
|
||||
boolean edit=near(p,a)&&canBuild(p,a.getBlockPos());String error="";
|
||||
if(m.action()==6){AnchorPackets.view(p,a,edit,edit?"":"protected");return;}
|
||||
if(m.action()==1){
|
||||
|
||||
@@ -1,41 +1,56 @@
|
||||
package fr.koka.media.mixin;
|
||||
|
||||
import fr.koka.media.anchor.Anchors;
|
||||
import fr.koka.media.anchor.*;
|
||||
import java.util.*;
|
||||
import com.dreamdisplays.platform.client.displays.DisplayScreen;
|
||||
import com.dreamdisplays.platform.client.render.*;
|
||||
import com.dreamdisplays.libs.kotlin.Unit;
|
||||
import com.dreamdisplays.libs.kotlin.jvm.functions.Function2;
|
||||
import com.mojang.blaze3d.vertex.*;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.renderer.rendertype.RenderType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.spongepowered.asm.mixin.*;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/** The same pose, visibility and Vulkan draw path as the video, with constant block-space thickness. */
|
||||
/** Block-space ring using Dream's Vulkan draw path; its video transform deliberately flattens Z. */
|
||||
@Mixin(targets="com.dreamdisplays.platform.client.render.ScreenRenderer",remap=false)
|
||||
abstract class AnchorBorderMixin {
|
||||
@Unique private Vec3 media$eye=Vec3.ZERO;
|
||||
@Unique private final Map<DisplayScreen,AnchorFrameMesh> media$meshes=new WeakHashMap<>();
|
||||
@Inject(method="render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/Camera;ZLcom/dreamdisplays/libs/kotlin/jvm/functions/Function2;)V",at=@At("HEAD"),remap=false)
|
||||
private void media$camera(PoseStack stack,Camera camera,boolean replay,@Coerce Object renderer,CallbackInfo ci){media$eye=camera.position();}
|
||||
@Redirect(method="render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/Camera;ZLcom/dreamdisplays/libs/kotlin/jvm/functions/Function2;)V",
|
||||
at=@At(value="INVOKE",target="Lcom/dreamdisplays/platform/client/displays/DisplayScreen;getHasTexture()Z"),remap=false)
|
||||
private boolean media$blankScreen(DisplayScreen s){return s.getHasTexture()||s.getCustomData().containsKey(Anchors.MARKER);}
|
||||
@SuppressWarnings("unchecked")
|
||||
@Inject(method="renderScreenTexture",at=@At("HEAD"),cancellable=true,remap=false)
|
||||
private void media$back(DisplayScreen screen,PoseStack stack,boolean replay,@Coerce Object renderer,CallbackInfo ci){
|
||||
var info=AnchorInfo.decode(screen.getUuid(),screen.getCustomData().get(Anchors.MARKER));
|
||||
if(info!=null&&!AnchorFrameMesh.videoVisible(info.geometry(),media$eye,DisplayPoseStack.INSTANCE.surfaceClearance()+(replay?.01:0))){
|
||||
if(!replay)screen.fitTextureWhileHidden();
|
||||
media$draw(screen,stack,renderer,info.geometry());ci.cancel();
|
||||
}
|
||||
}
|
||||
@Inject(method="renderScreenTexture",at=@At("TAIL"),remap=false)
|
||||
private void media$border(DisplayScreen screen,PoseStack stack,boolean replay,@Coerce Object renderer,CallbackInfo ci){
|
||||
if(!screen.getCustomData().containsKey(Anchors.MARKER))return;
|
||||
private void media$front(DisplayScreen screen,PoseStack stack,boolean replay,@Coerce Object renderer,CallbackInfo ci){
|
||||
var info=AnchorInfo.decode(screen.getUuid(),screen.getCustomData().get(Anchors.MARKER));
|
||||
if(info!=null)media$draw(screen,stack,renderer,info.geometry());
|
||||
}
|
||||
@Unique @SuppressWarnings("unchecked")
|
||||
private void media$draw(DisplayScreen screen,PoseStack stack,Object renderer,AnchorGeometry geometry){
|
||||
var mesh=media$meshes.get(screen);
|
||||
if(mesh==null||mesh.width!=geometry.width()||mesh.height!=geometry.height()){
|
||||
mesh=new AnchorFrameMesh(geometry.width(),geometry.height());media$meshes.put(screen,mesh);
|
||||
}
|
||||
var frame=mesh;var origin=Vec3.atLowerCornerOf(screen.getPos());
|
||||
var draw=(Function2<RenderType,Function2<PoseStack.Pose,VertexConsumer,Unit>,Unit>)renderer;
|
||||
stack.pushPose();
|
||||
DisplayPoseStack.INSTANCE.liftTowardViewer(stack,screen.getFacing(),replay?.025f:.015f);
|
||||
DisplayPoseStack.INSTANCE.applyScreenTransform(stack,screen.getFacing(),screen.getWidth(),screen.getHeight());
|
||||
float x=1f/(16*screen.getWidth()),y=1f/(16*screen.getHeight());
|
||||
draw.invoke(DisplayYuvRenderTypes.INSTANCE.solidColorType(),(pose,v)->{
|
||||
media$rect(pose,v,0,0,1,y,100,69,39);media$rect(pose,v,0,1-y,1,1,151,115,65);
|
||||
media$rect(pose,v,0,y,x,1-y,127,90,49);media$rect(pose,v,1-x,y,1,1-y,80,51,27);
|
||||
media$rect(pose,v,x,y,x*2,y*2,184,19,13);
|
||||
for(var face:frame.faces)for(var vertex:face.vertices()){
|
||||
var p=AnchorFrameMesh.world(geometry,vertex).subtract(origin);int c=face.color();
|
||||
v.addVertex(pose,(float)p.x,(float)p.y,(float)p.z).setUv(0,0).setColor((c>>16)&255,(c>>8)&255,c&255,255);
|
||||
}
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
stack.popPose();
|
||||
}
|
||||
@Unique private static void media$rect(PoseStack.Pose p,VertexConsumer v,float x0,float y0,float x1,float y1,int r,int g,int b){
|
||||
v.addVertex(p,x0,y0,0).setUv(0,0).setColor(r,g,b,255);v.addVertex(p,x1,y0,0).setUv(0,0).setColor(r,g,b,255);
|
||||
v.addVertex(p,x1,y1,0).setUv(0,0).setColor(r,g,b,255);v.addVertex(p,x0,y1,0).setUv(0,0).setColor(r,g,b,255);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package fr.koka.media.mixin;
|
||||
|
||||
import fr.koka.media.anchor.Anchors;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.*;
|
||||
import net.minecraft.client.renderer.rendertype.RenderType;
|
||||
import net.minecraft.client.renderer.state.level.BlockOutlineRenderState;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.*;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/** The invisible 1x1 anchor must not leave its vanilla selection rectangle inside the large screen. */
|
||||
@Mixin(LevelRenderer.class)
|
||||
abstract class AnchorOutlineMixin {
|
||||
@Inject(method="submitHitOutline",at=@At("HEAD"),cancellable=true)
|
||||
private void media$outline(PoseStack pose,SubmitNodeCollector collector,RenderType type,BlockOutlineRenderState state,
|
||||
int color,float width,boolean translucent,CallbackInfo ci){
|
||||
var level=Minecraft.getInstance().level;
|
||||
if(level!=null&&level.getBlockState(state.pos()).is(Anchors.BLOCK))ci.cancel();
|
||||
}
|
||||
}
|
||||
+2
-165
@@ -1,170 +1,7 @@
|
||||
{
|
||||
"ambientocclusion": false,
|
||||
"textures": {
|
||||
"particle": "minecraft:block/oak_planks",
|
||||
"wood": "minecraft:block/oak_planks",
|
||||
"redstone": "minecraft:block/redstone_block"
|
||||
"particle": "minecraft:block/oak_planks"
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"from": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
16,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#wood"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
0,
|
||||
15,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
16,
|
||||
16,
|
||||
1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#wood"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
0,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
1,
|
||||
15,
|
||||
1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#wood"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
15,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
16,
|
||||
15,
|
||||
1
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#wood"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#wood"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"from": [
|
||||
1,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"to": [
|
||||
3,
|
||||
3,
|
||||
2
|
||||
],
|
||||
"faces": {
|
||||
"north": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"south": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"east": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"west": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"up": {
|
||||
"texture": "#redstone"
|
||||
},
|
||||
"down": {
|
||||
"texture": "#redstone"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
"elements": []
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "sanctuary_media",
|
||||
"version": "0.3.0",
|
||||
"version": "0.3.2",
|
||||
"name": "Sanctuary Media",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"environment": "*",
|
||||
|
||||
@@ -2,7 +2,33 @@
|
||||
"required": true,
|
||||
"package": "fr.koka.media.mixin",
|
||||
"compatibilityLevel": "JAVA_25",
|
||||
"mixins": ["AnchorDisplayGuardMixin", "AnchorActionGuardMixin"],
|
||||
"client": ["AnchorUseMixin", "AnchorMenuMixin", "AnchorModeMixin", "AnchorBorderMixin", "AnchorTextureBudgetMixin", "AnchorResizePlaybackMixin", "AudioSinkMixin", "MasterClockMixin", "PlayerClockMixin", "SessionStartMixin", "FrameGateMixin", "TimelineMixin", "VideoUrlMixin", "AudioUrlMixin", "SoundManagerAccess", "SoundEngineAccess", "LibraryAccess", "SoundLifecycleMixin", "VideoOptionsMixin"],
|
||||
"injectors": {"defaultRequire": 1}
|
||||
"mixins": [
|
||||
"AnchorDisplayGuardMixin",
|
||||
"AnchorActionGuardMixin"
|
||||
],
|
||||
"client": [
|
||||
"AnchorUseMixin",
|
||||
"AnchorMenuMixin",
|
||||
"AnchorModeMixin",
|
||||
"AnchorBorderMixin",
|
||||
"AnchorOutlineMixin",
|
||||
"AnchorTextureBudgetMixin",
|
||||
"AnchorResizePlaybackMixin",
|
||||
"AudioSinkMixin",
|
||||
"MasterClockMixin",
|
||||
"PlayerClockMixin",
|
||||
"SessionStartMixin",
|
||||
"FrameGateMixin",
|
||||
"TimelineMixin",
|
||||
"VideoUrlMixin",
|
||||
"AudioUrlMixin",
|
||||
"SoundManagerAccess",
|
||||
"SoundEngineAccess",
|
||||
"LibraryAccess",
|
||||
"SoundLifecycleMixin",
|
||||
"VideoOptionsMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package fr.koka.media;
|
||||
|
||||
import fr.koka.media.anchor.*;
|
||||
import net.minecraft.core.*;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
final class AnchorFrameMeshTest {
|
||||
static void run(){
|
||||
for(int[] size:new int[][]{{1,1},{7,4},{33,7},{256,64}}){
|
||||
var mesh=new AnchorFrameMesh(size[0],size[1]);assert mesh.faces.size()==16;
|
||||
for(var face:mesh.faces){
|
||||
for(var v:face.vertices()){
|
||||
assert v.u()>=0&&v.u()<=size[0]&&v.v()>=0&&v.v()<=size[1];
|
||||
assert v.depth()==0||v.depth()==AnchorFrameMesh.THICKNESS;
|
||||
}
|
||||
assert ((face.color()>>8)&255)>40; // No red anchor marker remains.
|
||||
Vec3 a=local(face.a()),b=local(face.b()),c=local(face.c());
|
||||
assert b.subtract(a).cross(c.subtract(a)).lengthSqr()>0;
|
||||
}
|
||||
for(Direction facing:Direction.Plane.HORIZONTAL){
|
||||
var g=new AnchorGeometry(new BlockPos(12,70,24),facing,size[0],size[1]);
|
||||
var n=new Vec3(facing.getStepX(),0,facing.getStepZ());
|
||||
assert AnchorFrameMesh.videoVisible(g,g.center().add(n.scale(2)),.008);
|
||||
assert !AnchorFrameMesh.videoVisible(g,g.center().subtract(n.scale(2)),.008);
|
||||
for(var v:new AnchorFrameMesh.Vertex[]{new AnchorFrameMesh.Vertex(-1,size[1]/2.0,.03),
|
||||
new AnchorFrameMesh.Vertex(size[0]+1,size[1]/2.0,.03),new AnchorFrameMesh.Vertex(size[0]/2.0,size[1]+1,.03),
|
||||
new AnchorFrameMesh.Vertex(size[0]/2.0,-1,.03)})assert !AnchorFrameMesh.videoVisible(g,AnchorFrameMesh.world(g,v),.008);
|
||||
assert AnchorFrameMesh.videoVisible(g,AnchorFrameMesh.world(g,new AnchorFrameMesh.Vertex(size[0]/2.0,size[1]/2.0,.03)),.008);
|
||||
assert AnchorFrameMesh.videoVisible(g,AnchorFrameMesh.world(g,new AnchorFrameMesh.Vertex(size[0]+1,size[1]/2.0,.25)),.008);
|
||||
for(var face:mesh.faces)for(var v:face.vertices()){
|
||||
var p=AnchorFrameMesh.world(g,v).subtract(Vec3.atCenterOf(g.wall()));
|
||||
assert Math.abs(p.dot(n)-(.5+v.depth()))<1e-7;
|
||||
assert Math.abs(p.y-(v.v()-.5))<1e-7;
|
||||
var r=g.right();assert Math.abs(p.x*r.getStepX()+p.z*r.getStepZ()-(v.u()-.5))<1e-7;
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("Frame mesh: exact bounds, 16 nondegenerate faces, four orientations and front-only video passed");
|
||||
}
|
||||
private static Vec3 local(AnchorFrameMesh.Vertex v){return new Vec3(v.u(),v.v(),v.depth());}
|
||||
}
|
||||
@@ -66,5 +66,6 @@ public final class MediaSyncTest {
|
||||
HlsProxy.forget("other");
|
||||
System.out.println("Media clock / reconnect / timeline / HLS contracts passed");
|
||||
AnchorGeometryTest.run();
|
||||
AnchorFrameMeshTest.run();
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -142,6 +142,12 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
|
||||
check(failures==0,"Close PBR stays stable under subpixel motion: failing distances="+failures);
|
||||
}
|
||||
private static void materials(){
|
||||
try(var materials=new PbrMaterials()){
|
||||
check(!materials.candidate(Blocks.BARRIER.defaultBlockState())&&!materials.candidate(Blocks.MOVING_PISTON.defaultBlockState()),
|
||||
"Invisible and entity-rendered blocks cannot leave normals from their baked terrain model");
|
||||
check(materials.candidate(Blocks.OAK_PLANKS.defaultBlockState())&&materials.candidate(Blocks.GLASS.defaultBlockState()),
|
||||
"Visible wooden and glass terrain models keep their material pass");
|
||||
}
|
||||
int[] flat=new int[16];Arrays.fill(flat,0xFF808080);var profile=PbrMaterials.profile("bricks");
|
||||
for(int p:PbrMaterials.generate(flat,4,4,profile))check((p>>8&0xFFFF)==0x8080,"Flat source has neutral normal");
|
||||
flat[5]=0xFFFFFFFF;var slope=PbrMaterials.generate(flat,4,4,profile);check((slope[4]>>16&255)<128&&(slope[6]>>16&255)>128,"Height gradients produce opposite normal slopes");
|
||||
|
||||
@@ -24,7 +24,9 @@ final class PbrMaterials implements AutoCloseable {
|
||||
return state.getFluidState().is(net.minecraft.tags.FluidTags.WATER)&&state.liquid()?2:
|
||||
(name.equals("glass")||name.endsWith("_glass")||name.equals("glass_pane")||name.endsWith("_glass_pane"))?1:0;
|
||||
}
|
||||
boolean candidate(BlockState state){return !state.isAir();}
|
||||
// Invisible blocks may still have a baked model (for particles or items).
|
||||
// Their hidden faces must never enter the normal/material buffer.
|
||||
boolean candidate(BlockState state){return state.getRenderShape()==net.minecraft.world.level.block.RenderShape.MODEL;}
|
||||
Profile profile(BlockState state){return profiles.computeIfAbsent(state,s->profile(BuiltInRegistries.BLOCK.getKey(s.getBlock()).getPath()));}
|
||||
static Profile profile(String name){
|
||||
if(name.equals("water"))return new Profile(.12f,0);
|
||||
|
||||
@@ -31,7 +31,7 @@ final class PbrTerrain implements AutoCloseable {
|
||||
PbrTerrain(){this(false);}
|
||||
PbrTerrain(boolean transparent){this.transparent=transparent;maxBytes=(transparent?8L:24L)*1024*1024;}
|
||||
private boolean candidate(net.minecraft.world.level.block.state.BlockState state){
|
||||
return transparent ? PbrMaterials.family(state)==1||state.getFluidState().is(net.minecraft.tags.FluidTags.WATER)
|
||||
return transparent ? (materials.candidate(state)&&PbrMaterials.family(state)==1)||state.getFluidState().is(net.minecraft.tags.FluidTags.WATER)
|
||||
: materials.candidate(state)&&!state.liquid()&&PbrMaterials.family(state)==0;
|
||||
}
|
||||
private final Long2ObjectOpenHashMap<Section> sections=new Long2ObjectOpenHashMap<>();
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
name = "Sanctuary"
|
||||
author = "KOKA99CAB"
|
||||
version = "beta.296"
|
||||
version = "beta.298"
|
||||
pack-format = "packwiz:1.1.0"
|
||||
|
||||
[index]
|
||||
|
||||
@@ -40,10 +40,17 @@ def run(name):
|
||||
step("anchor-place", anchor_a="place", aim_a=[-3.5, 81.5, .5])
|
||||
state = last("anchor-server")
|
||||
check("one frame consumed on placement", state.get("anchor") and state["frames_in_inventory"] == 3)
|
||||
check("first placement opens dimensions", last("anchor-client-a").get("screen") == "AnchorScreen" and last("anchor-client-a").get("settings"))
|
||||
inventory = state["frames_in_inventory"]
|
||||
original_id = state["display"]
|
||||
step("near-anchor", anchor_server_step="near", aim_a=[-3.5, 81.5, .5], anchor_a="close")
|
||||
step("open-anchor", anchor_a="click")
|
||||
check("reopening near the anchor opens native player", last("anchor-client-a").get("screen") == "DisplayMenu")
|
||||
step("shift-held", anchor_a="shift-hold")
|
||||
crouching = last("anchor-server").get("crouching")
|
||||
step("shift-reader", anchor_a="click")
|
||||
check("Shift-right-click also opens native player", crouching and last("anchor-client-a").get("screen") == "DisplayMenu")
|
||||
step("release-shift", anchor_a="shift-release")
|
||||
step("custom-size", anchor_a="size:7:4")
|
||||
check("custom 7x4 and free enlargement", last("anchor-server")["width"] == 7 and last("anchor-server")["height"] == 4 and last("anchor-server")["frames_in_inventory"] == inventory)
|
||||
step("shrink", anchor_a="size:1:1")
|
||||
|
||||
Reference in New Issue
Block a user