Berserker axes B final
Blackglass Fang after the final shaping pass: pale knapped edges so it reads at the game camera.
How fighters close in, hit, block and die, and how missiles fly. Newest first.
Blackglass Fang after the final shaping pass: pale knapped edges so it reads at the game camera.
Blackglass Fang in his hands, final shaping pass.
The three axe designs: A Raptor Claw, B Blackglass Fang (your pick), C Twin-Bite.
Stills of the re-seat, before and after.
Live | re-seated: the axes turned in his fists so they hang head-down with the edge forward. Wrist bend in the walk 110-135 down to 19-31 degrees.
ORC BERSERKER AXES - design preview, 2026-11-09
Sheet: berserker_axes_options.jpg (this folder). Nothing is in the game. No game files were touched.
All three are our own shapes, modelled and painted by script in Blender. No AI generator, no other game's weapon used.
THE THREE OPTIONS
A - RAPTOR CLAW (follows the FIRST idea: heavy forged iron)
Dark hammered-iron crescent. The top sweeps up into a hooked horn like a raptor's killing claw, dipped in
blood-red. The underside breaks into a row of raked saw teeth. Three slanted "claw rake" slots cut through the
blade are our own notch pattern. A boar tusk is the back spike, set in a riveted iron collar. Hide-wrapped grip,
studded iron band, fur ruff, iron pommel spike. Sibling difference: left axe has three slots and two claws tied on;
right axe has two slots, a battle chip in the edge and one big claw.
B - BLACKGLASS FANG (follows the SECOND idea: chipped stone and sinew)
A knapped obsidian slab pushed through a crooked dark haft so a chipped stub sticks out the back. Faceted, flaked
faces, a tall horn on top, a hook below, scalloped edge. Lashed on with blood-red sinew (wound collars plus crossed
loops), ember-red cracks in the glass, dark hide grip, antler-point pommel. Sibling difference: left carries two
red-tipped feathers; right carries three fangs and has an extra flake knocked out of the edge.
C - TWIN-BITE (blend of both, and the true DOUBLE axe)
Two blades on one head: a hammered-iron "jaw" with teeth and a fang-shaped cut-out in front, a chipped obsidian
blade with ember cracks behind, a tusk spike on top. Held by a riveted iron collar AND a red sinew cross-lash.
Fur ruff, bone-studded band, bone-knuckle pommel. Sibling difference: tied-on claws differ and the right axe has
extra chips in both blades.
MY PICK: A. It has the clearest, most aggressive outline (horn, teeth, tusk) both close up and in the hand, it
looks most like "orc" next to the grunt/warchief gear, and it is the cheapest (lowest triangle count).
C is the one to pick if "double axe" is meant literally; it is the busiest and clips the body most.
B is the most "Primeval" in material, but on the sheet its outline is the lumpiest of the three; if you like the
stone-and-sinew look best I would give its blade one more shaping pass first.
A fair mix is also possible: A's shape with B's red sinew lashing instead of the iron collar.
SIZES AND TRIANGLES (current axe = 366 triangles each, 0.763 long x 0.518 wide in model units; brief asked for
1.5x-2.5x triangles and no more than about 30% bigger)
triangles L / R length width
Current 366 / 366 0.763 (100%) 0.518 (100%)
A 664 / 622 (1.8x/1.7x) 0.988 (129%) 0.637 (123%)
B 828 / 902 (2.3x/2.5x) 0.978 (128%) 0.617 (119%)
C 880 / 862 (2.4x/2.4x) 0.984 (129%) 0.672 (130%)
Whole unit today is 8,501 triangles; the pair would add about 550 (A) to 1,030 (C).
Each option has its own 1024 painted texture (both siblings on one sheet) in the work folder.
BODY CLIPPING CHECK (measured, all 204 frames of the 6 clips; frames where any part of an axe above the hand is
inside the body)
all clips Idle Walk Run Attack Work Death
Current 43 0/61 0/29 6/21 5/25 15/31 17/37
A 69 16/61 1/29 6/21 14/25 15/31 17/37
B 67 12/61 0/29 8/21 14/25 16/31 17/37
C 93 10/61 15/29 9/21 18/25 17/31 24/37
So the bigger heads DO touch him more than today, mainly in Idle and Attack (C also in Walk). I did not see it in the
idle picture on the sheet, so it is probably a tip brushing a leg, but it is real and I only measured it, I did not
look at every frame. Fix at install time: scale the pair to about 115-120% of current instead of 129%, or shorten
the top horn; then re-run the same check.
HOW THE CURRENT AXES ARE ATTACHED
- They are NOT separate objects. Both axes are loose pieces inside the one body mesh (orc_berserker_mesh in
orc_berserker.fbx), 366 triangles each, skinned 100% to the hand bones CC_Base_L_Hand / CC_Base_R_Hand.
There is no weapon bone or socket.
- In the rest (T) pose the haft lies front-to-back through the fist, blade pointing outward away from the body.
Grip centre is about 0.125 up the haft from the butt.
- They use the body material and the body texture: the narrow shared "prop strip" on the right edge of the
basecolor (u 0.878-0.997). That strip is tiling swatches (wood, cream ribbed, grey), not a painted axe.
- Team colour: the three bands on the current haft sit on a swatch that is 100% team colour in
orc_berserker_teammask.png. So today the axes carry a little team colour.
WHAT INSTALLING A NEW PAIR WOULD INVOLVE (not done)
- orc_berserker.fbx: delete the 2 x 366 old axe triangles, add the new pair skinned 100% to the same two hand bones
(the preview already hangs them exactly this way). orc_berserker_campfire.fbx needs the same swap.
This must wait until the worker editing this unit's animations is finished, and be done on their final file.
- Animations: no change needed. The axes ride the hand bones, so every clip carries over. Only the clipping above.
- Texture: the new painted axe does not fit the tiling strip. Either (a) a second material + the new 1024 axe
texture on the unit (simplest, one extra draw call per berserker), or (b) repaint the axe into the unit's own
basecolor/normal (needs free space in that atlas; I did not look for any). The prefab / material list changes
with (a).
- Team mask: YES, needs a matching edit. With (a) the axe needs its own mask (all black = no team colour, or mark
the sinew wraps / studded band as team colour so the bands keep doing today's job). With (b) the mask must be
black wherever the new axe is painted.
- Normal map: the preview has none for the axes (flat neutral would be needed, or a baked one).
NOT SURE ABOUT
- Whether grunt/other orc units share this same prop strip (the grunt's basecolor file is the same size as the
berserker's); if so the strip must not be repainted.
- Whether the unit shader allows a second material and whether it has emission. The "ember cracks" are only paint;
they do not glow.
- The game-camera row is my estimate of in-play size and angle (55 degrees down), not a screenshot from the game.
At that size the dark blades read less brightly than today's cream ones; the horn/tusk outline and red accents are
what carry. In the idle pose the blades hang edge-on to the camera, same as today.
- I could not see the two example pictures; I worked only from the written description of their style.
- The little tied-on claws/feathers are rigid (they do not swing).
WORK FILES: local file: berserker_axes (local file: axes_A/B/C.blend, local file: berserker_axe_*_basecolor.png,
local file: preview_scene.blend with all sets on the rig, scripts, renders)
ORC BERSERKER AXES - option B "Blackglass Fang", final pass - 2026-11-09 Sheet: berserker_axes_B_final.jpg Video: berserker_axes_B_final.mp4 (also in local file: Primeval_review) NOTHING IS IN THE GAME. Everything is staged in local file: staged. No game file was written. Still our own shapes, modelled and painted by script in Blender. No AI generator, no other game's weapon. WHAT CHANGED FROM THE FIRST B - Outline: one more shaping pass. A defined upper horn, one long convex cutting edge with small knapped scallops (instead of the even zig-zag), a pronounced beard hook that sweeps down and back, and a bite cut out of the underside next to the haft plus a small notch on top. The flakes are now a few large planes: a thick spine by the haft, a hollow flake scar, and a ridge that follows the cutting edge. - Readability: a pale grey-white knapped band along every sharp edge, pale ridge lines between flakes, and a glassy sheen on the facets that face up and out. The middle of the blade is still black glass with the ember cracks. The sinew is a deeper, more saturated blood red, the wound collars are fatter and the crossed loops thicker. - Borrowed from option A: YES, one thing. A boar tusk is the back spike, bound in by the lashing, where the first B had a chipped stone stub. It gives the pair the cream accent today's axes have. Nothing else of A was used. - Kept: antler-point pommel, hide grip, crooked dark haft, feathers on the left axe / three fangs on the right axe, and the right axe has an extra flake knocked out of its edge (siblings, not twins). - Team colour: three grip bands (pommel, top of the grip, under the head) take the player's colour, doing the job the three haft bands do today. The red lashing on the head is paint and stays red for every player. - Ember cracks: PAINT ONLY, they do not glow. I read local file: TeamLit.shader: it lists the emission properties but compiles no emission (its own comment: "no shader_feature keywords", and it never defines _EMISSION, which URP's surface setup needs before it reads the emission map), and orc_berserker.mat has emission black with no map. A glow would need a shader edit and an emission texture. Not done. FINAL SIZE (model units; current axe = 0.763 long x 0.518 wide) length width (back spike tip to cutting edge) triangles L / R Current 0.763 (100%) 0.518 (100%) 366 / 366 B first 0.978 (128%) 0.617 (119%) 828 / 902 B FINAL 0.959 (126%) 0.555 (107%) 893 / 958 The cutting edge stands 0.390 out from the haft against 0.348 today (112%). Whole unit: 8,501 -> 9,620 triangles. How it was fitted: the pair is 95% of the size I drew it at, and each axe is turned 13 degrees about its own haft (left one way, right the other, a mirror pair). The grip position and the hand pose are untouched, and the haft keeps today's thickness inside the fist. The lower hook was NOT trimmed and the haft below the hand was NOT shortened: neither was needed. Two things I did change to stop touching: the tusk is shorter than I first drew it, and the feathers/fangs hang from the upper collar (where the first B had them), not under the head. CONTACT WITH HIS BODY - READ THIS, THE EARLIER NUMBERS WERE MISLEADING The first pass counted a frame when an axe point was on the "back" side of the nearest piece of body surface. I re-ran exactly that test and get the same 43 of 204 for the current axes. But I then looked at what it flags: in Idle the flagged points are on the haft, 0.13 to 0.50 units AWAY from him (he stands 1.91 units tall, so think 13 to 50 cm), and their "nearest surface" is the back of his own index finger. In Attack the flagged points are 0.09 to 0.44 units from the body. None of them is inside him. That test counts near-misses and its own blind spots, so its numbers jump about with tiny changes and do not measure clipping. So I added a real test: a point counts only if it is more than 4 mm INSIDE the closed body (checked with rays in 7 directions; I confirmed it says "inside" for the middle of his thigh, calf, forearm, upper arm and hand, and "outside" for points in the air). Checked on every vertex, every edge midpoint and every face centre of both axes, above the grip, every frame. A) REAL TEST: frames where any part of an axe is inside his body LIVE clips (204 frames) NEW Idle/Walk/Run file (204) CAMPFIRE clips (367) Current axes 0 0 0 B first (128%) 1 (Attack) 1 (Attack) 3 (StandUp) B FINAL 0 0 0 Per clip the final is 0 in Idle, Walk, Run, Attack, Work, Death and in Sit, SitDown, SitDrink, SitEat, SitWarm, StandUp, on both files. Same as today: no clipping. For the record, without the 13 degree turn the final at 95% touched in 1 Attack frame and 2 StandUp frames (2-3 cm deep), and at 100% size with the turn the tusk/feathers touched him in 9 SitEat frames (under 1 cm). That is why the answer is 95% with the turn: the largest size I tested that is clean everywhere. B) FIRST-PASS TEST, as asked (frames flagged; all clips / Idle Walk Run Attack Work Death) LIVE clips NEW Idle/Walk/Run file Current axes 43 / 0 0 6 5 15 17 72 / 32 2 1 5 15 17 B first 64 / 10 1 7 13 16 17 88 / 33 4 5 13 16 17 B FINAL 50 / 0 0 6 12 15 17 80 / 31 2 3 12 15 17 By this test the final is 0 in Idle and Walk on the live clips, as today, but it is NOT at or under today's total: 50 against 43, all of the difference in Attack (12 against 5), and 80 against 72 on the new file (Attack 12 against 5, Run 3 against 1). I could not get this test down to 43 at any size from 78% to 100%, because what it flags is not contact. I am reporting it rather than hiding it. If you want this number matched anyway, say so. (The first pass reported 67 for B; I get 64 with the same rule on the same file. Current matches exactly at 43.) WHICH BASE FBX local file: orc_berserker.fbx is built on the ANIMATION WORKER'S staged file local file: orc_berserker.fbx (as it was at 15:45 today, md5 f8eb72496a84cd40bca90b84280a5df4; unchanged when I finished). So it carries their new Idle, Walk and Run, plus the new axes. local file: orc_berserker.fbx is the same swap on the LIVE game file (today's clips, new axes), in case their animations are not approved. Made with the one-command script, which I left for later files: powershell -ExecutionPolicy Bypass -File local file: restage_on.ps1 <input.fbx> <output.fbx> If the animation worker changes their file again, run that on the new file. It takes about 2 minutes. Both outputs were re-imported and compared with their input: same two objects and names, same 71 bones (names, order, parents, rest pose), same six takes with the same names and frame counts (61/29/21/25/31/37), every bone on every frame equal (largest difference 0.00002), the body's 4,017 vertices equal in position, UV, normal and skin weights on every frame, one material, the same "V_None" blend shape. New axes are 100% on CC_Base_L_Hand / CC_Base_R_Hand. The old axes' 732 triangles are gone. ONE MATERIAL OR TWO: ONE. No extra draw. The new axes are painted into the berserker's existing 1024 atlas, in the props strip on its right edge (x 892 and up), and ONLY in rows the old axes never used. Measured texel density: the axes average 241 texels per model unit (about 310 on the blade faces), his body averages 244. Front and back of each blade share texels; both hafts share theirs. - 41,552 texels of orc_berserker_basecolor.png change, all inside that strip. Every other texel is identical to the file in the game now (checked texel by texel against the copy installed at 10:37 today, md5 e1bc2eb7...). - Because the old axes' own texels are untouched, the new texture also looks right on the OLD model. Textures can go in first, or the FBX can be rolled back alone, without a broken-looking unit in between. - The normal map needs no change (already flat there). - Grunt: NOT affected. The berserker already has his own files (own PNGs, own GUIDs; only orc_berserker.mat points at his basecolor), they just happen to hold the same picture as the grunt's. After install they differ. The one thing to watch: whoever next installs a skin by writing the grunt's picture over the berserker's file will wipe the axe paint. Re-apply it to any new skin with (output folder first, then the new files, nothing overwritten): blender -b --factory-startup --python local file: stage_textures.py -- <outdir> <basecolor.png> <teammask.png> <runtime_mask.png> What a SECOND material would have cost, from the code (not guessed): - TeamCloth.WearOnModel gives EVERY material on the body renderer the body's cloth mask, so an axe texture with its own layout would get the body's team-colour patches in the wrong places. - DetailedUnits adds the wound overlay and the hit flash as an extra material slot, and its own comment says an extra slot "draws the mesh's last submesh again". With two submeshes the wounds and the hit flash would show on the axes only, not on him. - The prefab only sets material 0 and the FBX imports no materials, so the prefab would need a second material too. So a second material needs code changes in two places plus a prefab change, and one more draw per berserker. A separate Held_ object would not work as-is either: Held_/Prop_ renderers are given the shared props palette mask. INSTALL LIST (staged file -> live path under local file: _Primeval). No .meta, prefab, material, controller or code change. 1. local file: orc_berserker_basecolor.png -> local file: orc_berserker_basecolor.png 2. local file: orc_berserker_teammask.png -> local file: orc_berserker_teammask.png 3. local file: orc_berserker.png -> local file: orc_berserker.png 4. local file: orc_berserker.fbx -> local file: orc_berserker.fbx (or local file: orc_berserker.fbx if the new Idle/Walk/Run are not going in) Put 1-3 in before or with 4: the new model on the old texture would show wood and stone swatches on the axes. Not needed: orc_berserker_campfire.fbx. I opened it: it holds only the six campfire clips and no mesh, so there are no axes in it to swap. The campfire clips play on the main model and are included in the contact check above. Not needed: local file: levels.txt (the bands are painted at the berserker's reference brightness, 0.243). orc_berserker.json: I did not touch it. If the animation worker ships one with their clips, that is theirs. The masks: only the texels the new axes use were changed (3,026 and 3,225 texels); 1,148 are the grip bands. The staged PNGs are smaller or larger on disk than the originals only because of PNG compression. WHAT I COULD NOT CHECK - Nothing was imported into Unity and the game was not run. I have not seen the axes under Primeval/Team Lit, only in Blender renders. Team colour in my pictures is the shader's formula applied by me, for a BLUE player. - Unity's own import of the new FBX (welding, tangents, the 60 degree smoothing) is unseen. The check above compares the file with its input inside Blender, not inside Unity. - The game-camera rows are the first pass's estimate of the in-play view (55 degrees down), not a game screenshot. On that estimate the new pair covers 4,594 pixels over the three poses against 2,335 for today's, and its average colour distance from the grass is 41 against 30 (from his body 28 against 26), so it should be easier to spot, not harder. That is measured on my renders, not in the game. - Unit icon / portrait: if one is rendered from the model it will still show the old axes until it is re-rendered. I did not look at how icons are made. - Death: I checked the axes against his body, not against the ground. - The feathers and fangs are rigid, as before. - In my blue-player pictures the scars on his chest and arm are blue too. That comes from the mask that is in the game today, not from this work; I have not confirmed in the game that it really looks like that. - Machine sharing: two other workers' Blenders were running nearly the whole time, so most of my runs started with theirs still going. I never ran more than one of my own, and never with less than 13 GB free. WORK FILES: local file: final (scripts, local file: axes_fang.blend, local file: final_params.json, local file: measure2_final_live.json and measure2_final_anim.json = every frame's result, logs, renders)
ORC BERSERKER - AXES RE-SEATED IN HIS FISTS (staged only, nothing installed) 2026-11-10 Base: the live file local file: orc_berserker.fbx (read only). It is byte-identical to local file: orc_berserker.fbx (SHA-256 65C9BAEA...). New file: local file: orc_berserker.fbx (SHA-256 2ED57233...). Settings used: local file: reseat_params.json. HOW THE AXES ARE ATTACHED, AND WHAT I DID - The two Blackglass Fang axes are loose pieces of the body mesh, skinned 100 % to CC_Base_L_Hand / CC_Base_R_Hand. There is no socket and no separate weapon bone. - Re-seat: I moved each axe's vertices rigidly in its hand bone's own space, pivoting on the centre of the fist. The haft is now turned 50 degrees in the fist toward the fingers (the diagonal of a real power grip), tilted 30 degrees, and rolled 52 degrees about its own length. Shape, UVs, material, weights, bones, the body mesh and the clip names and frame counts are all unchanged. The axe moves the same way in both shape keys. Bone list (71) and take list are identical to live, so the .fbx.meta and controller need no change. - Idle, Walk, Run: re-keyed the forearm, the two forearm twist bones and the hand only (legs, body, shoulders and upper arms are untouched). The elbows straighten by half their bend in Idle and Walk (the weight of the axes pulls the arms long). In the Run they keep their bend. The axe hangs head-down, about 20 degrees off vertical with the head a little forward. It swings with the arm and leans 7 degrees out from the leg. The forearm turns knuckles-forward by 22-50 degrees, shared along the forearm skin by the twist bones (20 % / 70 %). - Attack, Attack2: I kept the whole arm and body exactly as before and turned only the hand about the wrist (plus the twist bones), so that the axe HEAD lands where it did on every frame. This is a best fit: because the fist now holds the haft at a different angle, the head points can differ by up to 12-13 cm. Frames 0-5 and 18-24 blend into the new Idle, so the clips still start and end on the Idle pose exactly. - Death, Work: not re-keyed. Bones and skin are identical to live (checked). Only the axes sit differently in the fists. THE NUMBERS (current live -> new) Wrist bend = how far the hand bends away from the forearm, NOT counting the forearm's own turn (the turn is listed separately because it happens along the forearm, not at the wrist). Idle wrist bend L 41 / R 30 -> L 26 / R 28 forearm turn 0 -> 36-40 haft from straight down 90-94 -> 20-22 Walk wrist bend 110-135 -> 19-31 forearm turn 2-29 -> 22-50 haft from down 5-16 -> 13-30 Run wrist bend 85-128 -> 17-32 forearm turn 15-36 -> 27-49 haft from down 5-17 -> 14-34 Largest wrist bend in Idle/Walk/Run: 32 degrees (Run R). The target was under 35. If the forearm turn is counted too (the attack tool's "vs forearm bone" number): Idle 45-48, Walk 35-58, Run 33-58. Edge direction in Idle/Walk/Run: live faced straight BACK (forward component -0.97..-1.00). New faces forward-outward (forward component +0.42..+0.59, i.e. 55-65 degrees out from straight ahead, tilted a little up). Closest axe to body / legs: Idle 0.29 -> 0.28 m, Walk 0.17 -> 0.22 m, Run 0.27 -> 0.37 m. Axe inside the body: 0 frames (before and after). Lowest axe point above the floor: Idle 0.08 m, Walk 0.03 m, Run 0.03 m (live Walk/Run 0.10 m). Idle -> Walk / Run start: axe tip jump 1.36-1.59 m -> 0.11-0.22 m (the old Idle held the axes straight forward). Walk loop seam 0. Attack (chop A, right hand), new vs live: edge at the stand-in on the impact frame 9: nearest edge point -10 cm (inside) -> -12 cm; edge points inside 6/9 -> 6/9; edge centre height 1.52 -> 1.52 m. First touch still frame 9 (nothing within 3 cm before it). swing frames 6-11 (same), arc plane 54 -> 53 deg, swept 239 -> 240 deg, peak tip speed 50 -> 53 m/s (frames 8-9 both). Edge leading: flat-leading worst 9 -> 15 deg (limit 15). In-plane at impact 46 -> 32 deg (better). Axe inside own body: 0 frames (both). Closest axe to body 0.06 -> 0.14 m. Wrist (attack tool, against the skin above the wrist) L 41 / R 57 -> L 61 / R 56. True bend at most 58. Forearm turn during the swing up to 73 deg (live 13). Attack2 (chop B, left hand): nearest edge point at frame 9: -13 cm -> -11 cm; edge points inside 5/9 -> 5/9; edge centre height 1.55 -> 1.55 m; first touch frame 9 (same). Swept 238 -> 239 deg, peak tip speed 53 -> 56 m/s. Flat-leading worst 12 -> 16 deg. In-plane at impact 41 -> 28 deg. Axe in body 0 frames. Closest 0.07 -> 0.13 m. Wrist vs the skin above it L 60 / R 29 -> L 57 / R 59. Forearm turn up to 76 deg. Feet, pelvis, head, loop: identical to live in both attacks (no body bone touched). WHAT STILL LOOKS WRONG / HONEST LIMITS - "Edge forward" and "keep the attacks" pull against each other. With a fist round the haft, a head-down hang naturally puts the edge BACKWARD. Every degree I turn the edge forward in the hang costs wrist and forearm turn in the chops. I settled on forward-OUTWARD (about 60 degrees out from straight ahead), not straight forward. A straight-forward edge would need about 75-85 deg of forearm turn in the chops, or a re-grip during the wind-up (a new bone). I did neither. - In the attacks the forearm now turns up to 73-76 degrees through the swing (live about 13). The axe path is the same, but the forearm skin twists more. It is visible in close-ups; at game distance I could not see it. - Flat-leading in Attack2 is 16 deg at its worst frame, 1 over the method's 15 limit (live was 12). - Grip: the haft now crosses the fist on a 56-degree diagonal. In close-ups the fingers read as wrapped round it, but the short butt end of the haft above the fist is hidden inside the hand / wrist. That part is not in the body test, which skips the fist area, as before. - Death: once he is down, the axe heads go into the floor (the arm lies along the ground and the axe now runs along the arm). That is floor, not body, so I left it. Work: the two axes overlap each other when the hands come together overhead. Neither clip passes an axe through the body in the sheets I looked at. - The Walk/Run axe heads pass 3 cm above the floor at the lowest frame. - One frame of the Attack wind-up (frame 3-4) shows the off-hand axe briefly horizontal while it blends from the hang. WHAT I DID NOT CHECK - Not in Unity or in the game (forbidden here). The game camera view is a Blender stand-in. - The Run played 25 % faster was not rendered. - Death and Work: still frames only, not measured frame by frame. - Close-ups of the fists were rendered for Idle, Walk and Attack only, and on trial p3 (roll 60 instead of the final 52; same haft angle). The final file's fists were seen only in the full-body sheets and the video. INSTALL (only when the owner approves; nothing was installed): copy local file: orc_berserker.fbx -> local file: orc_berserker.fbx No import setting changes: same 71 bones, same takes (Attack, Attack2, Death, Idle, Run, Walk, Work) with the same frame counts (25, 25, 37, 61, 21, 29, 31). The .fbx.meta, controller and json stay as they are. FILES local file: orc_berserker.fbx, local file: reseat_params.json reseat_before_after.jpg, reseat_old_new.mp4 (57 s, 4.1 MB; Walk, Run, Attack, Attack2; three-quarter, side, game camera; two loops at game speed then one at quarter speed). Copies named berserker_reseat_* are in local file: Primeval_review and local file: berserker_axes. local file: reseat.py (the job), seatlib.py, seatsearch*.py (the grip search), measurement copies; work (trials p1-p5), logs, renders (frame sheets, close-ups).
Orc Shamans play their cast in the game (the failure was a test fault).
A unit leaves through its own gate while enemies press on it (it used to be stuck).
Ghoul vs Skeleton: the longer weapon holds its distance, the shorter steps in on its own blows.
REGRESSION FIXES - report, 2026-11-10 (local file: PrimevalMirror only; nothing committed; local file: Primeval not touched) Brief: fix the real regressions the test triage found (local file: test_triage), settle its "likely real" and "undetermined" items, and make two fighters with different reach each strike at their own weapon's distance. BUMPS RulesRevision 120 -> 121 (one bump; the comment in SimulationHost.cs names the three rule fixes of items 1-2). Save format NOT bumped: no new saved state (the gate cells are rebuilt every tick like MovementSystem's; nothing else stored). NetProtocol NOT bumped (no command, no wire change). Replay format unchanged (replays carry the rules revision as before). Item 5 is presentation only. Item 3 changed a test only. 1. PTERANODONS LAND BUT NEVER PECK; THE PINNED DIVE TRACE MOVED Cause: rules 120's two melee rules were applied by attack kind, not by who fights whom: rule B (tight slack) to every non-ranged attacker, so a landed pteranodon lost its 1.2 m lunge between pecks; rule A (places round a foe) to every person with a hand weapon, so the villager under the flock took "places" round the bird (and the 2 cm shuffle cap hit everyone). Fix: both rules now apply only to a player's melee soldier (a person of a player's side, not in a herd, hand weapon, not a healer) fighting a PERSON (rule B also at a building, as built). Wild animals, flyers, beasts, creeps' guards, towers and any fight with wildlife are rules 119 again: target choice (SurroundFoe hands back to rules 119's own choice when the nearest foe or the current target is an animal), places, slack, the wall reach and the 2 cm shuffle cap (MovementSystem.SurroundShuffle). Files: Task.Surround.cs (MeleeSoldier, Surrounds(i,t), SurroundRules119), TaskSystem.cs (call sites, SwingCommitted(i,t,..), AttackBuildingInPlace), MovementSystem.cs. Proof (reproduce first: local file: swap.ps1 put the four sim files back, local file: r10_orig; after: r07, v1): PteranodonTests.ItDivesLandsPecksAndClimbsAway as found FAIL "never pecked" -> PASS PteranodonFlightUnchangedTests.ItsDiveOnAPersonIsUnchanged as found 1788058534532768175 -> 2174182417866656760 = the rules-62 pin exactly, NOT re-pinned. PteranodonFlightUnchangedTests.ItsFlightOverAVersusMapIsUnchanged: now gives world 11955043186974726501 / flock 15426423904698948146, bit for bit what rules 119 gave (missile_impact's run 11-09 18:16). Its old pin had been stale since rules 115-119 (it moved by 11-09 14:03 and again by 18:16; every log searched). Re-pinned to the rules-119 values with a dated comment (the triage's condition, "once the birds behave", is met). The surround job's own tests pass unchanged (MeleeSurroundTests 7, MeleeSurroundSwitchTests 3), and its re-pinned FormationMarch TightGolden still matches (soldiers against people are untouched). 2. A UNIT CANNOT LEAVE THROUGH ITS OWN GATE WHILE ENEMIES PRESS ON IT Cause (traced tick by tick, local file: RegressionFixDiagTests.cs): an attacker took the place straight in front of the friend standing in his gate (a place sits exactly on the line to its foe) and struck from the gate's mouth; a striking soldier never gives way along its strike line, so the friend walking out pushed nose to nose into it for the whole 4 s order; the waiting attackers also stood in the mouth or in a one-cell gap. Fix: (a) no place round a foe and no waiting spot is chosen in a gate, the cell before or behind it, or in or at the mouth of a one-cell gap (Task.Surround.cs SurroundInTheWay; gates from a per-tick cell set, as MovementSystem builds one); a held place that has come to lie there is looked again within a third of a second; a waiting unit only takes a spot it can walk straight to, else waits where it is, or steps 2 m back if it stands in the way. (b) a soldier at its place round a person gives way along its strike line too to anyone walking on a plain move order (MovementSystem): an order to move wins. Proof (as found -> fixed): WallGateHoldTests.AFriendWalking...: friend crossings 0 -> 3 (needs 2; rules 119 way, surround off: 2). Whole class 20/20. New SurroundPassageTests (4 cases): NoWaitingAttackerStandsInAOneCellGap: waiting attackers standing still in the gap or its mouths 126 -> 0 unit-ticks; other attackers standing there 893 -> 0 (rules 119 way: 2019). Waiting attackers still pass through it while walking round (1072 -> 1129 unit-ticks): they queue on their own side; not fixed further. AManOrderedOutOfASurroundWalksOut: a man surrounded by six is ordered 10 m away: there in 115 ticks (as found 116; rules 119 way 194). A guard test: it did not fail before. Not done: places round a foe are still not checked for being reachable from the attacker's side of a wall (some attackers hold a place beyond a tree line and press against it); the gates showcase (film b) reads the same before and after because its attackers walk round the end of the line. 3. CASTERS NEVER ENTER THEIR CAST ANIMATION (DetailedSpellCastDispatchTests x2) Cause: NOT the game. The renderer runs its Animators at (frame time given / Time.deltaTime). In a batch editor Time.deltaTime is the clamped 0.333 s of a long editor frame (unscaled 1.45 s: loading the detailed library got slower with the 11-09 model installs), so the test's BeginFrame(.1f) ran the Animator at 0.3x and the 0.12 s fade into Cast was unfinished after its 0.2 s Update: the state read Idle for every caster, the necromancer too. Measured: speed 0.30, frame 1 in transition to Cast, frame 2 in Cast (local file: RegressionFixDiagTests.CastDiag). Melee drawing / steps code is not involved: the Cast branch, the attack variants (exact names Attack2/Attack3, used only inside the Attack branch) and the facing/step code (hand weapon and at work on a target only; Bowman and missile troops excluded) cannot take Cast, Work, Block or Throw. In the game: three Orc Shamans cast (staff raised in the Cast clip) identically in the build as found and the new one (film a, local file: cast_before|local file: crop.jpg). Fix: the test gives the renderer the editor's own frame time (speed 1, as in the game), with a dated comment. Both cases pass. 4. THE TWO "LIKELY REAL" AND SIX "UNDETERMINED" ITEMS - DetailedSpellCastDispatch x2: see 3 (test artefact; fixed in the test). - StuckWindupPoseTests.BlowsTakenLeaveNoShrugOnTheShoulders: ENVIRONMENT, proved: passes with a graphics device (local file: r13_gfx; the 11-08 passing runs used one; -nographics fails RenderTexture.Create). Left. - StuckWindupPoseTests.PersonHitLeavesNoTurnOnAnyBoneAfterBlows x3: REAL but small and OLDER than the melee jobs: 2.74 deg at 11-09 15:03 (war_charge's code as found), 1.29/1.87 deg since 18:24 (spear_warrior.fbx 17:16, PersonHit.cs 17:31); passed 11-08 13:54. The same with FootPlant off, foot moves off, the hit reaction Off, a graphics device, 0.1-0.43x animator speed, and 8 or 15 s of standing instead of 3 (1.29 every time): a fixed head offset, not a pile-up. The only presentation change in that window big enough is the v0.9.45 landing into the mirror (11-08 ~21:00: DetailedUnits.cs, FootPlant.cs, UnitRenderer.cs, DetailedUnits.asset from main); the 16:00 snapshot is after the break, so it cannot bisect it. Not fixed (not this week's melee work; cosmetic, under 2 degrees). - WarinessTests.AtWarinessZeroPeopleAmongTheHerdsChangeNothing: STALE PIN. Its real checks pass (no herd grows warier, no range home); only the 2-minute world hash moved. Last logged 11-05 (pin value), next 11-10; no code it runs changed after the 11-09 16:00 snapshot except the melee rules, which it does not touch (same hash at 120 and 121). Left. - MigrationTests.TwoHerdsTradeRangesAndTrekAcrossTheMap: PLAY-DEPENDENT PREMISE. Map 3141 (the 9th of 16; the first 8 pass) has no kind with two eligible roaming herds at minute 7. EcologySystem.Migrate is unchanged since before the snapshot; last ran 10-29. Left. - FreezeScanBatch3Tests.IRouteChecks...: PLAY-DEPENDENT PREMISE. Cold/warm determinism holds; in its 6 AI minutes no seat asks about two drop-offs in one think. AiBrain unchanged since the snapshot; any rules change since 10-29 can do it. Left. None of the three could be run on an 11-08 tree (E: is off limits; the snapshot is rules 118 and may not be copied over). 5. MELEE STANCE DISTANCE (presentation only) Cause: two at grips stood at the MEAN of their two striking distances (melee steps), so a Ghoul (0.98 m) struck 0.2 m short and a Skeleton Warrior (1.41 m) 0.2 m deep; first blows were short because the guard was still easing in at 1.6-2.2 m/s. One centre distance cannot be both weapons' at once. Now (UnitRenderer.MeleeContact.cs PairAtGrips): the pair hold the LONGER weapon's distance (neither body inside the other), and the shorter weapon steps its difference in over its own wind-up and back after its blow (in its committed step's plan, so FootPlant steps it); within 0.15 m they keep the shared mean as before. The first blow's wind-up takes up what is left of the guard by its impact. In the game (num_all.ps1, same 7 duel stages as melee steps, 24 s each, before = the build as found): share of blows within 15 cm before -> after melee steps "old" (= melee drawing) for reference Ghoul 17% -> 58% 42% Grunt 73% -> 82% 96% Heavy Guard 88% -> 86% 100% Brute 89% -> 89% 89% Orc Berserker 100% -> 89% (1 of 9) 89% Skeleton Warrior 67% -> 33% (3 vs 7 blows) 33% Spear Warrior / Villager 100% -> 100% 100% Planted-foot slide (cm, both feet, per blow): later blows median 0.0 for every unit both ways; worst later blow: Ghoul 49 -> 125 (melee steps reported 107), Skeleton 41 -> 26, others unchanged (<= 15). First blows: Grunt 50 -> 39, Heavy Guard 16 -> 3, Villager 19 -> 7, Skeleton 76 -> 0.3, Brute 129 = 129, Berserker 58 = 59. Where it falls short: when both blows land on the same tick (equal swing times, as in a 1v1 Ghoul v Skeleton: every blow) the distance is the shorter weapon's: the Ghoul touches (-0.05) and the Skeleton's blade goes 0.43 m deeper than its touch (-0.48; was -0.25) - contact both ways, no air, bodies 0.98 m apart. First blows of a pair arrive together for the same reason (Heavy Guard -0.37, Berserker -0.20 on their first). A Ghoul striking a Skeleton busy with another Ghoul can still land short (+0.1 to +0.5: its step limit). Target "back to the melee drawing numbers" met for Ghoul and Brute, not for Grunt, Heavy Guard or Skeleton. Small samples (5-13 blows a unit). FINAL TEST TABLE (local file: v1: 64 classes = every class in the triage's FAILURES.csv, the 7 it edited, and the feature groups: MeleeSurround(+Switch), SurroundPassage, MeleeEngageSprint, WarCharge, ShieldBlock, WeaponParry, MissileImpact, LiveMissile, ArrowSpeed, MeleeDrawing, MeleeSteps, FootPlant(+People), PersonHit, CombatPresentation, SquadSpacing, FormationMarch/Rank, Gate, GateLock, WallLine, WallGateHold, Pteranodon(+Detailed, Fishing, FlightUnchanged), Predator, WildHerdMakesWay, CreepLeashAndEmptyHerdFix, Spell, LateMatchAbilityInteraction, DetailedSpellCastDispatch, StateHashChunk, SaveGame, Replay, OrderAndReplayFix, CombatBatch/Depth/Realism, AiTests incl. IdenticalAiGamesProduceIdenticalStateHashes; Explicit probes excluded as the triage did). Peak 4.9 GB, 8 min. total 645, passed 587, failed 58: 46 stale by design (Codex re-review guards: Peon* 25, Villager stance/grounded 19, Archer settled stance 1, BugHunt4 1) - untouched 5 pre-existing (ApprovedMining, ArcherSupportGrip, Architecture, CurrentBuildingArrowProfile, VillagerStance controller) - untouched 1 environment (StuckWindup BlowsTaken: passes with a graphics device) 3 real, older than this week's melee work, not fixed (StuckWindup PersonHit x3, 1.3-1.9 deg) 3 stale pin / play-dependent premise (Wariness, Migration, FreezeScan) Determinism: AiTests.IdenticalAiGamesProduceIdenticalStateHashes, MeleeSurroundTests' same-fight-twice and the replay/save groups pass; the pinned traces gave the same values in every run today (r01, r07, r12, v1). COST (perf_run.ps1, 4v4 computer players, 10 min, seed 5, one run each; local file: perf_v4v4_*): sim_ms avg / max TaskSystem avg/max MovementSystem avg/max fps units / deaths before (120) 1.683 / 47.2 0.15 / 45.6 0.19 / 0.6 163.9 723 / 347 after (121) 1.637 / 58.2 0.15 / 1.2 0.19 / 0.8 168.4 731 / 367 No increase in average tick time; Task/Movement averages unchanged. The single-frame maxima are noise (before's 45.6 ms spike hit Task/Snapshot/HerdThink together; after's 57.7 ms is in AiSystem). The game differs from tick ~ the first soldier fight on (different state hash), as a rules change should. FILMS (ingame, copies in local file: 18_regression_fixes and local file: Primeval_review; before = the test player as found, local file: before_build; after = the rebuilt one; side by side, then quarter speed; test windows, gated) regression_fixes_a_orc_shamans_cast.mp4 (10.2 MB): -showcaseTarget spells; the shamans raise their staffs in the Cast clip in BOTH: the cast was never broken in the game (item 3). regression_fixes_b_walker_through_own_gate_under_attack.mp4 (11.2 MB): -showcaseTarget gates, six grunts at the first gate, a villager walking through it; whole frame. Reads about the same before and after (this stage's attackers walk round the end of the wall; the stuck case is the test's, 0 -> 3 crossings). regression_fixes_c_ghoul_vs_skeleton_own_distance.mp4 (15.1 MB): close, low; after, the ghoul is drawn in to its own touch. I looked at stills and crops, not at motion. NOT VERIFIED / LIMITS - Nobody has played it. One cost run each way. No multiplayer session; old saves not loaded specifically (SaveGame/Replay pass). - The shooters' shuffle cap (rules 120) is left as built for players' shooters fighting people (MovementSystem has no attack kind). - Wild/creep/beast scope: covered by the code paths and the pteranodon tests; raptor packs, creeps and tame beasts not staged. - Item 4's older failures were categorised, not bisected to a commit. FILES changes.diff (7 changed files against local file: orig + the new test in full). Changed: local file: SimulationHost.cs, local file: TaskSystem.cs, Task.Surround.cs, MovementSystem.cs, local file: UnitRenderer.MeleeContact.cs, local file: DetailedSpellCastDispatchTests.cs, PteranodonFlightUnchangedTests.cs. New: local file: SurroundPassageTests.cs (+ .meta). local file: mine = final copies; local file: orig = as found; removed = my diagnostic test files (moved out after use) and the GraphicsSettings.asset the build flipped (restored to its start hash). No art, .fbx or .meta of art touched; settings and unit materials match their start hashes; git status = start + the new test and its .meta. CatapultInstallTemp.cs left in place. The test player local file: CatapultTest holds the final code (build1, rules 121).
Grunts vs Spear Warriors, watch the feet: one real step in, then the stance is held.
Brute vs Heavy Guard, a long exchange: no back-and-forth, feet planted.
Berserker vs Heavy Guard, a long exchange.
Ghouls vs Skeleton Warriors: the pair whose contact got worse (see Regression fixes).
Grunts striking a hut with planted feet.
6 vs 6 at the game camera.
MELEE STEPS - fighters step in on planted feet and hold their stance - report, 2026-11-10 (local file: PrimevalMirror only, nothing committed) Owner: "the units are sliding forward in order to make contact visually and is it possible to have it do steps with real foot contact? Because the sliding is off putting"; refinement mid-job: "once he does make contact the unit can stay up and in the enemy's space while repeatedly swinging and hitting; they don't have to keep moving back and forward." PRESENTATION ONLY. No simulation file touched; RulesRevision stays 120; no save, net or replay change. WHAT A PLAYER NOW SEES (test build local file: CatapultTest, build13) - A fighter walks the last metres in (its "guard" is taken up during the approach, walked), then on its first blow its lead foot (the one nearer the man) lifts, travels in an arc and plants on the impact frame (or the one before) where the clip has it; the rear foot stays and pivots; the hips settle a little over the wide stance. - After that first blow it STAYS in: no lunge back and forth. Every later blow is struck from the planted stance (the attack clip's own arms and body), feet held. It re-plants only when the body drifts about 0.3 m off its feet (FightTrigger 0.36 of the leg: target shoved or walking away, rules-120 corrections bigger than that, a new place or a new target), then with a real step. Small sim corrections are absorbed by the stance (the drawn body is never drawn nearer than its mark). - Heavy fighters (Brute, Abomination, Heavy Guard, Chieftain, Warchief, Berserker) play the existing "stomp" footfall when the committed step lands. No dust puff (not done). - Switch: -meleeSteps off = the melee drawing exactly (the before). -logSteps = per-blow foot diagnostics ([Steps] lines). HOW IT WORKS 1. MeleeContact: the lunge per blow is capped at what one step carries (0.35 x the drawn leg for people, 0.25 for beasts and mounts, 0.15 m when the feet are not planted: past the people budget / low-poly). The rest of the full step (same total as the melee drawing) is the guard, so the reach on the impact frame is unchanged. The lunge eases in over the whole wind-up (lands with the blow) and is then HELD while the unit fights that target (reset on a new target, knock-down, fight over). 2. UnitRenderer.MeleeContact: guard taken up in the last 3 m of the approach (arrival distance, not current; never for a Hold unit; of two walking at each other only the lower id); a drawn body is never drawn nearer than wanted (the rules walking it in shrink the guard at once); two at grips keep one stable split (lower id keeps) and stand at the mean of their two marks. 3. FootPlant: people (and mounts) are put in Hold during the swing and block (and the first half of the crossfade out, which used to hand the feet to the clip and slide them); new Strike(): the lead-foot committed step timed to the blow, stride capped at 0.7 leg; in a fight: step trigger 0.36 leg (normal again while the body is still walking in), held reach 0.5 leg, hips may settle 0.15 leg, the rear leg may straighten, a foot the leg cannot reach steps instead of being dragged. 4. Beasts (protoceratops etc.): the existing dinosaur Hold (feet held, steps under the body) with the smaller lunge. Mounts (raptor/wolf riders): the mount's feet now Hold in the bite instead of being the clip's. Riders on top unchanged. NUMBERS A. On the models (MeleeStepsTests, real Idle/Attack clips crossfaded at 60 fps, 4 blows 1.2 s apart, local file: model_slide.txt), feet sliding while down (cm, both feet, wind-up + follow-through, 4 blows) before -> after, longest in one stance: grunt 1650 -> 6.1 (850 -> 1.9) brute 1613 -> 3.9 (847 -> 1.4) berserker 1465 -> 4.8 (807 -> 1.3) spear warrior 649 -> 1.3 (96 -> 1.2) heavy guard 1597 -> 4.3 (805 -> 1.3) villager 1607 -> 3.4 (825 -> 1.7) ghoul 1562 -> 3.8 (825 -> 1.3) skeleton 1604 -> 5.1 (825 -> 1.6) chieftain 1585 -> 5.7 abomination 1612 -> 5.7 (2.0) raptor rider 61 -> 35 (24 -> 12) wolf raider 2946 -> 13 (810 -> 4.5) protoceratops 1252 -> 9 (79 -> 3.5) Feet FootPlant holds: <= 1 cm per stance for every person (target 2 cm); 1 committed step, 0-1 re-plant in 4 blows. B. In the game (build13, -logSteps -logContact, duel stage, close camera, all planted; steps_summary.ps1, local file: steps_summary.txt; "old" = same build -meleeSteps off). Per blow, slide of both feet while down over the whole blow (cm), first blow apart: unit first blow old->new later blows median old->new (worst new) gap median old->new in 15 cm old->new grunt (+hut) 350 -> 0 457 -> 0.0 (15) -0.05 -> -0.05 96% -> 87% brute 308 -> 129 536 -> 0.1 (0.1) -0.05 -> -0.05 89% -> 89% orc berserker 541 -> 58 309 -> 0.0 (0) -0.05 -> -0.05 89% -> 100% spear warrior 28 -> 8 68 -> 0.0 (0) -0.05 -> -0.05 100% -> 100% heavy guard 411 -> 4 399 -> 0.0 (7) -0.05 -> -0.05 100% -> 89% villager 219 -> 19 214 -> 0.0 (0) -0.05 -> -0.05 100% -> 100% ghoul 167 -> 26 152 -> 0.0 (107) +0.21 -> +0.17 42% -> 17% skeleton warrior 211 -> 3 206 -> 8.0 (27) -0.05 -> -0.27 33% -> 33% raptor rider 136 -> 32 6 -> 6.2 (12) -0.05 -> -0.05 100% -> 100% wolf raider 676 -> 87 501 -> 20 (92) -0.05 -> -0.05 100% -> 100% risen protoceratops 622 -> 254 338 -> 16 (16) -0.05 -> -0.05 100% -> 100% Re-planting in a long fight: lifts per later blow 0.0-0.4 for Grunt, Brute, Heavy Guard, Villager (Berserker 1.0 and Spear Warrior 4.0 are their attack clips' own authored foot lifts); Brute v Heavy Guard 9 blows, Villager 13 blows, no re-plant. WHAT STILL SLIDES, HONESTLY - The FIRST blow of a fight: the sim's first swing starts a sixth of a second after arrival while the body is still being walked in; feet are held and some drag (Brute 1.3 m summed over both feet, Berserker 0.6 m; Grunt, Guard, Skeleton ~0). - Mismatched pairs at grips (Ghoul 0.98 m vs Skeleton 1.41 m striking distance): with the stance held they stand between the two marks, so Ghoul blows land ~0.2 m short and Skeleton blades ~0.2 m deep (skeleton median gap -0.27, was -0.05). This is the one contact number that got worse; the alternative (alternating step-ins each blow) brought the back-and-forth back and slid 60 cm a blow, so I took it out. Needs a decision (fix idea: let the shorter weapon lunge its difference with a strike step). - Mounts and beasts: wolf raider 20 cm a blow, protoceratops 16 cm a blow, raptor rider 6 cm (their generic Hold steps). - In-game "within 15 cm" dropped a little for Grunt (96 -> 87%) and Heavy Guard (100 -> 89%): first blows. - Heel-then-toe on the committed step not done (the ball is the planted point, the clip keeps its heel); no dust puff. COST (30 Grunts v 30 Spear Warriors, 1920x1080, 40 s, two runs each, local file: perf_steps_*): fps 169.7/168.6 off vs 170.0/170.3 on; units render 0.60-0.61 ms both; units alloc 1.11-1.30 kB/frame both (noise). No new per-frame allocation (arrays grown once; the meter allocates only with -logSteps). TESTS New local file: MeleeStepsTests.cs, 5 tests, 5 pass: lunge cap / split / fallback / curve; on every person's model: a held foot moves <= 5 mm a frame and <= 2 cm a stance, the step lands on the impact frame or one before, stride <= 0.7 leg, no leg past 99.95% straight, the held foot is reached, one committed step and <= 2 re-plants in 4 blows; beyond the budget the lunge is <= 0.15 m; in the renderer the step is held through later blows (old mode steps back); the slide table. Changed MeleeDrawingTests.TheStepEasesInOverTheWindupAndOutAfter: pinned to -meleeSteps off (it tests the old curve). Related groups (local file: final_related, 280: MeleeSteps, MeleeDrawing, FootPlant, FootPlantPeople, PersonHit, CombatPresentation, DetailedAttackRecovery, DetailedInjuredAttackTiming, DetailedUnitSize, Glide, ShieldBlock, WeaponParry, StuckWindupPose, MovementFeel, PeonCombatReady, SquadSpacing, MeleeEngageSprint, WarCharge, MeleeSurround): 228 pass, 51 fail, all on the known list or failing identically on the code as found (melee drawing report): DetailedUnitSize 44 (gait pace / hips sway / ghoul height), StuckWindupPose 5, MeleeEngageSprint six-places, WarCharge shove. Earlier run: DetailedControllerMotionPreservation ("villager") timed out at 180 s while films ran alongside - not rerun. FILMS (ingame, copies in local file: 17_melee_steps and local file: Primeval_review): before (untouched test player, local file: before_build) | after (build13), then quarter speed. 13-18 MB each. melee_steps_b1_brute_vs_heavy_guard_long_exchange.mp4 READS BEST: 12 s exchange, before the boots skate, after they stay put (checked frame by frame at the feet: local file: check_b1_feet_before/after.jpg). melee_steps_b2_berserker_vs_heavy_guard_long_exchange.mp4 long exchange, holds the stance. melee_steps_a_grunts_vs_spear_warriors_feet.mp4 knee height; busy, blood and grass partly hide feet. melee_steps_c_ghouls_vs_skeleton_warriors.mp4 feet partly behind grass. melee_steps_d_grunts_strike_a_hut.mp4 the Grunts are at the frame edge (camera as for the before). melee_steps_e_6v6_game_camera.mp4 difference small at that distance. NOTE: the ground looks different between before and after (the ground worker's WIP is in the shared build). NOT VERIFIED Nobody has played it; I looked at stills and frame strips, not motion. Shoves / War Charge arrival / staggers / blocks are covered by Hold and the existing re-plant rules but were not filmed. Low-poly units, units past the budget (small lunge only), riders' bodies, walls/gates/other buildings, Death Rider, Tame Tyrant: not looked at. Crossfades are modelled in the harness, not the Animator's exact blend. FILES: changes.diff (7 changed files against local file: orig, 2 new in full; copies local file: mine). Changed: local file: MeleeContact.cs, UnitRenderer.MeleeContact.cs, UnitRenderer.cs, local file: DetailedUnits.cs, local file: FootPlant.cs, local file: GameBootstrap.cs, local file: MeleeDrawingTests.cs. New: local file: MeleeStepMeter.cs, local file: MeleeStepsTests.cs (+ .meta). No art, fbx or meta of art touched. GraphicsSettings flipped by builds: reverted; git status = start + my 2 new files (+meta) + the ground worker's GroundLayers.Placement.cs. CatapultInstallTemp.cs left in place. local file: Primeval not touched (git reads the worktree metadata there). The shared test player was also being rebuilt/used by the ground workers; one of my builds (build10) failed because their window held the files - rebuilt (build11-13) once free.
Twelve Grunts into four Spear Warriors, high camera: does the group wrap round? (Reads only partly here.)
Twelve Grunts into four Heavy Guards: the clearest wrap of the set.
8 vs 8 line fight: the overflow goes round the flanks. Looks more chaotic than before.
3 vs 3 long fight: fighters step back into reach between blows instead of swinging at air.
Surround at the normal game camera.
MELEE SURROUND AND REACH SLACK - report, 2026-11-09 (work in local file: PrimevalMirror only, nothing committed) Owner: "the bigger group would circle around ... You wouldn't want them to get stuck or like awkwardly slide ... there's a certain spacing and a certain spreading out"; "if it's pretty free, that would be awesome"; units "1 to 2 people length away from the target ... and it's showing as if they're hitting them". SIMULATION RULES CHANGE. RulesRevision 119 -> 120 (one bump, comment names both rules). Save format NOT bumped (the new per-unit state lives in two WildMoves fields a soldier never used, already saved and hashed; 0 in an older save = "no place yet"). NetProtocol NOT bumped (no command, no wire change). Replays carry the rules revision as before. WHAT A PLAYER NOW SEES - A bigger melee group meeting a smaller one no longer piles onto the nearest one or two enemies and swings over its own men's shoulders: each enemy has a few places round it (about 4 round a man), the first arrivals take the places in front, the rest walk round the flank of the fight to places at the side and the back, and the group wraps the enemy within a few seconds. Soldiers with no free place wait just outside the fight and step in when a place frees. - Equal groups meeting: front-to-front, the overflow goes round the enemy group's flanks rather than into its ranks. - Fighters no longer slide sideways at up to 1.5 m/s while swinging: the shuffle is capped at 0.6 m/s. - In a fight that drifts (shoves, a target stepping away), a soldier steps back into its striking distance between blows instead of swinging at air from up to 1.2 m too far; only a swing already winding up still lands at the longer reach. - Melee soldiers attacking a building stand about 0.8 m closer to it (1 m of weapon range instead of their full range). THE RULES AS BUILT (both behind their own constant in Catalog; MeleeSwitches lets a test switch either off at run time) A. Catalog.MeleeSurround (new file local file: Task.Surround.cs; hooks in TaskSystem AttackMoveTick, AttackTick, AttackTargetInPlace and the idle auto-acquire; built on rules 48's AttackSeat, which melee soldiers no longer use; shooters keep their firing line exactly as before). - Places per foe: floor(2 pi (foe radius + attacker radius) + 2 x a long body's length) / (1.5 x attacker width), 3 to 12. A man offers a man 4, a Brute offers a Grunt 4, a triceratops 7, a long tyrant up to 12. - A place is a direction round the foe (32 per turn) on the attacker's striking ring (reach - 0.3 m, as AttackSeat's), at least 360/places from every place already held there, on the lattice the first holder set (so they pack), not on blocked ground, not where another body stands, and (for one coming at the foe's group from outside) not in among the foe's own friends. The first to come takes the place straight ahead of it, so a line meeting a line stays a line. - Held in WildMoves.SurroundClaim / SurroundSinceTick (target + direction, and since when). Released when the holder dies, takes another target or stops fighting (derived: nobody has to clear it). One walking to its place looks again only after 3 s; a held place keeps a soldier from switching targets for 3 s; target re-looks on attack-move every 0.5 s, and a held place is given up only for a free one 1.5 m nearer. No unit changed place twice within 2 s in any test. - Target choice (attack-move, idle auto-acquire, target dead): the nearest of the four nearest foes in sight that has a place free; all full: the one it had or the nearest, and it waits outside that foe's ring. An explicit attack order is always obeyed: the unit keeps the named target and waits for a place round it (retrying every 1/3 s). - Overflow routing: a place 56 degrees or more round the foe from where the soldier comes in is reached by steering to a point on a ring outside the fight (ring + body + 0.8 m), 45 degrees on round toward it, re-aimed each tick, then straight in; it may strike only once within 22 degrees (or 0.8 m) of its place. Walking is ordinary locomotion (MovementSystem). - Shuffle while swinging capped at 2 cm a tick (0.6 m/s; was 5 cm, 1.5 m/s) in MovementSystem. - Engage sprint and War Charge untouched (they still need an open line to the target). B. Catalog.MeleeTightSlack (TaskSystem.SwingCommitted, AttackBuildingInPlace). - The 1.2 m committed-swing lunge now applies only while the swing is winding up (the last wind-up before its impact); between blows a melee soldier may stand Catalog.MeleeDriftSlackCm = 30 cm past its reach, further out it walks back in. Shooters unchanged. Settled at 30 cm: no swings lost against a walking target (52 blows in 30 s both ways), no jitter seen. - At a building a hand weapon reaches from min(range, 1 m) + radius + 0.3 m (fix plan 6a; Catalog.MeleeBuildingRangeCm). 0.5 m was tried and dropped: four Grunts coming at a corner bunched on one perimeter cell and two stood idle. A soldier crowded off the wall by a friend already striking it (that friend between it and the wall) strikes from where it stands, within rules 119's reach, so nobody stands idle at a wall. COST (autotest reports, one run each, same seeds; local file: perf_run.ps1, local file: perf_*) sim_ms_avg sim_ms_max TaskSystem avg/max ms MovementSystem avg/max ms fps 5,000-unit stress before 4.725 38.53 0.29 / 10.8 1.62 / 6.9 64.0 after 4.535 10.62 0.26 / 1.4 1.60 / 2.3 64.3 4v4 AI, 10 min before 1.672 23.71 0.14 / 12.3 0.19 / 0.7 166.1 (728/723 units, after 1.671 14.48 0.14 / 1.1 0.19 / 0.7 168.7 354/347 deaths) No measurable increase in average tick time and no new worst-tick spike (the maxima are lower after, but one run each: single-frame maxima are noise, not a win). The stress test's units do not fight (deaths=0), so it only shows the idle paths cost nothing; the 4v4 is the real melee load. By construction the choosing (one hash scan round a foe) runs only when a soldier picks or re-picks a target or place; one walking to its place reads its stored place, which is cheaper than rules 48's rank count it replaces (that scanned the hash every tick for every approaching melee soldier). Nothing given up. BALANCE (MeleeSurroundSwitchTests.LogSurroundClashes: 60 s, 8 seeds, no AI; A attack-moves from 26 m, B attack-moves back or, for shooters, stands. Share of starting health left, A first; "ahead" = A has more left. "neither" = rules 119 bit for bit. B alone came out equal to neither in every matchup (within 0.01), and both = A alone, so two columns are shown.) matchup (A vs B) neither (= B alone) A alone (= both) equal numbers 8 Grunt vs 8 Clubman 0.494 / 0.000 8/8 0.489 / 0.000 8/8 8 Grunt vs 8 Spear Warrior 0.118 / 0.052 4/8 0.087 / 0.003 7/8 8 Ghoul vs 8 Grunt 0.000 / 0.589 0/8 0.000 / 0.472 0/8 unequal numbers, equal cost (1200) 12 Clubman vs 6 Brute 0.700 / 0.000 8/8 0.633 / 0.000 8/8 15 Ghoul vs 6 Heavy Guard 0.653 / 0.000 8/8 0.565 / 0.000 8/8 melee vs ranged (1200) 13 Grunt vs 10 Bowman 0.574 / 0.000 8/8 0.551 / 0.000 8/8 7 Spear Warrior vs 12 Skel. Archer 0.000 / 0.604 0/8 0.001 / 0.583 1/8 Reading: no winner changes except the knife-edge Grunt vs Spear Warrior (4/8 -> 7/8 for the Grunts). Against expectation, surrounding does NOT help the many cheap units: they keep 7-9 points less health, because the place cap (4 round a man) stops the over-the-shoulder blows the back rows used to land; fights last a little longer and the outnumbered side trades longer. The underdog does a bit better in Ghoul vs Grunt (Grunts keep 0.47 not 0.59). B changes nothing measurable. If the owner wants numbers to pay off more, the lever is the places count (Catalog/Task.Surround: 1.5 widths each; 5 round a man would be 1.2) - not tried. TESTS New: local file: MeleeSurroundTests.cs (7 tests) and MeleeSurroundSwitchTests.cs (3 tests + the explicit balance probe). Both compile on rules 119; the switch file needs MeleeSwitches (added after the baseline). Reproduce first, on the code as found (local file: swap.ps1 put the rules-119 files back; local file: base3, base4), then after (local file: final_tests): rules 119 (as found) rules 120 12 Grunts attack-move into 4 Spear Warriors (2x2, 3.5 m): FAIL pass most face to face at once (no body on the line) 11 (in reach of some enemy: 12) 10-12 (12) widest spread of attackers round each defender 133/111/0/0 deg (back two never reached) 165/175/136/89 closest two Grunts / Grunt to a defender (of contact) 0.43 / 1.49 0.56 / 0.57 fastest sideways move while striking 1.50 m/s 0.60 m/s first time 10 fight face to face 3.1 s 5.0-5.2 s (they walk round) places capped: 8 Grunts ordered onto one Spear Warrior 8 striking: FAIL 4 (places 4); all 8 kept 10 Grunts onto a risen triceratops 10 striking 7 (places 7) the target 6 into a line of 6 (holding) stays a line pass (0 behind, 6 fighting) pass (0 behind, 6) nobody changes place twice within 2 s 0 (bearing measure; 1 by the first, 0 noisier octant measure) same fight twice, same state hash pass pass blows on a target shoved 0.9 m every 2.5 s: median past 0.88 m, 29 of 52 > 0.5 m: FAIL -0.07 m, 8 of 52 > 0.5 m reach (the 8: a swing already winding up when shoved) blows on a target walking at 2 m/s 52 blows, median 0.38, worst 0.63 m 52 blows, 0.12 / 0.27 m Grunts striking a town hall, off its footprint (m) 2.47 2.47 2.53 2.53: FAIL 1.65 1.72 2.17 2.48, all 4 striking (corner approach: two crowded off, see B) with both rules switched off, the wrap fight's hash = rules 119's (5057818561072223846): pass (A and B are cleanly removable) The brief expected "today far fewer" within striking distance; in fact today almost all 12 are in reach of someone: they strike over their own men's shoulders from the second rank. What fails today is the spread (the back of the enemy group is never reached) and the crowding. Related groups, final code (local file: final_tests; 248 cases: the two new files, FormationMarch, FormationRank, SquadSpacing, MeleeEngageSprint, WarCharge, ShieldBlock, WeaponParry, AiArmyControlFix, StateHashChunk, CombatBatch, CombatDepth, CombatRealism, AttackScheduleTask, MovementFeel, TribeRiders, SaveGame, Replay, AiTests.IdenticalAiGamesProduceIdenticalStateHashes, CannotRecover, ArrowSpeed determinism, CannotRecoverProbeTests.RunDuels): 243 passed, 4 failed, 1 not run/inconclusive. Same groups on rules 119 (local file: base1, 244 cases): 239 passed, 4 failed (my 3 reproduce tests + SavedCombatFixture). 1. MeleeEngageSprintTests.ASquadOfSixStillTakesSeparatePlaces - CAUSED BY RULE A, by design: it wants 5 of 6 Clubmen swinging at one Grunt; the owner's "about 4 round a man" allows 4 (the other 2 wait at arm's length). Confirmed: passes with A as rules 119 would have it? (not run alone; it failed with B switched off, so it is A). NOT changed by me: the assertion should become "at least min(5, places)" if the lead accepts the cap. 2. WarChargeTests.AChargedBlowCaughtOnAShieldShovesAndStaggersNobody - CAUSED BY RULE B: it measures the shove 15 ticks after the blow; the shoved Heavy Guard now steps back into reach between blows, so it is back where it was. Passes with B switched off (local file: check_slackoff). The shove still happens (event Struck); the test should take the largest displacement in those 15 ticks. NOT changed by me. 3. CannotRecoverProbeTests.RunDuels - see below (fails at 119 too, with other matches). 4. StateHashChunkTests.SavedCombatFixtureHashesRemainBitIdentical - known stale; same value as at 119. Re-pinned, 1 plain state-hash golden: FormationMarchTests.TightGolden 14572433427997916768 -> 9257906358234786457 (twelve Clubmen attack-move into two enemies: places and the slack move them; with both rules off it matched 119 exactly). Not run (known failing / not mine): StuckWindupPose, MountedRagdollEligibility, PteranodonFlightUnchanged, EdgeScenery, GroundLayer, CurrentBuildingArrowProfile, the long AI balance matrix, AiLongGame, HerdPathProbe. Peak editor memory 4.2 GB. RUNDUELS (the loose end) CannotRecoverProbeTests.RunDuels: an explicit slow probe (6 Hard AI duels, 25 min each, yields every 900 ticks; about 4 min). Every 6 minutes it kills all of one seat's workers ("a wipe") and fails if a seat with a living worker, or a seat within 2 minutes of a wipe, is flagged "cannot recover" (the forfeit heuristic), or a flag is later lifted. - rules 119 as found: FAILS - Orc-Undead/0: wipe at minute 12, flagged at 13.9 (wipesFlagged 1). Identical to the arrow worker's table (local file: base3). - rules 119 with the four arrow-speed constants put back to 118 (Bowman/Skeleton Archer 28, Crossbow 30, towers 30; work arrow_restore_backup, restored after): PASSES, no wipe flagged (local file: runduels_arrows118, reveal_probe_arrows118.tsv). - rules 120 (final): FAILS again, a different match - Orc-Undead/1: flagged at 24.9 within 2 min of a wipe. Reading: the arrow speed did not break anything specific; any rules change re-rolls the six matches, and in some of them the cannot-recover heuristic flags a seat that is rebuilding from a staged worker wipe. It is a latent weakness of the forfeit heuristic (or of the probe's 2-minute grace) that 118 happened not to hit. Needs the heuristic's owner. FILMS local file: ingame (copies in local file: 16_melee_surround and local file: Primeval_review). BEFORE = the untouched test player copied at the start (local file: before_build, rules 119), AFTER = build3 (final code), same duel-stage arguments (-duelShift 20, -camClose), side by side then quarter speed, 1920x540. melee_surround_a_12_grunts_into_4_spear_warriors_high.mp4 (a) 11.4 MB. The Spear Warriors die in about 4 s, so there is little time to wrap. Before: one dense blob on one side. After: looser, some Grunts go round the left end. READS ONLY PARTLY. melee_surround_a2_12_grunts_into_4_heavy_guards_high.mp4 (a2, extra) 9.5 MB. The same with Heavy Guards, who last. Before: the Grunts pile on the near side, the Guards struck from one side only. After: Grunts on several sides of the Guards (left, below, behind) by about frame 200. THE CLEAREST WRAP. Still a scrum, not a neat ring. melee_surround_b_8_vs_8_line_fight.mp4 (b) 11.5 MB. The duel stage spawns each side as a clump, not a line. Before: two blocks meet on one front. After: the fight spreads and the sides intermix more; it reads as a brawl, not a line. (The holding-line test stays a line; mutual attack-moves of clumps do not.) melee_surround_c_3_vs_3_long_fight_reach.mp4 (c) 11.7 MB. Low close view, Grunts against Clubmen. The Clubmen die in about 7 s, so it is not the long drifting fight asked for; blows look at grips in both. WEAK EVIDENCE for the slack: the numbers above (shove and walk tests) are the evidence. melee_surround_d_normal_game_camera.mp4 (d) 16.1 MB. From the game camera the difference is small. I looked at stills and contact sheets (local file: check_*.jpg), not at motion. Sliding: the sim shuffle is now 0.6 m/s (was 1.5), and I saw no unit gliding in the stills, but sliding is a motion judgement I could not make from stills. NOT VERIFIED / LIMITS - Nobody has played it. Stills only. Whether the wrap reads, and whether the walk round the flank reads as purposeful or as milling, is the owner's call. In film (a) it reads only partly; (a2) best; (b) looks more chaotic than before. - The cap makes larger groups slightly weaker per head (balance above). Not tuned; 4 places round a man is the brief's number. - Two existing tests fail because of the new rules and were NOT edited (Engage squad-of-six: A; War Charge shove: B). - Buildings: no place cap round a building (attackers still pick the nearest perimeter cell); corner approaches crowd and two of four struck from rules-119 distance. Only a town hall tried. Riders, beasts, long bodies (tyrant), units in front of a T-Rex, a wild pack attacking, hunts: covered by the code, not tested or filmed. - The flank waypoint circles one foe; it can lead through that foe's neighbours (closest Grunt to a defender 0.57 of contact). No path-finding round a whole enemy group. - Waiting units with no place stand on an outer ring at their own bearing; they do not walk round to a better spot. - Cost: one run of each, no repeats; the 5,000-unit stress has no fighting. Old saves: not loaded specifically (SaveGame and Replay groups pass). No multiplayer session. - RunDuels: not fixed, it is the forfeit heuristic's. FILES changes.diff (6 changed files against local file: orig, 4 new files in full; local file: mine_final copies). Changed: Simulation local file: TaskSystem.cs, local file: MovementSystem.cs (shuffle cap), Components.cs (two accessors), Catalog.cs (constants), SimulationHost.cs (RulesRevision, switch reset), local file: FormationMarchTests.cs (re-pin). New: Simulation MeleeSwitches.cs, local file: Task.Surround.cs, local file: MeleeSurroundTests.cs, MeleeSurroundSwitchTests.cs. work (unity.ps1, swap.ps1, capture/film_all/encode/perf_run/mkdiff scripts, orig, before_build 3.8 GB), logs. No art, .fbx or .meta of art touched; git status = the start plus my 4 new files and their .meta; no settings noise. CatapultInstallTemp.cs left in place. local file: Primeval never touched (git status reads the worktree's metadata there, read-only). The test player local file: CatapultTest holds build3 (rules 120).
Close lab view of this unit's blow against a target: where the weapon meets the body.
Close lab view of this unit's blow against a target: where the weapon meets the body.
Close lab view of this unit's blow against a target: where the weapon meets the body.
Close lab view of this unit's blow against a target: where the weapon meets the body.
Grunts vs Spear Warriors, close: attackers turn to their man and step in to their own reach.
Three Grunts round one Chieftain: the second and third attackers turn to him too.
Grunts striking a hut: they now walk up to the wall.
The Berserker alternating his two chops, A and B.
Melee contact at the normal game camera.
PRIMEVAL - WHY MELEE BLOWS LOOK LIKE THEY MISS measurement audit, 9 Nov 2026 ==================================================================================================================== Read-only. Nothing was run in the game, nothing changed. Rules read in local file: Code; every Attack clip measured in headless Blender from the mirror's FBX files as they stood this evening (25 units with a detailed model: all three factions, heroes, riders, workers, the risen beasts). Numbers per unit: CONTACT_TABLE.txt. Pictures: contact_worst_cases.jpg. What to do about it: FIX_PLAN.txt. "A man" below = a Grunt (2.0 m tall, 0.6 m front to back; 0.92 m across in the rules). All distances centre to centre. THE SHORT ANSWER The owner is right, and there is not one cause but a stack of them. Standing square to a lone target the game is already close: of 25 units, 12 touch, 3 miss by under 20 cm, 10 miss clearly (nine by 23 to 38 cm, the Risen Protoceratops by 1.14 m). The "one to two people away" comes from four things on top of that, each worth about a metre: the 1.2 m of slack, attackers not turned to face their target, buildings, and beast-bodied units. THE CAUSES, BIGGEST FIRST 1. THE RULES FIGHT FROM ABOUT A METRE FURTHER THAN THE WEAPONS REACH. about 1.0 m (0.7 to 1.3), measured A unit stops when its target is within attack range + its radius + the target's radius: a Grunt at 2.72 m, a Brute at 3.24 m. Its weapon meets a man's body at 1.42 m and 1.87 m. Across the 16 foot soldiers the swing would end 1.02 m short (0.71 to 1.32) if nothing hid it. The combat feel pass of 15 Oct hides it: through the wind-up the drawn body steps in toward the target (never more than 1.0 m, to both radii + 0.6 m, + 1.2 m for spears) and steps back after. Where that step does not exist, the whole gap shows: - AT BUILDINGS nobody steps in. Units stop range + radius + 0.3 m from the edge of the building's footprint: a foot soldier's weapon ends 0.86 to 1.51 m short of that edge (typically 1.2 m; 20 of 25 units clear miss). The walls themselves were not measured; they are at or inside the edge, so the real gap is that or more. - BEAST-BODIED UNITS never step in (risen dinosaurs, tame tyrant, the war triceratops). Risen Protoceratops: beak 1.14 m short of a man, and 0.3 m off the ground (below the knee). 2. 1.2 M OF SLACK THAT LASTS THE WHOLE FIGHT. adds 0.9 to 1.2 m; gap then 0.9 to 1.5 m The "committed swing" rule lets a blow land on a target up to 1.2 m outside reach. As written (TaskSystem SwingCommitted) it is not only mid-swing: once a unit has started fighting it does not walk closer again until its target is more than 1.2 m out of reach. Targets drift (shoved by the crowd, knocked back, walking at their own enemy) and the attacker keeps swinging from where it stands. The step-in is capped at 1.0 m, so it cannot follow. Measured at the far end of that band: 23 of 25 units clear miss, by 1.22 m typically (Grunt 1.41, Brute 1.50). This is the pale figure in the pictures. How often units sit in that band I could not tell without running the game. 3. ATTACKERS ARE NOT TURNED TO FACE WHAT THEY HIT. a blow 60 deg off misses by about 0.7 m Read in the code, not seen: a person's drawn body faces the way it last walked. The rules do set a facing toward the target for a striking unit, but the drawing only reads velocity, and a striking unit's velocity is zero. Only the Bowman has its own turn-to-target. The first attacker walks straight in, so it faces its man. The second and later attackers of the same target walk to a place 30 degrees further round per rank and stop the moment they are in reach: replaying those rules they stop 43 to 65 degrees off the line to the target. With the body 60 degrees off, the same blows miss a man by 0.70 m typically (0.34 to 1.36). The step-in still slides them toward him, so they slide one way and swing another. Needs one look in the game to confirm. 4. WHAT IS LEFT AFTER THE STEP-IN. 0.06 to 0.38 m on 11 of 16 foot soldiers Three small things, added together: - the 1.0 m cap stops long-reach units short of the step's own mark: Grunt and Berserker by 0.2 m, Abomination and Warchief 0.4, Wolf Raider 0.5, Brute and Chieftain 0.6; - the mark assumes about 1.1 m of weapon; short weapons have less (Ghoul 0.77 m, Villager 0.83, Shaman 0.86, Peon 0.91); - bodies are drawn smaller than their collision size: a man's surface is 0.29 to 0.37 m from his centre, his radius is 0.46 (9 to 17 cm of air); Brute and Abomination 0.34 to 0.39 against 0.58 (about 20 cm); a triceratops' flank 0.93 against 1.25 (about 30 cm). Worst: Abomination 0.38, Ghoul 0.38, Chieftain 0.34, Brute 0.33, Wolf Raider 0.30, Shaman 0.28, Villager 0.26, Grunt 0.25, Peon 0.23. When the target is itself a Grunt fighting back, the two stand closer and the Brute and Chieftain do touch. 5. THE ATTACK CLIPS THEMSELVES. - Every foot soldier's Attack is rooted: the hips move 0.00 m forward (only the Berserker's new chop moves). - The weapon is at full reach for a single frame. Typically 0.36 m shorter the frame before and 0.41 m shorter the frame after (Grunt 0.73 / 1.22 / 0.29 m; Abomination 0.89 / 1.44 / 0.16). The contact lasts 1/30 s. - Low blows: Shaman 0.5 m up, Orc Shaman and Ghoul 0.8, Necromancer and Peon 0.9 (knee to hip). - Riders' lances never move: they are fixed to the mount. The tip passes 0.3 to 0.7 m clear of a man, 1.9 to 2.6 m up, half a metre or more to the side. The mount's jaws do the visible hitting. 6. THE OPPOSITE FAULT: DRAWN INTO THE TARGET. The step-in assumes a hand weapon. Mounts and beasts reach further, so they end up inside their target: Raptor Rider 1.0 m into him, Death Rider 0.7, Bone Raptor 0.6, Raptor Bone Rider 0.5, Risen Parasaurolophus 0.7 (at 2.1 m, over his head). The Risen / Tame Tyrant strikes from 4.26 m but its jaws are drawn 7.45 m ahead of its middle on the impact frame: they close 3.4 m BEHIND the man. (The wild tyrannosaur bites from its jaws; the player's one is ruled from its middle.) 7. BIG TARGETS. Brute / Abomination: about 10 cm worse than a man. Triceratops, from the side: like a man (typically +0.23 m); from the front people stand inside its head. T-Rex beside the hips: +0.28 m typically (up to 0.67). In FRONT of a T-Rex, at the jaws end: 1.08 m typically (0.3 to 1.5): the head is overhead and the blow lands on air under it. 8. HIT EFFECTS ARE NOT AT THE WEAPON. Blood and sparks are drawn on the victim, at a spot picked by the hit roll (head, heart, arm, leg), and at a building's footprint face. That adds no gap, but it shows one up. ALSO READ IN THE CODE (not seen): on the first blow of a fight the rules wind up for 1/6 s but the step-in is spread over 0.25 to 0.4 s, so for clubs and axes it can start about 80 % done: most of a metre in one frame. THE BERSERKER: the mirror's file is already the new chop (identical to staged_A). It touches a man (6 cm in). Note the real drawn distance for him is 1.72 m, not the 1.52 m the method sheet uses: the 1.0 m cap was missed there. The same holds for every unit whose range is over 1.6 m. The real distances per unit are in CONTACT_TABLE (column "drawn"). NOT DETERMINED (needs the game running, or was out of reach) - how often fights sit in the 1.2 m slack band, and how often attackers stand turned off their target; - whether the feet hold or slide during the step-in (FootPlant), the blend between clips, hit-stop on screen; - the Clubman, Ceratopsian Lancer and Heavy Triceratops: low-poly rigs with no Attack clip to measure (their distances are in the table, their reach is not); - how far each building's wall stands inside its footprint; - wild animals attacking (only the three playable factions' units were measured). WORK FILES: local file: melee_contact (tools, local file: contact.json has every number, local file: fbx_hashes.txt the files measured). One stray EMPTY folder was created by mistake at local file: renders (a relative output path); nothing is in it and I did not delete it.
MELEE DRAWING - fighters that meet what they hit; swing variants; the Orc Berserker's two chops - report, 2026-11-09 (work in local file: PrimevalMirror only, nothing committed) Owner: "make sure that they actually look like they're making contact with each other ... they're at least 1 to 2 people length away from the target sometimes ... it's showing as if they're hitting them"; on the Berserker's two new chops: "a and b are great use those"; on the old attack: "goofy". PRESENTATION ONLY. No simulation file touched: damage, timing, reach, rules, saves, net, replays as before. RulesRevision stays 119. Save format, NetProtocol and replay format not bumped. Every step and turn below is drawn on top of where the simulation has the unit. WHAT A PLAYER NOW SEES (test build local file: Primeval.exe, build5) 1. Every melee fighter at work on a target is drawn turned to it, the second and later attackers round one man too (before only the Bowman turned; the others kept the way they last walked: measured in the game, the Brute's blows came a median 53 degrees off their man, 95% of them over 30 degrees). Eased: most of the turn in about a quarter second, never faster than 12 rad/s for a person, a beast at its own pace. Planted feet pivot and re-step through the existing FootPlant. 2. The step into the blow now goes to each unit's own measured striking distance (the melee contact audit's touch distance: the weapon 5 cm into the body on the impact frame), plus an allowance for a bigger body (Brute +0.02 m, triceratops flank +0.7, tyrannosaur +0.87). The lunge is still at most a metre (eased in over the wind-up, back over the follow-through, never faster than 8 m/s, so a first blow no longer jumps 0.8 m in one frame). A long weapon that needs more than the metre (Grunt 1.30 m, Brute 1.37, Chieftain 1.38, Abomination 1.43, Wolf Raider 1.34) closes the rest (at most 0.5 m) as its stance for the whole fight: taken up and given back at 1.6 m/s and drawn as a walk (the legs step it), not a slide. Mounts and beasts whose jaws reached past their man are drawn back as their stance instead (Raptor Rider 0.17 m; the Death Rider, Bone Raptor and Raptor Bone Rider no longer lunge a metre into him: 0.31, 0.43, 0.37 m). 3. Beast-bodied attackers step too (risen protoceratops / triceratops / parasaur, the tyrants), and so does anyone striking a building: toward the building's drawn wall (the arrows' surface profile of its model, at chest height; a 40-degree median so a yard flag or pole does not count), not its footprint's edge (a hut's thatch stands a metre and more inside it). A wall does not step aside, so there the stance may close up to 2 m more (walked). 4. Hit effects of a hand blow sit where the weapon meets the body: on the victim's front toward the striker's weapon hand, at that hand's height (before: on his middle line at a height picked by the hit roll). Building chips at the drawn wall nearest the striker, at its hand's height. Big beasts keep their existing flank point. 5. Swing variants: a model whose controller has Attack2 (and Attack3) clips of exactly Attack's length takes them in turn, swing by swing (a counter per unit, never random, never the sim's); one of another length, or without a state, is not used (a warning, and the unit keeps to its Attack). Units without variants are drawn exactly as before. 6. The Orc Berserker: his final model (local file: staged_final, READY.txt 18:49) is installed: Attack = chop A, Attack2 = chop B, axes hanging down in Walk and Run. In the game he alternates A, B, A, B (logged per blow: see below). Switches (films / A-B only): -meleeDrawing off runs the old drawing exactly; -logContact logs every melee blow's impact frame. THE NUMBERS A. Measured on the game's own models, in Unity (MeleeDrawingTests.LogTheGapsBeforeAndAfterMeasuredOnTheModels: each unit's imported prefab posed on its Attack's impact frame, every vertex baked, against a baked standing Grunt placed where the old drawing and the new put him; gap = how far the weapon's foremost point stops short of his front at that height and side, + air, - into him). Against a man, square on: within 15 cm 8 of 25 before -> 23 of 25 now. unit gap before -> now (m) unit gap before -> now Risen Protoceratops +1.25 -> -0.03 Peon +0.48 -> -0.05 Abomination +0.47 -> +0.04 Acolyte +0.12 -> -0.01 Chieftain +0.37 -> -0.01 Heavy Guard +0.09 -> +0.00 Shaman +0.36 -> +0.04 Orc Warchief -0.24 -> -0.05 Brute +0.33 -> -0.04 Raptor Bone Rider -0.45 -> -0.02 Wolf Raider +0.32 -> -0.02 Bone Raptor -0.61 -> -0.04 Grunt +0.31 -> +0.01 Death Rider -0.71 -> -0.02 Ghoul +0.31 -> -0.11 Raptor Rider -1.00 -> -0.03 Villager +0.28 -> -0.01 (Skeleton Warrior, Berserker, Spear Warrior, Risen Triceratops, Necromancer, Risen Parasaurolophus: touched before, touch now) STILL MISS: Orc Shaman +0.54 (his staff's low blow, 0.76 m up, passes 0.44 m to the SIDE of a man's thigh: an animation fix, aim the staff in; the audit counted it as a hit, so his table row is the audit's). Risen / Tame Tyrant -0.82 (its jaws still close 0.8 m past the man after the most it may be drawn back, a metre: a rule, fix plan 6c, strike from the jaws). The Unity measurement agrees with the audit's touch distance within 0.1 m for 21 units; where it does not, the table row is the Unity one (and says so): the Peon (his approved combat axe, 0.91 not 1.17), the Risen Parasaurolophus (its head passes OVER a man; its neck meets him at the rules' own distance, 3.51 not 4.23: the 0.7 m pull-back the audit asked for would have opened a gap) and the tyrant as drawn (x1.25, set back: 6.03 not 7.62). Not measured (no detailed Attack clip): Clubman, Ceratopsian Lancer, Heavy Triceratops: they keep the old step. B. In the game (build5, -logContact, the duel stage as filmed, side B holding; the same build with -meleeDrawing off as the before; local file: summary.ps1; local file: contact). Every melee blow's impact frame: the drawn distance against the unit's measured striking distance, and how far the striker is turned off its target. Median gap and share within 15 cm / median degrees off and share over 30 degrees: attacker blows old/new gap old -> new within 15 cm old -> new turned off old -> new (>30 deg old -> new) Grunt 30/30 +0.22 -> -0.05 13% -> 70% 25 -> 0 deg (47% -> 0%) Brute 22/22 +0.07 -> -0.05 36% -> 95% 53 -> 0 (95% -> 0%) Ghoul 15/14 +0.32 -> -0.05 0% -> 100% 33 -> 0 (53% -> 0%) Abomination 9/6 +0.32 -> -0.05 0% -> 100% 19 -> 0 (33% -> 0%) Death Rider 8/8 -0.76 -> -0.05 0% -> 88% 61 -> 0 Raptor Rider 9/9 -1.07 -> -0.05 0% -> 100% 13 -> 0 Risen Protoceratops 4/4 +0.90 -> -0.05 0% -> 75% 65 -> 0 Skeleton Warrior 7/6 -0.08 -> -0.05 86% -> 83% 59 -> 0 Grunt at a hut 13/13 +1.98 -> -0.05 0% -> 100% (the gap to the hut's drawn wall) Brute at a hut 9/9 +2.11 -> -0.05 0% -> 100% (The old 'gap' at a hut is to the drawn wall, which the old drawing never stepped toward: about 2 m of air.) The blows still outside 15 cm in the new runs (15 of about 150) are nearly all the FIRST blow of a fight (the sim winds up only a sixth of a second on it; the step and the stance are eased and are still on their way: up to 0.34 m short) and Grunts on a Risen Parasaurolophus (radius 0.9: 0.46-0.72 m short, the step at its 1.5 m limit). The Death Rider's first blow can still land 0.6 m into a Grunt (pass 2 of a mutual pair). Turned off the target: 0 degrees median on every unit. The Orc Berserker, logged blow by blow against a Heavy Guard: swings A,B,A,B,A,B,A,B,A,B,A (11 blows, every one -0.05 m: touching). Against a Chieftain 2 blows: A then B. COST 30 Grunts against 30 Spear Warriors, normal game camera, 1920x1080, 40 s, two runs each (perf.ps1; autotest report): avg fps cpu main ms units stage ms units stage alloc kB/frame untouched build 166.1 170.9 2.69 2.75 0.68 0.64 1.14 1.27 new, drawing off 171.1 171.1 2.76 2.76 0.64 0.65 1.12 1.14 new, drawing on 171.0 171.2 2.78 2.74 0.66 0.65 1.13 1.12 No measurable cost and no new garbage (the units stage allocates the same 1.1 kB a frame in all six). By reading: the new per-unit arrays are grown once; no per-frame allocation (the swing-variant list is built once per model). TESTS New: local file: MeleeDrawingTests.cs, 14 tests, 14 pass (local file: t9, final_tests): the table equals the audit's CSV (bar the three Unity rows); every melee unit of the three factions has a row or the old mark, and every detailed model with an Attack has a row (fallback only Clubman, Ceratopsian Lancer, Heavy Triceratops); no step puts a body inside its victim (vs bodies of radius 0.35-1.4, and at a wall; lunge <= 1 m, stance <= 0.5 m); the gap tables above (by the table and measured on the models; asserts 'never worse', 'misses only at the limit', 'more touch'); the turn to a target is smooth (no faster than 12 rad/s, under 15 degrees on the first frame, never overshoots, frozen when paused, short way round); the step eases no faster than 8 m/s; a building is struck at the nearest point of its footprint and its wall is reached; in the renderer: three Grunts round one Clubman, each turned 60 degrees off, all face him within 3 degrees (with -meleeDrawing off they stay turned away: the reproduce of the fault); on the impact frame each unit stands at its measured distance (Skeleton Warrior at its touch, Grunt with its stance, Raptor Rider drawn back, the Risen Protoceratops now steps, Death Rider no longer in his man); the step eases in over the wind-up and out after, the stance is given back at 1.6 m/s when the fight ends; swing variants: equal length taken in turn, another length / a gap / no state not used; every unit json that declares Attack2/3 has Attack's frames, length and impact and a controller state of Attack's length; the Berserker in the renderer swings A,B,A,B while a Grunt always plays Attack. Reproduce first: on the code as found there is no table, no turn and no variant; the old drawing, kept behind -meleeDrawing off, is what the 'before' halves of the measurements and the facing test run. Related groups (local file: related_a, 412 cases: MeleeDrawing, CombatPresentation, PersonHit, HitWeight, SquadSpacing, DetailedAttackRecovery, DetailedInjuredAttackTiming, DetailedBowReleaseAnchor/Contract, DetailedControllerMotionPreservation, DetailedSpellCastDispatch, DetailedToolPreservation, DetailedUnitSize, TeamCloth, StuckWindupPose, FootPlantPeople, MissileImpact, SiegeImpact, CurrentBuildingArrowProfile, ReplayPresentationReset, ClassicPresentationLifetime, PresentationHistory, UnitPresentationHistory, MeleeEngageSprint, PeonCombat*, MovementFeel, BloodFx, CinematicPresentation, CombatSoakLifetime, MountedRagdollEligibility, ShieldBlock, WeaponParry, Glide): 335 passed, 77 FAILED. None caused by this work: I swapped my code out (local file: swap.ps1) and ran the failing groups on the code as found (local file: base_related): the same 53 fail there with the same messages; the other 24 are on the known list or check other units' reviewed FBX bytes: - DetailedUnitSize 44: 'planted balls cover the json's pace', 'planted feet run back steadily', 'hips sway/roll', ghoul height, on 17 units (Grunt, Brute, Peon, Villager, Spear Warrior, Heavy Guard, Chieftain, ... and the Orc Berserker): fail identically on the code as found. NOT on the known list. Probably the new gaits installed today by other workers. The Berserker's own (Run pace 3.30 against the json's 3.63) was run with his new FBX in both runs, so I cannot say whether the new FBX or the shared cause did it. - DetailedSpellCastDispatch 2 (Orc Shaman cast state): same on the code as found. Not on the known list. - PeonCombatGrip* / PeonCombatIdle* 22: 'reviewed peon source/import changed' (peon.fbx bytes): someone changed the Peon's FBX; I did not touch it. Not on the known list. - Known: CurrentBuildingArrowProfile (lodge, hut), MountedRagdollEligibility.CurrentThirtySevenEntryPolicy, StuckWindupPose x3 (head 1.29 / spine 1.87 degrees, same as the missile report) and two StuckWindupPose 'RenderTexture.Create failed'. Last run on the final code (local file: final_tests: MeleeDrawing, CombatPresentation, PersonHit, SquadSpacing, TeamCloth, SiegeImpact, MissileImpact, ReplayPresentationReset, CurrentBuildingArrowProfile): 90 cases, 86 passed, 4 failed, all known (CurrentBuildingArrowProfile, StuckWindupPose x3). Sim groups (StateHash, SaveGame, Replay) NOT run: no sim file changed. Peak editor memory 5.3 GB. Five player builds, all succeeded (local file: build1-5). FILMS local file: ingame (copies in local file: 15_melee_contact and local file: Primeval_review). Side by side BEFORE (the untouched test player, local file: before_build) | AFTER (build4), same stage arguments, then the moment again at quarter speed (zoomed x1.5 for a, b, c). 1920x540, 9-19 MB. melee_a_grunts_vs_spear_warriors_close.mp4 (a) READS: before a clear body-width of air between the lines; after the bodies are at grips and the cleavers land on the spearmen. Busy at full speed. melee_b_three_grunts_round_one_chieftain.mp4 (b) READS: the three Grunts are turned to him and against him, the VITAL burst at his chest by the cleaver. Short (he dies at ~4 s). melee_c_grunts_strike_a_hut.mp4 (c) Before the Grunts chop at air 1.3 m from the hut; after they stand at its thatch. From that low side angle the depth is hard to judge: a top view (local file: sheet.jpg) shows them at the wall, chips at the wall. melee_d_berserker_alternates_two_chops.mp4 (d) Before chop A only; after A and B in turn (B the overhead chop). He was already touching before, so the distance barely changes. A and B are hard to tell apart at that size: the animlab clip shows them plainly. melee_e_normal_game_camera.mp4 (e) From 30 m: the lines meet instead of standing a body apart. Modest at that size. Animation lab (build5, -showcaseTarget animlab, three-quarter then side, two loops, clip name on screen): animlab_orc_berserker.mp4 (Idle, Walk, Run with the axes hanging down, Attack = chop A, Attack2 = chop B; 0.8 s each, blow on frame 9), animlab_spear_warrior.mp4, animlab_heavy_guard.mp4, animlab_villager.mp4 (Idle, Walk, Run, Attack). 2-4 MB each. I looked at stills and contact sheets of every clip (local file: check_*.jpg), never at motion. NOT VERIFIED / WHERE IT STILL FALLS SHORT - Nobody has played it. I judged stills, not motion. Whether the stance (closing up to 0.5 m, 2 m at a wall, walked at 1.6 m/s) reads as a step or as a drift, and whether 8 m/s lunges read as lunges, is the owner's call. - Feet: the lunge is drawn apart from the gait (as before); the stance is walked. FootPlant was not measured for either; planted feet were not checked for sliding during the lunge (they were not before either: an Attack hands the feet to its clip). - The 1.2 m slack (a target drifting during a committed swing: fix plan step 5) is a rule; not touched. A drifting fight can still show up to about a metre of air beyond what the 1.5 m step covers. - Buildings: the wall point is the arrows' surface profile; for the hut and the lodge that table is out of date (a known failing test), so I use the stale table's shape (closer than a box of the model, which takes in the hut's flag). Only the Tribe Hut was filmed / measured. Walls, gates, towers and other factions' buildings not looked at. - Riders and beasts against big targets, units in front of a T-Rex, the Clubman / Lancer / Heavy Triceratops (old step), low-poly bodies past the detailed budget (they get the step and the turn; their reach was never measured): not looked at. - Hit effects at the weapon: placed by the code (hand height, the front toward the hand); looked at in a handful of frames only (the burst at the Chieftain's chest in b). Blood at night, Low settings: not looked at. - Swing variants run on a per-unit swing counter that survives the model being pooled; after a replay seek the order may start at A or B (nothing in the sim depends on it). -meleeDrawing off does NOT switch the variants off. - Variant timing at run time is checked by clip length only (the impact time is checked by the test, from the json). - DetailedUnitBuilder was not run (forbidden): the library asset was not rebuilt; the variants are read from the controller at run time. If someone runs the builder it rebuilds the Berserker's controller from his FBX (DetailedControllerMotionPreservation) and should keep Attack2 as a state, as for every clip of the FBX; not tried. - Sim-side groups (StateHash, SaveGame, Replay, matchups) were not run: no simulation file was touched. SLIPS OF MINE, FOR THE RECORD - Two batches of in-game contact runs (30 s and 60 s, ingame perf stage, both sides attack-moving) logged almost nothing (my -logContact misses blows outside the filmed duel set-up; not understood); moved to local file: contact_30s and contact_60s and redone on the filmed duel stage (contact2.ps1). - I edited code while build1 was compiling; build1 lacked the wall code; rebuilt. - Player builds flipped two lines of local file: GraphicsSettings.asset twice; reverted with git checkout of that file. git status at the end = the start plus my four new files and their .meta. FILES changes.diff (5 changed code files against local file: orig, the Berserker's 3 text art files against backup, 4 new files in full), local file: mine (copies), work (runner, gen_contact_table, capture, film_all, encode, contact2, summary, perf, animlab, swap, mkdiff scripts; before_build = the untouched test player, 3.8 GB), logs, backup (the Berserker's FBX, .fbx.meta, controller (+meta), json (+meta) and prefab as found). Changed: local file: UnitRenderer.cs (old step moved out; the facing call; the lunge apart from the gait), CombatEffects.cs (hit point at the weapon, building chips at the wall), local file: DetailedUnits.cs (swing variants, the strike hand, two accessors), ProjectileRenderer.cs (TryGetWallPoint), local file: GameBootstrap.cs (-meleeDrawing, -logContact). New: local file: MeleeContact.cs, local file: MeleeContactTable.cs (generated by local file: gen_contact_table.ps1 from the CSV), local file: UnitRenderer.MeleeContact.cs, local file: MeleeDrawingTests.cs. Art (the one permitted change): local file: orc_berserker.fbx (the staged final, sha1 448B2A53...), .fbx.meta (an Attack2 clip, frames 0-24, Attack's settings), orc_berserker.controller (an Attack2 state), orc_berserker.json (Attack2: 25 frames, 0.8 s, blow 0.3 s). No other unit's art touched. CatapultInstallTemp.cs left in place. local file: Primeval never touched. The test player local file: CatapultTest holds the final code (build5). DISK: this job holds 31.5 GB under local file: melee_drawing (frames of every take, before_build 3.8 GB, removed).
A Bowman shot: old arrow speed vs new (about 80% faster).
New: a close shot flies flat, a far shot is lobbed.
Before: close and far shots were both lobbed.
A volley into advancing Grunts: arrows stick and jolt the part they hit.
A Grunt killed by arrows: thrown on his back about 1.5 m, the arrows left in him.
Arrows at the normal game camera.
A thrown axe tumbling and striking. The tumble is barely visible (a known weak point).
A Grunt killed by a spear: carried back about 1.7 m and pinned, the shaft quivering.
The same on a Villager.
The spear now leaves the hand on the throw and flies flatter.
MISSILES: THE SHOT, THE HIT, THE ARROW DEATH, THE SPEAR PIN, AND FASTER ARROWS - report, 2026-11-09 (work in local file: PrimevalMirror only, nothing committed) Owner: "make archer arrow shots look smoother and more realistic ... when a unit is hit by an arrow or spear ... add impact from it ... have the arrows stay in the body and ... the body hit by the arrow be hit back and into the ground and if it's a spear ... impale them to the ground with the momentum of the spear". Added during the job: "the arrows might need to go through the air a little bit faster ... whish"; "if they're shooting close, it's gonna be a lot more direct of a line; if they're shooting farther, it's gonna have more of an arc"; "the ones that are on a line ... a lot more straight ... conveying of power and speed". ONE SIMULATION CHANGE (the lead's instruction, the owner's direct ask): bow arrows fly faster. RulesRevision 118 -> 119. Everything else is presentation only: it reads nothing new from the simulation and changes no damage, timing or rule. Save format NOT bumped, NetProtocol NOT bumped, replay format NOT bumped (no new state, command or event; replays carry the rules revision, so a 118 replay is refused by a 119 build as for any rules change). WHAT THE OWNER WILL SEE (test build local file: Primeval.exe, build3) The shot - Arrows are about 80% faster (Bowman, Skeleton Archer 28 -> 50 m/s; Crossbowman 30 -> 54; tower and town hall arrows 30 -> 54). A 12 m shot lands in 0.20 s instead of 0.40. - A close shot is a straight line; only a long shot is lobbed. Height of the flight above the straight bow-to-target line (m): range 3 m 6 m 8 m 10 m 12 m 18 m (tower) arrow before 0.80 0.80 0.96 1.20 1.44 2.16 arrow now 0.03 0.09 0.32 0.76 1.42 3.20 spear / axe range 3 m 6 m 9 m 11 m before 1.20 1.20 1.20 1.43 now 0.06 0.29 0.85 1.41 (the old "distance-scaled arc" was a tenth of the range with a floor of 0.8 m for arrows and 1.2 m for spears, so every short shot looped). A tower shooting down at a close target now shoots a straight descending line. - The Bowman aims with the shot: chest, bow arm and nocked arrow tilt up by the launch angle (2 degrees at 3 m, 3 at 6 m, 17 at 10 m, 25 at 12 m), eased in during the draw and out after the release. The clip is not touched; the tilt is laid over it, so it follows whatever attack clip the animation workers install. - The arrow leaves with a short bright streak (first 0.1 s), then a fainter, shorter trail than before; it flexes a few degrees off the string and straightens within a third of a second. The shaft always points along its flight. - Spears, axes and sling stones now leave the throwing hand. Distance from where the missile first appears to the hand on that frame (measured in the game, -logMissiles, 17 to 26 shots each): Spear Thrower 0.68 m (max 0.83) -> 0.00 Slinger 0.58 (0.76) -> 0.00 Axe Thrower 0.45 (0.67) -> 0.00 Bowman 0.28 -> 0.28 and Skeleton Archer 0.15 -> 0.15: UNCHANGED. Both already started the flight at their nocked arrow model (the figure is that arrow's middle to the bow hand), so there was nothing to fix there. - A thrown axe turns end over end about its own middle at about 1.5 turns a second, a whole number of turns, and arrives blade first, head up (it used to spin three times whatever the range, about its handle end). A spear rolls slowly about its shaft. - A flat shot that misses now dips into the ground over its last two metres instead of blinking down to it. - A missile stopped on a Heavy Guard's shield glances off, tumbles, lies in the grass and fades (it used to vanish). The hit on a living body - The missile is carried from where its flight was last drawn into the body over one or two frames (no blink), and sticks in the part the simulation says it hit (chest, head, upper arm, thigh), each at its own spot and angle, a third of the shaft buried. It now rides that bone, so it moves with the body as it walks, swings and is struck. - That part is jolted along the shot on top of the existing flinch: chest and waist, a shoulder with its arm, the head, or the knee giving for a leg hit; struck off-centre the chest twists. Spear and axe about 1.6 times an arrow; a sling stone adds nothing to its ordinary flinch; a brute hardly feels an arrow. Measured chest throw: arrow 8.5 cm, spear 13.9 cm, axe 14.4 cm on a Spear Warrior; arrow on a Brute 3.6 cm. Thirty arrows in one second: capped, largest one-frame turn of the chest 6.4 degrees. - Big beasts, war beasts, siege engines, riders, animals: the missile sticks as before, their own flinch, no shove. Death by arrow - The body goes over backwards along the shot and lands on its back (measured: a Grunt's chest comes to rest 1.5 m back), turned a little by which side was struck, with a small thud and dust. Every arrow that was in him is still in him where it struck, standing out of the body. Before: he crumpled and the arrows stayed hanging where he had stood, then lay on the ground beside him. - They now stay for as long as the body lies (75 s), sink with it, and go with a body reaped or crumbled. Before they faded 25 s after they struck. Death by spear (or a ballista bolt) - The body is carried back with the spear through its chest (Grunt 1.7 m, a Villager further), the spear is driven into the ground at 52 degrees where the chest comes down, the chest is held there with 6 cm of give while the limbs settle, the shaft quivers for under a second and stands for the body's whole time. Exactly one staked spear per body. - A thrown axe that kills drops him about where he stands, the axe in him. Six missiles to a body and 600 in all, as before. WHAT ALREADY EXISTED / WHAT IS NEW Existed and reused: the stuck-missile table in UnitRenderer (arrows, bolts, spears and axes were all already in it; 6 a body, 600 in all), the building arrow system, PersonHit's springs and caps, the humanoid ragdoll and its "kept body" rests, BoneUndo, the blood and dust bursts on a hit (untouched; they already follow the Blood setting), the Bowman's nocked-arrow release point, the ground-stick of misses, EffectPools, AudioDirector.PlayFootfall. New: local file: MissileFeel.cs (all the numbers, pure), local file: UnitRenderer.Missiles.cs (missiles on bones, the last stretch in, kill detection, the stake), the arc curve, the hand release, the axe tumble, the launch streak and soft trails, the deflected shot, the bow tilt, PersonHit.Jolt, the ragdoll's backward topple, spin and stake. No new settings row. "Hit reactions" Off = no jolt, no knock-back, no stake (bodies fall exactly as before). Blood Off = no impaling sound. -missileFx off runs the old drawing path (films, timing). No new asset files; nothing downloaded. NOTHING WAS STOPPED FOR WANT OF A SIM CHANGE The killing missile is known without one: the simulation already sends UnitDied and then, in the same tick, ProjectileStuck for the same body with the flight direction and hit location. Limits that DO come from what the sim sends, with the smallest change that would lift each: - A sling stone sends no ProjectileStuck, so a stone has no part-jolt and a stone kill is an ordinary fall. One event (or a flag on UnitDamaged saying "missile") would give it. - The hit location is rolled at the moment of the hit, so the drawn flight cannot aim at the thigh or the head beforehand; the last-stretch carry hides it. Rolling the location at launch would be a real rules change; not worth it. FASTER ARROWS (rules 119) Where: UnitSpec.ProjectileSpeed in Catalog.Tribe / Undead / Human, and the new Catalog.TowerArrowSpeed (the two tower / town hall launches in TaskSystem had a literal 30). Flight time is distance / speed, one step a tick, straight at the aim point. The war triceratops' crew use the Bowman's bow; garrisoned archers their own. Spear Thrower 16, Axe Thrower 15, Slinger 19, Ballista 34, Siege Ballista 36, stone throwers 14, Meat Wagon 20, Dread Lich 24: unchanged. Flight time, still target (ArrowSpeedTests): 5 m 9 m 12 m (full range) before 28 m/s 4 ticks 0.13 s 9 ticks 0.30 s 12 ticks 0.40 s now 50 m/s 2 ticks 0.07 s 5 ticks 0.17 s 6 ticks 0.20 s Do arrows home? No (rules 103): aimed once with a lead and a spread, they can miss a target that turns. Balance, equal cost 1200 a side, 60 s, the melee squad attack-moves at the standing archers from 26 m, 8 seeds (ArrowSpeedTests.LogArrowClashes, local file: probe_sweep; share of starting health left; archers ahead in N of 8): 28 m/s (before) 45 m/s (+61%) 50 m/s (+79%, CHOSEN) Bowman vs Grunt 0.00 / 0.60 0/8 0.00 / 0.56 0/8 0.00 / 0.57 0/8 Bowman vs Spear Warrior 0.23 / 0.01 6/8 0.13 / 0.10 3/8 0.27 / 0.01 6/8 Bowman vs Clubman 0.00 / 0.56 0/8 0.00 / 0.54 0/8 0.00 / 0.53 0/8 Skeleton Archer vs Spear W. 0.71 / 0.00 8/8 0.68 / 0.00 8/8 0.69 / 0.00 8/8 Skeleton Archer vs Grunt 0.00 / 0.46 0/8 0.00 / 0.43 0/8 0.00 / 0.39 0/8 arrows that hit nobody 0 to 7.8% 0 to 7.2% 0 to 6.5% Reading: no winner changes at 50 m/s. At 45 m/s the one knife-edge matchup (Bowman vs Spear Warrior) dropped from 6/8 to 3/8 and came back to 6/8 at 50, so that is tick-timing noise in a close fight, not a trend; I still took the lead's rule literally and did not ship 45. Melee squads keep 3 to 7 points less health against archers at 50. 4-seed runs before (local file: probe_before) and at 45 (local file: probe_45) agree. +40% (39 m/s) was built first and passed its tests but was not probed after the owner asked for the fast end. At 50 m/s an arrow is on screen about 12 frames at 60 fps on a full-range shot and about 4 at 5 m. That is the fastest I would go; at 5 m it is close to a flash. Old saves: a unit's missile speed is stored on the unit, so archers already alive in a save made before this keep the old pace; new ones are fast. NOT TESTED beyond the SaveGame group passing. COST (final build, 1920x1080, normal game camera, 35 s, two runs each; -missileFx off = the old drawing path, same sim) avg fps cpu main ms my own timers ms/frame (flight / stuck) projectiles alloc kB/frame 30 Bowmen v 30 Grunts off 138.0 142.9 4.86 4.50 0.016 / 0.057 0.014 / 0.053 0.00 0.01 on 147.9 158.2 4.36 3.82 0.015 / 0.102 0.013 / 0.087 0.01 0.01 20 Spear Thr. v 20 Grunts off 152.6 140.8 3.71 4.04 0.012 / 0.037 0.012 / 0.033 0.00 0.00 on 139.7 158.9 4.55 3.56 0.011 / 0.054 0.010 / 0.046 0.01 0.00 Reading: not measurable in fps (the two identical runs differ by more than on differs from off). By my own timers the missiles on bodies cost +0.04 ms a frame in the archer fight (about 0.6% of a 6.9 ms frame), the flight nothing. The 0.01 kB reading is the counter's smallest step and appears in an "off" run too; by reading the code the only new allocations are one-off (draw batches and fade materials the first time each is used), none per frame. NOT MEASURED: 60 archers, a weak GPU, a full battle. TESTS New: local file: MissileImpactTests.cs and ArrowSpeedTests.cs, 24 cases, all pass (local file: t4, final_tests), plus the explicit probe. They cover: the launch point follows the hand; the shaft follows its velocity (within 0.6 degrees), climbs, noses over and dives; close shots flat and long ones lobbed, smoothly; the axe arrives edge first, head up, 1.6 turns a second; missiles ride the struck part, survive death, are capped at 6, do not float or go under the ground, stay as long as the body and go with it; a spear kill leaves exactly one staked spear, through the chest, head in the ground, still after a second, gone with the body; a low-poly body gets the stake where it lies; big beasts are never jolted, thrown or staked; a blocked shot glances off and nothing sticks anywhere; everything off with Hit reactions Off or -missileFx off; the speeds and flight times; the same fight twice gives the same state. Reproduce first: on the code as found the speed test failed (rules 118) as intended. I did NOT run the presentation tests against the old code (they are written against the new API). Re-pinned, 1: FormationMarchTests.TightGolden 9163312606696312740 -> 14572433427997916768. It passed at 118; the hash reads every archer's missile speed. Nothing else re-pinned. Baseline on the code as found (local file: base_related): 301 cases, 293 passed, 7 failed, 1 inconclusive. Final code: final_tests (264 cases: the new files, ReplayPresentationReset, DetailedBowReleaseAnchor / Contract, FormationMarch, LiveMissile, MissileVanishProbe, PersonHit, HitWeight, BloodFx, CombatPresentation, ShieldBlock, WeaponParry, SiegeImpact, DeathSeenAgain, MountedRagdollEligibility, ClassicPresentationLifetime, PresentationHistory, VisualPolish, SaveGame, Replay, StateHashChunk, Sandbox): 259 passed, 5 failed, all on the known list: MountedRagdollEligibility.CurrentThirtySevenEntryPolicy...; StateHashChunk.SavedCombatFixtureHashesRemainBitIdentical (was 3024812007831355229); StuckWindupPose.PersonHitLeavesNoTurnOnAnyBoneAfterBlows x3. related_b, sim groups (491 cases, one build earlier: before the trail-dispose fix and the re-pin): 483 passed, 8 failed. Known list: SavedCombatFixture; EdgeScenery and GroundLayer (pinned 10803087842307530174; was 11596832484050369708 at 118, now 2609061906737532392: moved again by the rules change, still not re-pinned because the first drift is not mine to bless); PteranodonFlightUnchanged (now 15426423904698948146). Fixed since: FormationMarch (re-pinned); ReplayPresentationReset x2 (MY fault: the two new trail material sets were destroyed with Destroy in edit mode; now DestroyImmediate there; passes in final_tests). NEW AND NOT FIXED: CannotRecoverProbeTests.RunDuels (an explicit slow AI-duel probe that my name filter pulled in): "Expected 0 But was 1" over six AI matches. The matches play out differently at rules 119, and one tripped the forfeit-flag check. I did NOT run it at 118 myself; the War Charge report shows the CannotRecover group clean at 118. Treat it as caused by the rules change until someone shows otherwise. It needs the forfeit heuristic's owner. related_a, presentation groups (325 cases, same earlier build): 314 passed, 10 failed, 1 inconclusive (the War Charge save writer). Three were the same dispose fault (fixed, pass in final_tests). The rest fail on the code as found too: CurrentBuildingArrowProfileTests (lodge and hut bounds): YES, IT FAILS BEFORE MY CHANGES, same message. MountedRagdollEligibility (known). StuckWindupPose.BlowsTakenLeaveNoShrugOnTheShoulders and LogWhereEachEndingLeavesTheSpearWarrior ("RenderTexture.Create failed"): not on the known list, fail identically in my baseline. StuckWindupPose.PersonHit... x3 (known): baseline 2.74 degrees (head), now 1.29 (head; Off, Low) and 1.87 (spine; High). The number moved during my job and I cannot say whether my code or the unit art did it (spear_warrior.fbx, heavy_guard, orc_berserker and villager were rewritten by other workers at 17:16). Still failing. The two big related runs were not repeated in full on the very last code; final_tests was. Peak editor memory 5.2 GB. Three player builds, all succeeded (local file: build1-3). FILMS local file: ingame (copies in local file: 11_arrows_spears and local file: Primeval_review). Each: left | right at full speed, then the moment again at quarter speed. 1920x540, 4 to 18 MB. BEFORE is the untouched test player copied before my first build (local file: before_build). arrows_a_bowman_shot_old_vs_new_speed.mp4 (a) one Bowman, 8 m, side on. READS: old lobbed slow arrow against a fast straight one with a streak; the Grunt is knocked back on the hit. arrows_a2_close_shot_flat_vs_far_shot_lobbed.mp4 new build, 6 m against 12 m. READS: level bow and a line; tilted bow and an arc. The 6 m arrow is only two ticks long: nearly a flash. arrows_a3_before_close_and_far_both_lobbed.mp4 the same two shots in the old build, for comparison. arrows_b_volley_into_advancing_grunts.mp4 (b) 6 Bowmen, 6 Grunts. READS in the zoomed quarter-speed half (jolts, arrows riding the bodies); at full speed it is busy and small. arrows_c_grunt_killed_by_arrows.mp4 (c) READS CLEARLY: over backwards onto his back, arrows standing in him; before, face down with the arrows left on the grass. spears_d_grunt_killed_by_spear_carried_back_and_pinned.mp4 (d) READS: carried back, on his back, spears standing. Five or six spears are in him by then, so the one that stakes him is not singled out. spears_d2_villager_killed_by_spear_carried_back_and_pinned.mp4 the clearest pin: a light body flown back on the spear. axes_e_thrown_axe_tumbling_and_striking.mp4 (e) THE WEAKEST. The target stands at the frame's left edge and the axe is a few pixels; the blow and the jolt read, the tumble barely does. arrows_f_normal_game_camera.mp4 (f) 8 v 8 from the game camera. Faster, straighter arrows and bodies going over are visible; the stuck arrows are specks at that height. spears_g_spear_throw_leaves_the_hand_and_flies_flatter.mp4 one Spear Thrower at 8 m. I looked at stills and contact sheets from every clip (local file: check_*.jpg, and crop.jpg / seq.jpg in the take folders), never at motion. The bow clips (a, a2, b, c, f) were filmed from build2; build3 differs from it only in the spear / axe arc and a dispose fix, so they were not re-filmed. SOUND Existing clips only: body_thud (the body landing), gore_impale (the stake, Blood on). Everything else unchanged. NEVER HEARD BY ANYONE. Nothing was added for the string or the faster arrow. For an audio worker: arrow_whish (a fast pass-by, 0.2 s), bow_string_snap (dry and sharp), spear_stake_earth (a heavy shaft driven into turf with a short wooden quiver), arrow_glance_shield (a tick and a skitter), body_fall_back (a man going down flat on his back, heavier than body_thud), axe_bite_flesh. NOT VERIFIED / FALLS SHORT - Nobody has played it. I judged stills, not motion; whether it "feels weighty" is the owner's call. - "A snap of the string": NOT DONE. The release has its existing glint and puff; only the streak is new. - The deflected shot: counted (3 in the Heavy Guard take local file: k_after) and tested, but I could not find one in the frames I looked at. How it looks is unverified. - The axe's edge direction is a guess from the model's bounding box (its mesh cannot be read at run time); the head end I corrected from film. Blade-first is tested in maths, and only roughly confirmed by eye. - The Clubman has no detailed model, and anyone past the detailed budget (260) is low-poly: no knock-back, the spear is staked where the low-poly body lies (tested, never looked at). With detailed units switched off entirely: the old look. - Riders: missiles ride the root as before, no throw. Animals fall the way their last blow drove them (existing). - Skeleton Archer, Slinger, Crossbowman, ballista bolt, tower arrows: measured or tested, not filmed or looked at. The bow tilt is the Bowman's only (the only model with aim data); the Skeleton Archer shoots level whatever the range. - Throwers do not tilt for a long throw (clip only). The held spear or axe is hidden by the clip, not by me. - More than 16 bodies falling at once: the rest play their death clip and get an ordinary stuck spear, no stake. - A body staked off screen and seen again keeps its spear but was not looked at. Replays, multiplayer, fast-forward: not run with it. - Shots at buildings use the new arc too, so where arrows stick on a wall shifts a little; not looked at. - CannotRecoverProbeTests.RunDuels, EdgeScenery / GroundLayer / Pteranodon goldens and the StuckWindupPose numbers: as listed above. SLIPS OF MINE, FOR THE RECORD - My copy of the Unity runner still pointed its logs at local file: logs for the first run. It wrote base_related.log / .xml there (new names, nothing overwritten); I moved both to my logs folder and fixed the path. - Several multi-line search-and-replace commands of mine silently did nothing; each was caught on the next check and redone by hand. One doubled three lines in my own film script, which I rewrote. - The dispose fault above reached two related runs before I fixed it. - A player build flipped two lines of local file: GraphicsSettings.asset; reverted with git checkout of that file. git status at the end = the start plus my four new files and their .meta. FILES changes.diff (15 changed files against local file: orig, 4 new files in full), local file: mine, work (runner, capture, film_all, encode, measure, perf, swap, mkdiff scripts; before_build = the untouched test player, 3.8 GB), logs. Changed, presentation: ProjectileRenderer.cs, UnitRenderer.cs, GoreRenderer.cs, local file: DetailedUnits.cs, local file: DetailedRagdoll.cs, local file: PersonHit.cs. Bootstrap: GameBootstrap.cs (-missileFx, -logMissiles, -duelArrowSpeed, the report line), ShowcaseDirector.cs (-duelArrowSpeed, films only). Changed, simulation: Catalog.cs, Catalog.Tribe.cs, Catalog.Undead.cs, Catalog.Human.cs, SimulationHost.cs (RulesRevision), local file: TaskSystem.cs (the tower arrow speed constant). Tests: FormationMarchTests.cs (the re-pin). New: local file: MissileFeel.cs, local file: UnitRenderer.Missiles.cs, local file: MissileImpactTests.cs, local file: ArrowSpeedTests.cs. No art, .fbx or art .meta touched. CatapultInstallTemp.cs left in place. local file: Primeval never touched. Raw frames of every take are in local file: <scene>_before_hd | _after; superseded takes in removed.
A stone on open grass: dust ring, hanging plume, clods, the stone breaking up and a crater that lasts about 5 minutes.
A stone among units: grit through the group, the people hurt thrown away from the impact.
A stone on a wooden building: planks and dust down the wall, a dent.
A stone on a bone building: bone debris.
The Meat Wagon payload: a wet burst (the weakest of the set: blood dominates).
The same effects at the normal game camera.
SIEGE IMPACTS - report, 2026-11-09 (work in local file: PrimevalMirror only, nothing committed) Owner: "when a catapult shot hits the ground and or units or buildings can you improve the effect it gives off. So it's more cool and substantial ... particles dust kick up on ground maybe a ground impact wound". PRESENTATION ONLY. No simulation file was touched. RulesRevision stays 118; save format, NetProtocol and replay format are not bumped. No new sim event was needed: ProjectileRenderer already knows, where a drawn missile's flight ends, what it is, whether it landed (ProjectileLanded / MissileFate.Landed), hit a unit, or hit a building and at which spot on its surface (BuildingAim); the blast centre for the victims is read from the spent missile the sim keeps for one tick. WHAT THE OWNER WILL SEE (test build local file: Primeval.exe) A stone on open ground (Orc Catapult, Tribe Stone Thrower: the same code path) Before: a faint puff you had to look for. Now: a low ring of dust races out about three metres, a taller plume goes up out of the middle, hangs about three seconds and drifts down the wind; twenty-odd clods and small stones fly up on arcs, land, some bounce, and lie for about ten seconds; the stone itself breaks into 3 to 5 pieces that are carried on, bounce, roll and stop, and lie about half a minute before sinking into the ground; a crater is left (a dark dug bowl about 1.6 m across with a lighter lip of thrown earth and earth scattered round it, 4 m over all) for 320 s, fading over its last quarter; grass tufts by the bowl lie down; the camera gets a short tremor. By the ground (read from the map, cheap): more and paler dust and fewer clods on sand and dunes; white powder and white lumps on snow; dark ash on volcanic land; less dust, mud drops and more lumps on wet ground (after rain) and tar; in a lake a column of water and spray, no crater, no pieces. On wet ground a crater holds water after a minute. A stone among units The same at the impact point, plus grit and stones through the group at body height. The stone breaks where it met the body. The people hurt are now shown thrown AWAY FROM THE IMPACT POINT (they were rocked along the line from the engine) at the hardest reaction a body has within two metres of it, easing off to the edge. Who is hurt, and by how much, is exactly as before. One crater per stone (before: a soil mark under every victim). A stone on a building Debris by what it is built of bursts out of the struck face and falls to the foot of the wall, where it lies about 20 s: planks and timber for the Horde and for the Tribe's palisades and towers, straw and thatch (with some timber) for the Tribe's huts and halls, stone for the Kingdom, bone shards (with some stone) for the Scourge. A puff of dust at the spot, coloured by the material, then a slower fall of dust sifting down the wall for a second and a low ring where the debris lands. The stone breaks on the wall and its pieces drop and roll off. The building rocks harder (strength 1.9 against the old ceiling of 1.6) from the moment the drawn stone arrives. A small dark dent stays at the struck spot for 45 s and rides the building as it rocks. The old random-spot chip puff for thrown siege is gone (the effect is now where the stone actually lands). The shot that brings the building down: burst and pieces only, nothing left clinging, and the existing collapse takes over (seen in both building clips at the second shot). The Meat Wagon's payload Keeps its own character: ropes of old blood and black-green ichor flung out, lumps of flesh that land dead and lie, bone shafts that bounce, a foul brown mist, a low dull ring of dust, a dark red and green splatter on the ground (not a crater). With "Rotting Payload" researched the mist is bigger, greener and hangs nearly twice as long. With Blood and gore Off: bone, clods and dust only. A ballista bolt (same path, lighter; Human Kingdom, benched): a kick of dirt and a small gouge where it sticks in the ground; splinters and a puff on a wall. NOT FILMED. Camera: a tremor of 0.12 for a stone on the ground (a tyrannosaur's step is 0.022), 0.10 on a building, 0.085 for the payload, through the same CombatEffects.RequestKick the big footsteps use: it falls off with distance from where the camera looks and toward the screen edge, is capped by the camera at 0.18 and obeys Settings "Camera shake on big impacts". A volley landing together shakes once (a heavier blow in the same instant may re-shake). SETTING No new settings row. It follows Settings > Graphics "Ground marks": Off = the old plain puffs exactly and nothing of this is spawned; Low = half the particles and pieces, 96 pieces at most, 16 craters, no shadows on the pieces; High (the default) = everything, 224 pieces, 40 craters. Debug: -siegeFx off|low|high. Blood and gore Off takes the red out of the payload. Camera shake Off takes the tremor. WHAT WAS REUSED, WHAT IS NEW Reused: ProjectileRenderer's landing code and its building aim points; EffectPools for the fine grit (GroundDebris, WoodChips, StoneDust, BoneChips, DarkIchor, FleshChunks, WaterLap); GroundDecals (its shader, atlas and Crater and Scorch tiles); AmbientLife's grass "parters" (bodies already press the grass); BuildingRenderer's hit rock and model root; CombatEffects.RequestKick and the camera's AddShake; AudioDirector's PlayFootfall (pitch and full-level range); the Rock and Log materials; the War Charge worker's work is untouched (nothing of it undone or duplicated). New: local file: SiegeImpacts.cs (four pooled particle systems made in code: ring, plume, wall dust, drops; a fixed pool of solid pieces with its own simple bounce and roll; lingering emitters; building scars; two meshes built in code, a broken lump and a box), SoftTextures.Puff (a lumpy dust texture built in code, so dust does not read as discs), GroundDecals.AddCrater with its own capped store and two new kinds in the GroundDecal shader (crater, gore), a repainted Crater tile (bowl, lip, scattered earth), AmbientLife.Press, BuildingRenderer.SiegeJolt, local file: AudioDirector.Siege.cs. No Blender, no downloads, no asset files added (everything is built at run time). FINDING: the ground-mark shader lights its marks from the per-object main light, which an instanced draw is not given, so every existing mark is lit by the sky alone (dark). A crater so lit was a black pit with no visible lip; the crater kind now reads the sun's global colour and direction. The older marks are left exactly as they were. NOT DONE AS ASKED: "find how the 16 material families are tagged and use that". They are tagged per texel of each model's palette (DetailTextures.Family), which would need a mesh ray and a texture read per hit. I used the table the collapse already uses (faction and role), with thatch added for Tribe huts. So the Crypt, which is drawn as grey masonry, throws mostly bone. Say the word and a per-building-type table can be written by hand. COST (test build, 1920x1080 window, normal game camera, ten Orc Catapults each landing a stone on open ground every 3 s for 45 s = 141 impacts; local file: perf.ps1; the autotest report) avg fps gpu ms cpu main ms siege main ms/frame alloc_kb_per_frame "projectiles" stage off run 1 171.2 3.63 2.51 0.0009 0.00 HIGH run 1 171.5 3.72 2.59 0.0168 0.00 low run 1 171.2 3.64 2.44 0.0102 0.00 off run 2 164.1 3.91 2.41 0.0006 0.00 HIGH run 2 172.2 3.67 2.45 0.0166 0.00 Reading: no cost that can be measured at this load. The two identical "off" runs differ from each other (171.2 and 164.1 fps) by more than off differs from High. Mean of the pairs: off 167.7 fps / gpu 3.77 / cpu 2.46; High 171.9 / 3.70 / 2.52. My own timer puts the main-thread work at 0.017 ms a frame on High (125 ms in all for 141 impacts, 9,588 particles and 4,373 pieces), against a target of 3% of a 5.8 ms frame = 0.17 ms. Garbage: the stage that holds all of it (projectiles) reads 0.00 kB a frame in every run, on and off; every other stage is the same to 0.03 kB. Frames over 16 ms in the High runs: 0 and 1 of about 7,500 (off: 9 and 18, start-up). Budgets: 320 + 320 + 192 + 384 particles at most, 224 pieces (up to 7 instanced draws), 40 craters, 16 lingering emitters, 24 scars (4 a building), 12 pressed-grass spots of which 6 at most take a grass "parter". NOT MEASURED: a GPU-bound case (low-end card, many plumes overlapping close to the camera): the dust is soft alpha-blended quads up to 3.4 m across, so overdraw is where a cost would appear. Ten buildings struck at once. TESTS New: local file: SiegeImpactTests.cs, 14 tests, 14 pass (local file: t3, related_a, related_b): the shader, the dust texture, the crater tile's bowl / lip / edge and the materials ship; it builds with the real assets; every sound id has a definition and a clip on disk; Off (by the override and by the setting) spawns nothing at all (no particle, piece, crater, tremor or sound); Low is about half of High (measured: one ground and one wall strike = 93 particles and 53 pieces on High, 47 and 28 on Low); a ground strike's counts, one crater, the pieces all at rest within 7 s and all gone within 47 s, the grass parter taken; ground types (more dust on sand, lumps in mud, nothing solid and no crater in water); a building strike's debris and the dust that goes on falling, nothing clinging on the killing shot; debris by building; the payload wet and dry; 1,200 impacts in volleys of ten: pieces never over 224 (Low 96), particles within the pools, craters never over 40 (Low 16), 6 parters; the crater store, its 320 s life and Off; the camera tremor off with the setting off, zero at 130 m, less at 13 m than at 0, within the ceiling; a volley of ten = one tremor and three sounds, and the audio's own limit of three impacts in any 0.4 s. Related existing groups, two runs on the final code: related_a: 210 cases, 206 passed, 4 failed. Groups: SiegeImpact, LiveMissile, MissileVanishProbe, CatapultMotion, SiegeIdlePresentation, SiegeWorkshop, WaveSiege, BuildingDestruction, BuildingDamageLook, BuildingLoot, CurrentBuildingArrowProfile, VisualPolish, BloodFx, CombatPresentation, ReplayPresentationReset, AudioFeedingHistory, CinematicPresentation, PersonHit, HitWeight, GrassTuft, PlantSway, PresentationHistory, DistantVoices, DetailedBowReleaseAnchor, ClientLifetimeQuery, CoopInstall, ShadowFit. 1-3. StuckWindupPoseTests.PersonHitLeavesNoTurnOnAnyBoneAfterBlows (Off / Low / High): 2.74 degrees, limit 1.0. On the brief's known list. 4. CurrentBuildingArrowProfileTests.EveryCurrentCatalogSelectedRigHasAProfileFittingItsActualImportedBounds: the lodge's and the hut's imported model bounds no longer match the arrow-stick table (lodge imported x -2.81..2.88, table -2.64..2.61). NOT ON THE KNOWN LIST. It compares model files with a generated table; I changed neither (the building art is being changed by others). I did not run it on the code as found, so "not mine" is by reading, not by a baseline run. related_b: 220 cases, 214 passed, 5 failed, 1 inconclusive (the War Charge save writer, by design). Groups: SiegeImpact, VisualPolish, StateHashChunk, SaveGame, Replay, EdgeScenery, GroundLayer, PteranodonFlightUnchanged, MountedRagdollEligibility, WarCharge, LiveMissile, MissileVanishProbe, CombatPresentation, CatapultMotion, AiTests.IdenticalAiGamesProduceIdenticalStateHashes. All 5 are on the known list, with the SAME values the War Charge report gives (so the simulation's pinned numbers did not move under this work): StateHashChunk.SavedCombatFixtureHashesRemainBitIdentical; MountedRagdollEligibility.CurrentThirtySevenEntryPolicy...; PteranodonFlightUnchanged (was 14106681360001469101); EdgeScenery.TheSimulationNeverSeesTheScenery and GroundLayer.TheSimulationNeverSeesTheGround (was 11596832484050369708). Also: the stone-among-units film and the Crypt film ended on the same state hash in the untouched build / effects off and in the new build (EDC270A5B8C5BB40 and 82C6074FC0D8CFF6 at their last hashed tick). The other film pairs do NOT match, and neither do two identical "off" cost runs: the showcase gives its orders on the wall clock, so those runs are not repeatable to begin with. That is the harness, not this change, but it means the films are not a determinism proof. The victims' blast centre was found for every siege hit filmed (report line "blasts=9/9", "6/6", "15/15"). Peak editor memory 4.8 GB (six player builds, all succeeded: local file: build1-6). SOUND (existing clips only; nothing made, nothing downloaded; picked from the file list and a crude waveform reading, NEVER HEARD BY ANYONE) Stone on ground: boulder_impact played about 14% low, over giant_footfall (the deepest clip in the bank) for the weight; deposit_ore, quiet, 0.42 s later for the earth and stones coming down (not on snow or mud). Stone on a building: the same boom, plus ram_impact for timber and thatch, mine_ore (low) for stone, pachy_crack for bone; 0.45 s later deposit_wood / deposit_ore / blunt_bone, quiet, for the debris at the foot of the wall. Meat Wagon: body_thud low, gore_impale, butcher_meat low, butcher_meat again 0.35 s later; Blood Off: body_thud and blunt_bone. On a building also building_hit. Into water: big_body_fall over drink_lap, pitched down (the crocodile lunge's recipe). Bolt in the ground: arrow_hit low. Further off: up to 18% lower and 30% quieter on top of the sources' own rolloff. Voice limit: three impacts in any 0.4 s (the second and third at 70%), on top of each clip's own cooldown and voice cap; SiegeImpacts itself asks for at most three per instant. Before this a stone on open ground was SILENT, and on a building played only the arrow's thunk (arrow_thunk_wood, which still plays under the new sounds: AudioDirector.PlayBuildingHit was not changed). SOUNDS AN AUDIO WORKER SHOULD MAKE (three variants each, mono, the hit in the first 15 ms): siege_stone_ground 1.0-1.4 s a heavy low earth "whump" with a spray of gravel in it siege_stone_wood 1.0-1.4 s stone smashing timber: a deep knock, a crack, splintering siege_stone_masonry 1.0-1.4 s stone on stone: a hard crack and rubble breaking loose siege_stone_bone 0.8-1.2 s a hollow bony crack and rattle (Scourge buildings) siege_debris_earth 1.2-1.8 s clods and small stones raining down on turf, soft (the "patter a beat later") siege_debris_wood 1.2-1.8 s planks and splinters clattering down siege_payload_splat 1.0-1.4 s a wet, heavy, rotten burst with a squelching tail (Meat Wagon) siege_water_plunge 1.4-2.0 s a big stone into water: plunge, then the spray falling back siege_bolt_earth 0.5-0.8 s a heavy bolt driving into earth (optional; Kingdom benched) AudioDirector.Siege.cs names every clip in one place; swapping these in is a few lines. FILMS local file: ingame (copies in local file: 08_siege_impact and local file: Primeval_review). Each: before | after side by side at full speed, then the impact again at quarter speed, both halves; 1920x540, 6 to 10 MB. Camera 9 to 11 m away and low (-camClose), except (e). siege_a_stone_on_open_ground.mp4 (a) a Catapult's stone on open grass, two shots; replay zoomed x2 siege_b_stone_among_units.mp4 (b) a stone among six Grunts; replay zoomed x2 siege_c1_stone_on_wooden_building.mp4 (c) a stone on an Orc War Camp; the second shot brings it down siege_c2_stone_on_bone_building.mp4 (c) a stone on an Undead Crypt; the second shot brings it down siege_d_meat_wagon_payload.mp4 (d) a Meat Wagon's payload among six Grunts; replay zoomed x2 siege_e_normal_game_camera.mp4 (e) two Catapults on ten Grunts from the normal game camera (30 m) BEFORE halves: (b), (d), (e) are the UNTOUCHED build (copied to local file: before_build before my first build, same arguments). (a), (c1), (c2) could not be staged in that build (it has no ground shot and no building target on the duel stage), so their "before" is the NEW build with -siegeFx off, which runs the old code path; it is labelled so on screen. For (b) I filmed both kinds of "before" and laid frames side by side (local file: check_b_off_vs_old.jpg): the untouched build and the new build with -siegeFx off look the same (the old blood burst and puff, nothing else). I looked at frames of all six (local file: check_*.jpg, crop.jpg and sheet.jpg in each take's folder). Do the impacts read? (a) and both (c): yes, clearly, at full speed and in the replay; the "before" is close to nothing. (b): yes; the dust and flying stones are plain beside the old blood-only burst. (e) from the game camera: the plume and the ring are visible but modest; the red blood splash of the victims is still the loudest thing on screen. (d): the weakest. The existing blood on the victims dominates both halves; the new bones, lumps and brown mist are there but you have to look. A payload on open ground or on a wall would show it better and was not filmed. WHERE IT STILL FALLS SHORT - The fine grit is the existing flat square particles; close up they read as little squares. - Pieces are low-poly lumps and boxes in flat colours (no bark or stone texture); fine at game distance. - The dent on a building is a small dark faceted lump, not a real crack; on a recessed face (a doorway) it floats slightly proud of the wall. - The crater is a decal: flat, no real hole. Grass standing inside the bowl is pushed over, not removed (one tuft in the second crater of film (a) still stands in the bowl). - Dust is unlit (dimmed by hand at night, never looked at at night) and does not catch the sun. - Wild animals wander into the edge of some shots, and the duel stage kills those within 40 m when it is set, so there are bodies, blood pools and odd yellow slabs at the edge of the ground shots, in both halves. Showcase-only arguments added for these films (never used in play): -duelBuildingB TYPE, -duelGroundShot (a missile put in the air at empty ground by the showcase itself; the game has no attack-ground order), -duelGroundEvery S, -duelFocusB. NOT VERIFIED - Nobody has played it or heard it. I judged stills and contact sheets, not motion. - Sound: never heard. Levels, pitches and the 0.4 s delays are guesses from waveform numbers. - Sand, snow, ash, mud and water: counted in tests only; the film map is temperate grass. The water column, the snow lumps and the mud drops have never been looked at. - Tribe Stone Thrower, the ballista bolt, thatch debris (Tribe huts), stone debris (Kingdom), Rotting Payload, the payload on a building or on bare ground, the Low level, night: not filmed, not looked at. - The harder building rock: applied (the call is made in both building films) but not measured, and no test covers BuildingRenderer.SiegeJolt or the scar with a real building. - Victims thrown away from the impact: the centre was found for every hit (30 of 30 in the films); that the bodies visibly lean the right way was not checked body by body. - Several ticks in one frame (fast-forward, catch-up): the spent missile may be gone before the event is drawn, and the victims then fall back to the old direction. Not exercised. - Replays and multiplayer: not run with it (it is presentation only and reads nothing new from the sim; the Replay, SaveGame and ReplayPresentationReset groups pass). - The CurrentBuildingArrowProfile failure was not run against the code as found. - A weak GPU. Ten simultaneous building hits. SLIPS OF MINE, FOR THE RECORD - Three times a search-and-replace script of mine wrote text into the wrong place in a source file (GameBootstrap.cs once, ShowcaseDirector.SiegeImpact.cs once, and line endings in UnitRenderer.cs); each was caught and put right within minutes, once only after a test run had failed to compile on it (local file: t1). The final files were checked against the originals line by line (changes.diff). - A player build flipped two lines of local file: GraphicsSettings.asset (as the War Charge worker also saw); reverted with git checkout of that one file. git status at the end = the start plus my 4 new files and their .meta. - A folder-size command of mine ran from the wrong directory (a script before it had changed directory) and listed file sizes across the whole of local file: C: for three minutes, hitting "access denied" on system folders. It only listed names and sizes: it opened, changed and deleted nothing. The sizes below were measured again with full paths. DISK: this job holds 18.5 GB under local file: siege_impact (ingame 4.3, work 3.8 of which before_build 3.8, removed 7.8 of earlier takes, logs 2.6). All of it can go once the clips are approved; nothing was deleted by me. FILES changes.diff (the 14 changed files against local file: orig, the 4 new files in full), local file: mine (copies), work (runner, capture, film_all, encode, perf, mkdiff scripts; before_build = the untouched test player, 3.8 GB), logs. Changed: local file: ProjectileRenderer.cs, CombatEffects.cs, UnitRenderer.cs, BuildingRenderer.cs, SoftTextures.cs, local file: GroundDecals.cs, local file: GroundDecalTextures.cs, local file: AmbientLife.cs, local file: AudioDirector.cs (one line), local file: RtsCameraController.cs (a test accessor), local file: GameSession.cs, GameBootstrap.cs, ShowcaseDirector.cs (showcase hooks), local file: GroundDecal.shader. New: local file: SiegeImpacts.cs, local file: AudioDirector.Siege.cs, local file: ShowcaseDirector.SiegeImpact.cs, local file: SiegeImpactTests.cs. No art, .fbx or art .meta touched. CatapultInstallTemp.cs left in place. local file: Primeval never touched. The test player local file: CatapultTest holds the final code (build6). Raw frames of the final takes are in local file: <scene>_before|after (about 4 GB); earlier takes in removed.
Path of the axe head for current, A and B; the impact frame in red.
Current | chop A | chop B against a grey Grunt statue. Four blows at game speed, then one at quarter speed with the edge path in yellow. You picked A and B, alternating.
Axes fitted edge-down (top) vs the 13-degree version (bottom).
Berserker: new chop (option A), edge-down Blackglass Fang axes and their textures installed into local file: PrimevalMirror 2026-11-09 17:29. No .meta, json or prefab change. Backups: local file: backup_before_install orc_berserker.fbx installed_ok=True meta_unchanged=True orc_berserker_basecolor.png installed_ok=True meta_unchanged=True orc_berserker_teammask.png installed_ok=True meta_unchanged=True orc_berserker.png installed_ok=True meta_unchanged=True
ORC BERSERKER - NEW ATTACK "THE CHOP" + AXES RE-FITTED EDGE-DOWN (9 Nov 2026) STAGED ONLY. NOTHING HERE IS IN THE GAME.
=====================================================================================================================
FILES IN THIS FOLDER
berserker_attack_options.mp4 CURRENT | A | B side by side, a grey Grunt statue standing where the game puts the enemy
(1.52 m). Three-quarter view, side view, game camera; each: 4 blows at game speed (one a
second, standing between blows as the game plays it), then one blow at quarter speed
with the path of the cutting edge drawn in yellow. 24 s. Copy in local file: Primeval_review.
berserker_attack_arcs.jpg the path of the axe head for CURRENT, A and B from the front, the side and above, the
impact frame in red, and a strip of frames for each.
berserker_axes_edge_down.jpg standing, mid-walk, mid-run, front and side: edge-down fit above, the 13-degree version below.
PART 1 - AXES, CUTTING EDGES DOWN
The designer's 13-degree turn about the haft is gone. Each blade now hangs in the upright front-to-back plane.
edge against straight down sideways lean (out)
standing mid-walk mid-run standing / walk / run
Edge-down fit (this work) 2 5 and 11 9 and 10 0.1 / 0.1 / 0.1 degrees
13-degree version 13 14 and 17 16 13 / 13 / 13 degrees
(Whole clips, edge-down: standing 0.5-3.6, walk 0.1-11.4, run 0.7-9.8. What is left is the haft tipping as the arm swings.)
SIZE: unchanged from the designer's final. 0.959 long x 0.555 wide = 126 % of the live axes' length, 107 % of their
width. I did NOT have to shrink them or trim the hook. Scaling was tried (95 % down to 84 %) and did not help: with
the turn removed, the raised flake ridges on the INNER face of each blade (the face against his leg) went 2 cm into
his calves on 2 frames of the campfire "stand up" clip. Fix used: that inner face is flattened (ridges to 12 % of
their height). The outer face, the outline, the size and the textures are untouched (same UVs, no texture change).
True-penetration test (more than 4 mm inside the closed body, every axe vertex, edge midpoint and face centre):
0 frames in Idle, Walk, Run, Work, Death and all six campfire clips (546 frames), 0 in the new Attack A and B.
WARNING: with the OLD Attack clip the edge-down right axe is 3 cm inside him on 1 frame (frame 10). The edge-down
axes should go in together with a new Attack. (The 13-degree version was clean on that frame; that is why it was turned.)
PART 2 - THE ATTACK. Same length (25 frames, 0.8 s) and same impact frame (9 = 0.3 s) as now: no json, meta or rule change.
CURRENT the clip in the game. An upright hack: the right axe lifts straight up in front and drops. It lands at waist
height, short of the enemy. Feet and hips do not move. It also starts 33 cm / 33 degrees away from his new
standing pose, so with the new Idle installed it would pop at the start and end of every blow.
A THE CHOP (right axe). Axe drawn up and out wide beside and behind the right shoulder, chest turned away, weight
back, left axe forward as a guard, lead foot lifting. Hips fire, chest follows, the axe comes over and
DIAGONALLY across from high-outside to low-inside, through the enemy's collar and chest on frame 9, past his
left hip. Body drops into it. Held low for 4 frames, then drawn back. Starts and ends exactly in the new Idle.
B THE CHOP, OTHER HAND (left axe). The same blow mirrored, same lead-foot step.
NOT the "double cut" that was asked for first: it does not fit. After the impact there are 15 frames. A
second cut needs its own 3-frame strike, follow-through and at least 5 frames to recover, which leaves
0-1 frames of hold for the main blow and a rushed recovery, and the game deals no damage on a second
contact. So B is the mirrored heavy chop, as the brief allowed. A and B could alternate if the game gets a
second attack slot.
NUMBERS (game scale, on the staged files; Grunt statue 1.52 m in front) CURRENT A B
Arc plane, degrees from upright (0 = up-and-down hack; target 35-55) 17 54 52
Flatness of the arc (tip's average distance from its plane) 12 cm 1 cm 4 cm
Arc length of the swing / angle swept (no clear swing) 5.4 m / 239 5.4 m / 238
Strike: frames from the top of the wind-up to impact none (steady lift) 3 (6 to 9) 3 (6 to 9)
Tip speed in the wind-up, m/s 20-23 30-35, then 7 30-35, then 7
Peak tip speed / between frames 56 / 8-9 50 / 8-9 53 / 8-9
Largest change of direction of the tip path, one frame to the next:
inside the strike arc 147 (f7) 65 (f8) 68 (f8)
at the top of the wind-up (slow, 7-15 m/s) - 144 (f6) 143 (f6)
Edge against its direction of travel through the strike:
flat of the blade leading (should be near 0) up to 26 up to 9 up to 12
within the blade's own plane, at the impact frame 60 46 41
Cutting edge on the impact frame 2 cm SHORT of him, at 0.87 m 10 cm INSIDE, at 1.52 m up to 13 cm INSIDE, at 1.55 m
Edge points inside the statue on the impact frame (of 9 along the edge) 0 6 5
First frame the edge is within 3 cm of the statue 9 9 9
Frames with an axe inside his own body 1 (3 cm) 0 0
Axe to axe, closest 1.12 m 0.33 m 0.30 m
Forearm / hand centre line inside his body (frames) 0 0 0
Forearm centre line to his body, closest 8 cm 4 cm 1 cm
Largest wrist bend (striking hand; his Idle is 29 right / 41 left) 30 57 60
Planted (rear) foot slide 0 0 mm 0 mm
Lead foot does not move 17 cm step, 5 cm lift same
Hips drop at contact / at the lowest 0 7 / 12 cm 7 / 12 cm
Head off the target, worst 25 deg 16 deg 15 deg
First frame against the new Idle pose 33 cm off 0.00 mm 0.00 mm
Every other clip (Idle, Walk, Run, Work, Death) against the base file - identical identical
MY PICK: A. B is the same quality; it exists so there is a choice of hand.
STILL WRONG / BLUNT
- The wind-up is too quick to feel heavy. There are only 6 frames (0.2 s) before the strike, and the axe has a long
way to go, so it flies up at 30-35 m/s, not far below the strike's 39-50. It reads as a whip-up, then a 2-frame
pause, then the blow. The hold after the blow is the only slow, heavy beat. See SUGGESTION.
- At the top of the wind-up the axe head doubles back on a narrow loop (144 degrees), not a round one.
- Through the first two strike frames the lower corner of the blade leads (72 and 62 degrees in the blade's plane);
at impact the edge faces 46 degrees off its travel. A fist beside the haft cannot bring that to zero, but it is
not the clean "edge first" I was asked to show. The flat never leads by more than 9 (A) / 12 (B) degrees.
- The strike covers 239 degrees in 5 frames, so the arc is drawn by about 6 poses with 44-65 degrees between them.
At game speed that is a blur with no trail; the smooth yellow ribbon exists only in my slow-motion pictures.
- Arm against body: nothing goes THROUGH him by the measure, but in B the left forearm's centre line passes 1 cm
from his belly on frames 9-10 and in A the right one 4 cm from his chest on frame 9, so the skin of the arm
touches or sinks a little there. I did not see it in the stills. Please look at frames 9-12 at quarter speed.
- The skin of the upper arm is turned up to 160 degrees between shoulder and elbow at the top of the wind-up
(frames 4-8), so that the elbow bends the right way. In the close stills I looked at (frames 5, 7, 8) the
shoulder looked acceptable; I did not check every frame.
- The forward drive comes after the hit: at contact his hips are 2 cm BEHIND where they stand in the Idle (the game's
striking distance is short for an axe this long, so the body cannot be far forward at contact); they go 14 cm
forward by frame 12. The lead-foot step is small.
- The blow lands at collar height on a 2 m Grunt. On a short enemy it would pass over the shoulder.
- The off hand is plain: forward as a guard, then drawn back low. Fingers and face do not move.
- Soles dip 7-9 mm under the floor on one frame of the step.
- B is a mirror, not a second idea.
SUGGESTION (not applied; it changes the json, the .meta clip range and probably the cooldown)
A 32-frame clip (1.07 s) with the impact on frame 13 (0.43 s) would give the wind-up 10 frames instead of 6: a slow
draw, a real pause at the top, the same 3-frame strike. The berserker attacks once a second, so that needs the
cooldown raised to about 1.2 s or the standing gap dropped. A true double cut needs about 34 frames.
NOT CHECKED
- I looked at still frames (key frames from the side, three-quarter front and back, above and the game camera,
close-ups of frames 5-18, four stills pulled from the finished video, the two sheets). I did NOT watch the video
play. Timing and weight in motion are judged from the numbers. Please watch the game-speed parts.
- Nothing was seen in the game: Unity was not started. No crossfade, no hit effects, no game lighting, no crowd.
The staged files were re-read in Blender only, never imported by Unity.
- The game-camera view is the game's angle (55 degrees down, 25 m) zoomed in about 2 times.
- The four twist bones of each arm are keyed in the Attack clip (they are still in the other clips). The exporter
bakes every bone in every clip anyway, but I have not seen Unity play it.
- Attack against buildings, big animals or anything that is not a person-sized enemy 1.52 m away.
- The 0.2 s between blows is shown as the first frames of the Idle; the game's own blend may differ.
- Unit icon / portrait, campfire clips in motion (measured only), Death against the ground.
INSTALL LIST (pick ONE of the two FBX files)
local file: orc_berserker.fbx -> local file: orc_berserker.fbx
(or local file: orc_berserker.fbx for the left-hand blow)
Each = the animation worker's final file (new Idle, Walk, Run) + the edge-down new axes + that Attack. It REPLACES
both that worker's staged berserker file and the axe designer's staged FBX (which has the 13-degree axes on an
older base). Same objects, 71 bones, material, clip names and lengths (Idle 61, Walk 29, Run 21, Attack 25, Work 31,
Death 37 keys), so no .meta, .json, prefab, controller or code change. impact_seconds stays 0.3.
The axe textures are the designer's, UNCHANGED, and are still needed with either file (his install list, items 1-3):
local file: orc_berserker_basecolor.png -> local file: orc_berserker_basecolor.png
local file: orc_berserker_teammask.png -> local file: orc_berserker_teammask.png
local file: orc_berserker.png -> local file: orc_berserker.png
Nothing else changes. The .json / .png files in local file: export_A and export_B are by-products: do not install them.
WORK FILES local file: berserker_attack
ATTACK_METHOD.md (the recipe for the other melee units), local file: chop.py (the attack), local file: atk_measure.py (numbers),
local file: atk_render.py, local file: berserker_attack_final.blend (actions Attack_A, Attack_B), local file: m_CURRENT.json, m_A.json,
m_B.json (every number, every frame), local file: axe_params_edge_down.json + local file: restage.sh (edge-down axes onto any
berserker FBX), local file: base_edge and local file: base_13 (the base file with each axe fit), logs.
Ghoul (Grave Leap): before | after, then the charged blow at quarter speed. No leap animation yet.
Grunt (Blood Rush): before | after, then the charged blow at quarter speed. Watch the lean, the shove of about 1 m and the stagger.
Spear Warrior (Spear Rush): before | after, then the charged blow at quarter speed.
WAR CHARGE - report, 2026-11-09 (work in local file: PrimevalMirror only, nothing committed) The full version of local file: CHARGE_PROPOSAL.txt, as the lead chose. WHAT THE OWNER WILL SEE A new research at each faction's first barracks. Once it is done, that faction's shock infantry no longer just jogs the last stretch: it leans forward and runs at its target half again as fast, and the first blow it lands at the end of the run is a heavy one: a bigger burst and spark where it lands, a kick of dust at the victim's feet, both bodies held in the hit for a moment, and the victim is thrown back about a metre and staggers before it can act again. Tribe "Spear Rush" War Hut 150 food 70 iron 30 gold 50 s after War Paint Spear Warrior key R Orc "Blood Rush" War Camp 170 food 75 iron 30 gold 50 s after Berserker Rage Grunt, Orc Berserker key B Undead "Grave Leap" Crypt 130 food 60 wood 40 iron 30 gold 48 s after Ghoul Frenzy Ghoul key L Passive, no button. It happens on an attack order, attack-move, patrol, hunt, or when a soldier picks a target itself. Nobody else charges (Clubman, Heavy Guard, Brute, Abomination, skeletons, riders, heroes, beasts keep the plain sprint). THE NUMBERS AS BUILT (Catalog.cs, constants named WarCharge...) starts 4 to 10 m past its reach, along an open line WarChargeMinMeters 4, WarChargeMeters 10 runs at 150% of its pace, 3 s at most WarChargePercent 150, WarChargeTenths 30 first blow after the run: 150% damage, that one blow WarChargeDamagePercent 150 shove: about 1 m (measured 0.98 m on a Bowman and a Villager) WarChargeShoveSpeed 7.5 m/s stagger: 0.4 s (12 ticks at 30 a second) WarChargeStaggerTenths 4 10 s before the next charge, counted from its end WarChargeCooldownTenths 100 the plain engage sprint (7 m, 125%) still runs in between, on its own timer (measured: 4 sprints between 3 charges) Big bodies (the same Catalog.IsLargeBody rule the shield and parry use: war triceratops, tame and risen tyrants, the risen big beasts, every big wild dinosaur), siege engines and boats take the 150% damage and are not moved or staggered. A charged blow caught on a shield (25%) or parried (8%) shoves and staggers nobody; the shield still lets 30% of the (150%) damage through as for any blow, a parry none. Decided as the lead suggested. WHAT DIFFERS FROM THE PROPOSAL, AND WHY 1. Front-line targets are shoved 0.4 m and staggered 0.2 s, not 1 m / 0.4 s (measured 0.38 m on a Clubman and on Heavy Guards). That is the owner's standing rule of 2026-11-02 for Clubman, Spear Warrior, Heavy Guard, Grunt, Brute, Berserker, Skeleton Warrior, Ghoul, Abomination ("less pushed around, pushed or stunned less"): the shove and the stun go through the same Throw / StunTicks code the Lancer uses, which applies it. Archers, throwers, workers, casters, heroes and riders get the full metre. Say the word and the charge can ignore that rule (one line). 2. "A charge of at least 4 m" is measured as the gap the two closed (4 m), of which the charger itself ran 2 m. Reason, measured: a soldier on attack-move only sees a foe 9 m away, so two that charge each other head-on run about 3 m each; counting only the charger's own run, a 6-a-side clash produced 3 charged blows, with the gap rule 9. 3. The 10 m start is only reached on a direct attack order (measured 9.0-9.9 m past reach). On attack-move, patrol and auto-acquire a melee soldier's sight is 9 m centre to centre, so its charge starts about 6 m out. I did not widen anyone's sight: that changes every fight in the game. 4. Nearer than 4 m past its reach the charge is not started and not spent: the plain sprint serves, the charge is kept. 5. No charge against buildings (they are attacked by another code path; nothing was added there). 6. The Ghoul has no leap animation. It runs leaning forward like the others. A pounce clip is art work, not done. 7. AI fight rule ("wait to attack-move from 10 m out") NOT built: the AI's armies already attack-move in from far away, and its armies' approach code is not a small change. Skipped as the brief allowed. 8. Prerequisite for the Tribe: the proposal says "after the armour or weapon upgrade". A research has one prerequisite slot, and those upgrades live at the Bone Forge; I used War Paint, the War Hut's own first upgrade and the twin of Berserker Rage and Ghoul Frenzy. One word to change. HOW IT IS BUILT Simulation: TaskSystem.WarChargeDue / StartWarCharge / EndWarCharge / WarChargeStrike beside the engage sprint, in the same two places a soldier walks in on a unit; MovementSystem reads the second speed factor (a charge wins over a sprint still running). The shove and stagger are the Lancer's own code (Throw with no lift, Catalog.StunTicks). Per-unit state: four spare WildMoves fields a foot soldier never uses (NextTailTick = next charge, ChargeRunDirection = where it began, NextStompTick = how far off its target was, ShakeOffTick = blow armed until). Already saved, already hashed. I avoided the fields MovementSystem and the projectile renderer read for every unit (ChargeUntilTick, HoldUntilTick, GrappleUntilTick, ChargeRunDistance / Altitude). Research ids: Orc 60 and Undead 94 in their own blocks; the Tribe's block is full, so Spear Rush is the second "overflow" topic (id 125, PlayerState.ExtraUpgrades bit 1), the mechanism the Second Campfire already uses. Events: SimEventFlags is a full byte, so the charge has its own event, SimEventType.WarCharge = 100, with a phase: Began, Struck (shoved), StruckUnmoved (big body), Stopped (blocked or parried). The blow's own UnitDamaged comes first. AI: the research counts as an army upgrade (the forge upgrades' stage) once the seat owns 4 of the units that charge; before that it is not considered. AiBrain.ResearchCategory + ResearchStage (which now also looks at the overflow id). Presentation, all on existing systems (the 2026-10-15 combat feel pass and 2026-11-03 HitWeight / PersonHit): charge: the body pitched 16 degrees forward about its feet (new: DetailedUnitRenderer.ChargeLean), the Run clip allowed to turn over up to 50% faster than its ceiling so the feet keep up; dust kicked back at the push-off. charged blow: the victim's reaction set to the heaviest a person has (hit strength 2.2, HitWeight.MostImpact: the stagger pose, full hit flash), hit-stop 0.12 s on the victim and 0.08 s on the striker, the striker's follow-through hold; a HeavyImpact burst, a hard spark, ground debris and a FootDust kick at the victim's feet along the blow. The sim's shove then carries the staggering body back, so it reads as thrown, not slid. sound: ram_impact (the battering ram's impact, the heaviest non-beast impact in the project) at 70% under the weapon's own sound and the body thud. No audio made or downloaded. Low-poly rigs (units without a detailed model) get the reaction and effects but no lean. Tooltip: each research's description says what it does; the tooltip's effect line shows "Engage sprint (7 m at 125% pace) -> charge (10 m at 150%, first blow 150% damage, every 10 s)". Showcase-only arguments added for the films (never used in play): -duelResearch, -duelHoldB, -duelGap N, -duelMid F, -duelShift N, -duelShiftSide N, and -camClose. FINDING: the cinematic camera silently held every -camDist under 15 m at 15 m (RtsCameraController.MinDistance), which is why the earlier workers' duel clips were too far away. -camClose lets a review film come to 4 m; the player's camera is unchanged. VERSION BUMPS RulesRevision 117 -> 118 (comment added). Save format NOT bumped, still 22: no new field (the research is a bit in masks that are already saved; the unit state reuses saved fields). NetProtocol NOT bumped, still 22: no new command or wire format (the research goes through the existing Train command). Replay format NOT bumped (headers carry the rules revision). TESTS New: local file: WarChargeTests.cs, 32 cases: 30 tests, 1 explicit balance probe, 1 explicit save writer. Written against ids so the same file compiles on the code as found. There (local file: base1): 23 of the 30 FAILED as intended (no research exists, nobody charges, no charged blow, the AI never buys it); two of those 23 also showed a fault in my own measuring (one-tick speed spikes, hit-location noise), fixed before the "after" runs. After (local file: fix2, final_tests): 30 of 30 pass (and the probe); the save writer is inconclusive by design (it refuses to run on rules 118). research: ids, costs, buildings, prerequisite, one faction's research is not another's; researched at a War Camp through the real Train command it costs 170 food and the Grunts charge afterwards gating: without it nobody charges (0 events, peak 1.25x); with it Spear Warrior / Grunt / Berserker / Ghoul peak at 1.50x from 9.0-9.9 m past reach; Clubman, Heavy Guard, Brute, Abomination, Raptor Rider stay at 1.25x cap and cooldown: chasing a target kept 8 m ahead for 32 s: charges of 86, 90, 90 ticks starting at ticks 5, 391, 781 (3 s, then 10 s), 4 plain engage sprints in between +50% once: 12 Grunts, first blow 39.4 against 24.8 for later ones (1.59x), second blow 25.5; unresearched 0.98x shove and stagger: Bowman 0.98 m / 0.4 s, Villager 0.98 m / 0.4 s, Clubman 0.38 m / 0.2 s (the test reads the stun one tick after the blow: 11 and 5 ticks left of 12 and 6); Heavy Triceratops, Catapult, Risen Tyrant 0.00 m, no stagger, damage taken block: 36 charged blows on Heavy Guards: 9 caught (moved 0.00 m), 27 landed (shoved 0.38 m or more) AI: Tribe / Orc / Undead computer with 3 chargers does not queue it, with 4 it does saves: a save written by the rules-117 build (local file: rules117.prmsave.bin) loads, reads as not researched, plays on 10 s with 76 blows and 0 charges; a save taken mid-charge hashes equal and stays equal 15 s on determinism: the same 6-a-side clash twice: 9 charged blows, hash 960A041B4DE74EB6 both times; without the research 0 charged blows and another hash Related groups (local file: related_a, related_b), 519 test cases in two runs: 510 passed, 8 failed, 1 inconclusive (mine). NONE of the 8 is caused by this work: each fails with the same values on the code as found (local file: base_related), or is on the brief's stale list. 1. StateHashChunkTests.SavedCombatFixtureHashesRemainBitIdentical - known stale. 2. MountedRagdollEligibilityTests.CurrentThirtySevenEntryPolicy... - known stale. 3. PteranodonFlightUnchangedTests.ItsFlightOverAVersusMapIsUnchanged - known stale (was 14106681360001469101 before and after my change). 4. EdgeSceneryTests.TheSimulationNeverSeesTheScenery } NEW TO THE LIST: the same pinned 600-tick 1v1 hash, expected 5. GroundLayerTests.TheSimulationNeverSeesTheGround } 10803087842307530174, was 11596832484050369708, identical on the code as found (rules 117). The earlier workers filtered on "SimulationTests" and never ran these two. Somebody's earlier rules change moved it; NOT re-pinned by me (I cannot say which change). 6-8. StuckWindupPoseTests.PersonHitLeavesNoTurnOnAnyBoneAfterBlows (Off / Low / High): "head left turned by the blows" 2.7 degrees, limit 1.0. Fails the same on the code as found. Probably the unit art in flux. Not mine. No golden was re-pinned. FormationMarch's TightGolden passes unchanged at rules 118. CannotRecoverTests: the stale "RulesRevision == 115" is now "at least 115" with a comment; the class passes. Groups run: WarCharge, MeleeEngageSprint, SquadSpacing, FormationMarch, FormationRank, MovementFeel, CombatBatch, CombatDepth, CombatRealism, AttackScheduleTask, TribeRiders, MissionWaveAttackMove, OrcRoster, UndeadRoster, Roster, TribeRoster, AiArmyControlFix, AiCoreFix, AiTests.IdenticalAiGamesProduceIdenticalStateHashes, StateHashChunk, ShieldBlock, WeaponParry, Faction, Campfire, UiArtCoverage (hotkeys and icons), "Simulation" (wider than before), SaveGame, Replay, HeroLevel, PteranodonFlightUnchanged, MountedRagdollEligibility, LiveMissile, Injury, WoundStage, CombatTextContent, CombatPresentation, Sandbox, TooltipText, DetailedInjuredAttackTiming, CannotRecover, HitWeight, PersonHit. Peak editor memory 5.3 GB. AiLongGame, HerdPathProbe and the balance matrix were NOT run. Last run on the final code (local file: final_tests: WarCharge, MeleeEngageSprint, TooltipText, UiArtCoverage, CannotRecover, FormationMarch, ShieldBlock, WeaponParry, Campfire, Faction, AiCoreFix, Sandbox, TribeRiders, SquadSpacing, CombatPresentation): 225 cases, 224 passed, 0 failed, 1 inconclusive (the save writer). The two big related runs were made before the last showcase-only and tooltip edits and were not repeated in full. BALANCE, MEASURED (WarChargeTests.LogWarChargeClashes, local file: probe1: equal cost 1200 a side, 60 s, 4 seeds, no AI; the melee squad attack-moves at a ranged line standing 26 m off, or two melee squads walk into each other; the figure is the share of its starting health the winner keeps; "researched" = the left side has its charge) not researched researched charged blows Spear Warrior x7 vs Sk. Archer x12 archers keep 0.75, 0/4 archers keep 0.51, 0/4 23 Spear Warrior x7 vs Axe Thrower x13 throwers keep 0.77, 0/4 throwers keep 0.73, 0/4 26 Grunt x13 vs Bowman x10 grunts keep 0.63, 4/4 grunts keep 0.70, 4/4 46 Berserker x8 vs Bowman x10 0.23 against 0.11, 3/4 berserkers keep 0.36, 4/4 32 Ghoul x15 vs Bowman x10 ghouls keep 0.73, 4/4 ghouls keep 0.79, 4/4 58 Grunt x13 vs Clubman x12 grunts keep 0.42, 4/4 grunts keep 0.57, 4/4 49 (3 blocked/parried) Ghoul x15 vs Clubman x12 clubmen keep 0.33, 0/4 0.10 against 0.15, 2/4 56 (6 parried) Reading: no ranged matchup changes hands. Spear Warriors still lose to both ranged lines 0/4; the two melee wins get a little cleaner; Berserker vs Bowman goes from a 3/4 edge to 4/4. The one real swing is melee against melee: Ghouls against Clubmen go from a clear loss to a coin flip. That is an advantage, not a rout, so NOTHING WAS SOFTENED: the stagger stays 0.4 s and the bonus 50%. Four seeds is a small sample (single runs swing about 0.1). The unresearched columns differ a little from the earlier reports' because the fields and seeds are this job's own. ICONS OWED (three) Spear Rush uses research_flint (Flint Spearheads'), Blood Rush research_berserker_rage, Grave Leap research_ghoul_frenzy, each sitting on the same card as (or a card away from) the upgrade it borrows from. No icon pipeline was run. TEST PLAYER AND FILMS local file: CatapultTest rebuilt three times (local file: build1-3, all succeeded, no other Unity running): it holds rules 118 and the final code. local file: ingame (copies in local file: 06_war_charge and local file: Primeval_review) war_charge_grunt.mp4 4 Grunts at 4 standing Bowmen war_charge_spear_warrior.mp4 4 Spear Warriors at 4 standing Axe Throwers war_charge_ghoul.mp4 4 Ghouls at 4 standing Bowmen Each: 7 s side by side at full speed (BEFORE = the same build without the research | AFTER = researched), then the first charged blow of the AFTER film again at quarter speed, zoomed x3. 1920x540, camera 9 m away and low (-camClose -camDist 9 -showcasePitch 18 -showcaseYaw 45), squads 18 m apart, targets holding their ground. I looked at frames of all three (local file: check_*.jpg). The quarter-speed halves read: you see the lean, the swing, the burst where it lands and the victim bent back and carried away. Best on the Grunt and the Ghoul. On the Spear Warrior it is weaker: the spear's reach keeps the two bodies apart, and the Axe Throwers stand shoulder to shoulder so the shoved one has nowhere to be seen going. At full speed and half size (the side-by-side) the difference is there but modest: the charging squad leans, arrives sooner and the first hit has a bigger burst. It does not jump off the screen at that size. The x3 zoom is soft (the footage is 960x540). Honest limits of the stage: the duel ground is the home clearing in front of the town hall, with the bodies of the seat's own starting workers lying about (the stage clears everything within 40 m before it starts). I tried three other grounds; trees there stood in the line and no charge started (the charge, like the sprint, needs an open line). Arrows from the standing archers are sticking in the chargers by the time they arrive. 2.3 GB of stage tests and first takes are in local file: removed (nothing deleted); raw frames of the six final takes (1.2 GB) are in local file: <unit>_before|after. NOT VERIFIED - Nobody has played it. Whether it "feels weighty" is the owner's call. I judged stills and contact sheets, not motion. - The sound was never heard by anyone: ram_impact at 70% was picked from the file list. - The research buttons, their tooltips and the borrowed icons were not opened in a running game (the hotkey test and the icon coverage test pass; TooltipText passes). - Only the three filmed pairings were looked at. The Orc Berserker's charge was measured, not filmed. A front-line victim (0.4 m) and a blocked charge were measured, not filmed. - Low-poly rigs: no lean was added, and none was looked at. - The lean is a whole-body tilt, not an animation; on a unit with planted feet it was only seen on these three models. - Dense trees: a charge needs an open line to its target, so in woods it will often not happen. Measured only in the open. Hills, water and walls were not tried. - A moving target was tested only for the cap and cooldown, not for the blow. - No AI-vs-AI games, no balance matrix, no multiplayer session. The AI test proves it queues the research at 4 chargers with everything else done; when it buys it in a real game was not watched. - Two melee armies that both have the research were not probed (each probe row gives it to one side only). - The Tribe prerequisite and the front-line shove (points 8 and 1 above) are my readings; both are one-line changes. - EdgeScenery / GroundLayer / StuckWindupPose failures predate this work and were left alone. SLIPS OF MINE, FOR THE RECORD - Twice I removed a small file I had just created myself in my own work folder (the first old-save file, a temporary copy) with a plain delete instead of moving it aside. Nothing of anyone else's. - An explicit test of mine overwrote the rules-117 save once; I regenerated it from the code as found and made the writer refuse to run on rules 118. FILES changes.diff (every file changed against local file: orig, new files in full), local file: mine (the changed files), work (runner, swap, capture, encode and diff scripts), logs. Changed, simulation: Catalog.cs, Catalog.Tribe.cs, Catalog.Orc.cs, Catalog.Undead.cs, Components.cs, Gameplay.cs, SimulationHost.cs, local file: TaskSystem.cs, local file: MovementSystem.cs, local file: AiBrain.cs. Changed, client / presentation: local file: DisplayCatalog.Tribe.cs, .Orc.cs, .Undead.cs, .Tooltips.cs, local file: UnitRenderer.cs, local file: DetailedUnits.cs, local file: CombatEffects.cs, local file: AudioDirector.cs, local file: RtsCameraController.cs (cinematic minimum only), local file: GameBootstrap.cs and local file: ShowcaseDirector.cs (showcase arguments only). Changed, tests: local file: CannotRecoverTests.cs (one stale assertion). New: local file: WarChargeTests.cs. No art, .fbx or art .meta touched. CatapultInstallTemp.cs left in place. git status at the end equals the start plus the new test file and its .meta; a player build had flipped two lines of local file: GraphicsSettings.asset, reverted.
Before | after: Clubman vs Grunt with the new weapon parry. The word PARRY shows when it happens; the feel is hard to read from this distance.
BLOCK AND PARRY - report, 2026-11-09 (work in local file: PrimevalMirror only, nothing committed) FIRST FINDING: THE SHIELD BLOCK ALREADY EXISTS, IN THE SIMULATION It is not presentation-only. Rules 104 (owner 2026-11-04/05, local file: shield_block and landing_stack43) already has, with the owner's own numbers and 13 tests (ShieldBlockTests): - shield bearers: Heavy Guard, Skeleton Warrior, Footman (benched). Decided from the models: those three rigs and the Ceratopsian Lancer are the only unit rigs with a shield mesh; the Lancer is mounted and was ruled out then. The Spear Warrior's model has no shield. - 25% of frontal hand blows and small-beast blows are caught; a caught blow keeps 30% of its damage ("70 percent mitigated", the owner's words), is never a crit, opens no wound or injury, and puts the bearer's next blow off by about half a second ("it resets his attack timing"). - the Heavy Guard alone also stops 60% of frontal missiles whole (no damage, arrow sticks in the shield). - never from the side or behind (front = 60 degrees either side), never while stunned, down or thrown, never a big beast, siege, bleed tick, grab, trample. - Block clip, shield jolt, wood chips and spark, "BLOCK" text, wood knock sound: all already there. I LEFT ALL OF THAT AS IT WAS. The lead's starting design differs in four places, and in each the existing rule is the owner's explicit earlier choice, so I did not overwrite it. Say the word and each is a small change: 1. blocked blow = no damage at all (now: 30% lands). One constant, Catalog.ShieldBlockDamagePercent. 2. missiles blocked 25% by every shield (now: Heavy Guard 60%, Skeleton Warrior none). 3. a big beast's hit halved by a shield (now: big beasts go straight through, "not a massive dinosaur"). 4. tower arrows never blocked (now: the Heavy Guard can catch a tower's arrow). Also not added to the shield: "no block in his own swing" (it would contradict "resets his attack timing"). WHAT IS NEW: THE WEAPON PARRY (rules 117) What the owner will see: a foot soldier with a hand weapon and no shield now and then turns a blow aside. The blow does no damage, no blood, no hit flash, no hit sound, no wound or injury. He rocks back a little (the existing recoil layer; these models have no Block clip), sparks and a puff fly where the weapons meet, a ring of iron (sword_hit, for blades and the iron spear) or a knock of wood (chop_wood, for clubs and mauls) plays, and the word PARRY floats up when combat text is on. No audio was made or downloaded: both clips were already in the project. Who parries, 8% each (Catalog.ParriesWithWeapon): Tribe: Clubman, Spear Warrior, Chieftain. Orc: Grunt, Brute, Orc Berserker, Orc Warchief. Scourge: Abomination. Human Kingdom (benched, one word in a list): Halberdier. Who does not, and why: shield bearers (they block instead); Ghoul and Bone Raptor (claws, teeth); all riders and war beasts (the mount takes the blows, same reasoning as the Lancer's shield); shooters, casters, workers, siege, animals. Rules of a parry: hand blows only, never a missile; only from the front (same 60 degrees as the shield); only what a shield could stop (people and small beasts, never a big beast or siege); not while stunned, down or thrown; not in the wind-up of his own swing; never bleed ticks, grabs, trample, spells. A parry costs the parrier no attack time. Measured rate: the roll is 7.9% over 20,000 blows; a Clubman held in front of three Grunts parried 35 of 500 (7.0%, a little under 8 because of the wind-up rule). In a free squad fight it is 3-4% of all hits, because many blows there come from the side or behind. Why 8: it is the lead's figure, about a third of a shield's chance, and at 8 no matchup changed winner (below). WHERE THE NUMBERS LIVE UnitSpec.BlockChance and UnitSpec.ParryChance (next to CritChance), filled in Catalog.GetUntuned. The simulation now reads both from the spec. The Sandbox Stats Lab has two new columns, BLOCK% and PARRY%, editable like CRIT% (TunedStat.Block = 24, Parry = 25). The tech tree's unit card shows "25% block" / "8% parry". Randomness: the same seeded hash the shield block uses (DeterministicRandom.Hash of striker id, defender id, tick), with its own salt. No floats, no UnityEngine.Random. Events: no new event type. A parry sends the existing UnitDamaged (Value 0) and ShieldBlocked events with a new flag bit, SimEventFlags.Parried = 128, set together with Blocked, so everything that already treats a shield catch as "no wound" does the same for a parry. VERSION BUMPS RulesRevision 116 -> 117 (comment added). Save format NOT bumped (no new saved state). NetProtocol NOT bumped (no new command or wire format; events are never sent or saved). Replay format NOT bumped (headers carry the rules revision). The two new TunedStat ids travel in the existing sandbox SetStat command. TESTS New: local file: WeaponParryTests.cs, 11 tests + 1 explicit balance probe. On the code as found (local file: base1, the 8 that compile there): 3 failed as intended (0 of 500 parried; no parry ticks; no parries in two runs); a 4th failed on a threshold of mine that was too high (75 hits, wanted 80) and was lowered to 50. The 5 "never" tests passed trivially, as expected before the feature exists. After (local file: fix1): WeaponParry 11 of 11 and ShieldBlock 13 of 13 pass. front 35/500 parried, behind 0/210, stunned 0/75, arrows 0/300, war triceratops 0/81, Villager / Bowman / Shaman / Raptor Rider / Ghoul 0 each, 14 parry-only ticks all at full health with no bleed or injury, two runs 15 and 15 parries with equal state hashes. Related groups, 343 tests in two runs (local file: related_a, related_b): 339 passed, 4 failed, none caused by this work except the first, which is a stale number: 1. CannotRecoverTests.ForfeitTravelsTheWireAndIsAnOrderAPlayerMaySend - asserts RulesRevision == 115. Already failing at 116 before I started. Left alone (not a state-hash golden; the lead may want it changed to 117). 2. StateHashChunkTests.SavedCombatFixtureHashesRemainBitIdentical - known stale. 3. MountedRagdollEligibilityTests.CurrentThirtySevenEntryPolicy... - known stale. 4. PteranodonFlightUnchangedTests.ItsFlightOverAVersusMapIsUnchanged - known stale. I did not check that its hash is the same value the previous worker saw. NO golden was re-pinned. FormationMarch's TightGolden still passes unchanged. Groups: MeleeEngageSprint, SquadSpacing, FormationMarch, FormationRank, MovementFeel, CombatBatch, CombatDepth, CombatRealism, AttackScheduleTask, TribeRiders, MissionWaveAttackMove, OrcRoster, UndeadRoster, Roster, TribeRoster, AiArmyControlFix, AiTests.IdenticalAiGamesProduceIdenticalStateHashes, StateHashChunk, Simulation, SaveGame, Replay, HeroLevel, PteranodonFlightUnchanged, MountedRagdollEligibility, ShieldBlock, WeaponParry, LiveMissile, Injury, WoundStage, CombatTextContent, CombatPresentation, Sandbox, TooltipText, DetailedInjuredAttackTiming, CannotRecover. Peak editor memory 4.9 GB. BALANCE, MEASURED (probe WeaponParryTests.LogShieldAndParryClashes: two squads of equal cost, 1200 each, walk into each other, no AI, 60 s, 4 seeds; the figure is the share of starting health the side has left; local file: base_probe and after_probe) before (116) after (117) winner Heavy Guard x6 vs Grunt x13 0.00 / 0.79 0.00 / 0.80 Grunt 4/4 both Heavy Guard x6 vs Bowman x10 0.49 / 0.00 0.49 / 0.00 Guard 4/4 both (identical) Skeleton Warrior x24 vs Clubman x12 0.15 / 0.04 0.13 / 0.07 Skeleton 3/4 both (35 s; free unit, sized by equal health) Clubman x12 vs Grunt x13 (no shields) 0.00 / 0.47 0.00 / 0.48 Grunt 4/4 both Spear Warrior x7 vs Brute x6 0.36 / 0.00 0.35 / 0.06 Spear 4/4 -> 3/4 Clubman x12 vs Ghoul x15 0.34 / 0.00 0.44 / 0.00 Clubman 4/4 both Reading: no matchup changes winner, so 8% stays. Shifts are 0.00-0.10 of a squad's health, inside the seed noise (single runs swing about 0.1). One seed of Spear Warrior vs Brute flipped; it was the closest one before (0.11 left). The clearest effect is where only one side parries: Clubmen against Ghouls keep 0.10 more. Shield rows barely move because the shield rules did not change. Not mine, but the probe shows it: six Heavy Guards lose to thirteen Grunts of the same cost without killing much. TEST PLAYER AND CLIP local file: CatapultTest was rebuilt (local file: build1, succeeded, no other Unity running): rules 117. local file: clubman_vs_grunt_before_after.mp4 (12 s, side by side, 4 Clubmen vs 4 Grunts on the duel stage, cropped x2; before = the build as found). Two parries happen in the after half, at 0.9 s and 4.3 s. local file: after_parry_frame131.png (a still with the word PARRY over a Grunt). Raw frames in local file: before_clubman_grunt and local file: after_clubman_grunt. Bluntly: the camera is far and the fight is a knot beside a hut; you can read PARRY, you cannot judge the feel. No shield clip was made: the shield block did not change. NOT VERIFIED - Nobody has played it. Whether a parry "reads" and feels weighty is the owner's call. The parry reaction is only the existing small recoil (a third of a hit's); there is no parry animation, and no clip for it exists. - The parry sounds and sparks were not heard or looked at closely by anyone; I only confirmed the text shows. - The Stats Lab's two new columns were not opened in a running game (the table may be two columns wider than it was laid out for). The Tuning path under them is tested. - Chieftain, Brute, Berserker, Warchief, Abomination and Halberdier parry by the same code but only the Clubman and (in the probe) Grunt, Spear Warrior and Brute were exercised. - A small beast that throws its victim after a hit (a pachycephalosaur's ram, for one) still throws him after a parried or blocked blow. That was already so for the shield; I did not change it. - "Never from behind" is tested with a held, still defender. Fleeing units are covered only by the same facing rule. - The determinism test's hash equals the pre-change hash, because the test tops health up every tick: it proves repeatability, not that parries alter state. The balance probe shows they do. - No AI-vs-AI games, no long balance matrix, no multiplayer session, no old-save load test beyond SaveGameTests. - Unit tooltips and descriptions were not changed (the Heavy Guard and Skeleton Warrior texts already mention blocks). FILES changes.diff (every file changed against local file: orig, and the new test file in full); local file: mine (the changed files), work (runner, capture and diff scripts), logs. Changed: Catalog.cs, Tuning.cs, local file: TaskSystem.cs, SimulationHost.cs, Gameplay.cs (simulation); CombatText.cs, CombatEffects.cs, local file: AudioDirector.cs, local file: StatsLab.cs, TechTree.cs (client / presentation). New: local file: WeaponParryTests.cs (+ .meta made by Unity). No art, .fbx or art .meta touched. git status before and after differs only by the new test file and its meta: no settings or material noise. CatapultInstallTemp.cs left in place.
Before | after: melee soldiers sprint the last few metres. Filmed from far away, so the speed-up is hard to see.
WARRIOR CHARGE - proposal for the owner (design only; nothing here is in the code)
2026-11-09
What every melee unit has today (built in this job): an "engage sprint". Within 7 m of what it is going to strike it
runs a quarter faster for up to 3 s, then needs 4 s before the next. No extra damage, no shove. It only closes the gap.
RECOMMENDED VERSION: "War Charge", one research per faction, for that faction's shock infantry only.
Who gets it
Tribe Spear Warrior (the spear is levelled and driven in). Not the Clubman or Heavy Guard: they hold the line.
Orc Grunt and Berserker (the faction that should hit hardest on contact). Not the Brute: slow, it is the anvil.
Undead Ghoul (a leaping pounce, same rules, different animation). Not the Abomination or skeletons.
Cavalry, heroes and beasts are left out: riders already have speed, and the Lancer has its own charge.
What it does
The unit's engage sprint is replaced by a real charge: it starts from further out (10 m instead of 7), runs faster
(x1.5 instead of x1.25) for up to 3 s, and the FIRST blow that lands at the end of a charge of at least 4 m:
- does +50% damage (one blow only),
- shoves a man-sized target back about 1 m and staggers it for 0.4 s (it cannot swing or shoot in that time),
- big bodies (war triceratops, tyrants, abominations, siege) take the damage but are not shoved or staggered.
Then 10 s before that unit can charge again (the plain engage sprint still works in between).
Passive: no button to press. It happens whenever the unit attacks from far enough away, on an attack order,
attack-move, patrol or auto-acquire. That keeps big fights readable and needs no new command.
Where and what it costs (in line with the other tier-2 melee upgrades, which run 150-250 total)
Tribe "Spear Rush" War Hut 150 food 0 wood 70 iron 30 gold 50 s (after the armour or weapon upgrade)
Orc "Blood Rush" War Camp 170 food 0 wood 75 iron 30 gold 50 s (after Berserker Rage)
Undead "Grave Leap" Crypt 130 food 60 wood 40 iron 30 gold 48 s (after Ghoul Frenzy)
How it differs from the Ceratopsian Lancer's charge
The Lancer must run 8 m in a straight line at full speed, hits for x2.5, FLINGS a man through the air and stuns
him 0.6 s, with 6 s between. That stays the heavy cavalry hammer. War Charge is the infantry version: shorter,
+50% not +150%, a 1 m shove not a throw, never kills by flinging, and it does not need a straight run-up.
How the AI would use it
Research: added to each faction's research list at the point it already buys its second melee upgrade, only if
it owns 4 or more of the units that benefit. In a fight it needs no new behaviour, because the charge is passive:
the AI's armies already attack-move into contact. One small rule: an AI army about to engage waits to attack-move
from at least 10 m out rather than creeping in, so the charge is not wasted.
What it touches in code
Rules: yes (RulesRevision +1). TaskSystem.EngageSprint gains the charged-blow check and the shove (the knockback
and stun code the Lancer already uses); MovementSystem reads a second speed factor; Catalog gets three researches.
Saves: one new research bit per faction in the player's upgrade mask. If the mask has free bits no format change;
otherwise SaveGame.FormatVersion +1, old saves read as "not researched". Per-unit state reuses the same spare
timer the engage sprint uses, plus "distance charged" in the field the Lancer uses, so no new unit fields.
Net: no new command (it is passive and research uses the existing Research command), so NetProtocol stays.
Presentation: a charge pose or the Run clip at a higher rate, a hit flash and a heavier impact sound on the
charged blow, a small dust kick. The research needs an icon and a tooltip per faction.
Balance check before shipping: the melee-vs-ranged probe from this job plus the unit matchup tests, since the
stagger interrupts archers.
LIGHTER ALTERNATIVE: "Hard Charge", one shared research, no shove.
One research per faction at the same buildings (about 120 food / 50 iron, 40 s). It applies to ALL that faction's
melee infantry and simply upgrades the engage sprint everyone has: x1.4 instead of x1.25, from 9 m instead of 7,
and the first blow after a sprint does +25% damage. No shove, no stagger, no new animation, no exclusions list.
Cheaper to build (a day, not several), almost no risk to ranged balance, but it reads less in a fight: the units
arrive quicker and hit a little harder, they do not visibly knock the enemy line back.
Recommendation: the full War Charge. The owner asked for melee that feels "weighty and heavy and raw" and for units
that look like they are really hitting each other; the 1 m shove and the stagger on impact are what the eye reads as a
hit. The lighter version is the fallback if the stagger turns out too strong against archers in the probe.
MELEE ENGAGE SPRINT - report, 2026-11-09 (work in local file: PrimevalMirror only, nothing committed) WHAT THE OWNER WILL SEE Every melee soldier of every faction (foot, riders, heroes, war beasts such as the war triceratops and tame/risen tyrants) runs the last stretch at its target a quarter faster, then stops dead at striking distance and swings. It happens on an attack order, on attack-move, patrol, hunt, and when an idle soldier picks a target itself. Not for: archers and throwers, healers, workers (they keep their pace even when they fight), siege, wild animals, plain move orders, hold-ground, or a target behind trees or a wall (the line to it must be open). THE NUMBERS (Catalog.cs, four constants) starts within 7 m of its reach EngageSprintMeters = 7 speed x1.25 EngageSprintPercent = 125 lasts 3 s at most EngageSprintTenths = 30 4 s rest before the next sprint EngageSprintRestTenths = 40 (the rest counts from the end of the sprint) Why these: a Clubman 9 m from a standing Grunt was in reach after 53 ticks (1.77 s) and is now there after 44 (1.47 s); peak speed 4.11 -> 4.90 m/s. I also tried x1.40 from 9 m: 40 ticks, and no clearer balance effect (below). x1.25 is the "slightly increased speed" asked for and leaves room above it for the upgradable charge (proposal). The Run clip: people already switch from Walk to Run above about 2.3 m/s, so melee soldiers were already in Run at march pace; in the sprint the same Run clip plays about a quarter faster. No presentation code changed. HOW IT IS BUILT (smallest change, no new system) TaskSystem.AttackTick and AttackTargetInPlace (the two places a soldier walks in on a unit) start the sprint when it is close enough, rested, and the line is open (EngageSprintDue / StartEngageSprint), and end it on arrival (EndEngageSprint). MovementSystem multiplies the unit's pace while the sprint runs and the unit is still closing on a target nearby. The firm stop is the existing one: in reach the task goes to Work and movement zeroes the velocity. State: ONE timer, kept in WildMoves.NextChargeTick, which a player's soldier never used (it is the wild beasts' charge timer; the code base already reuses these fields this way). Already saved and already in the state hash. VERSION BUMPS RulesRevision 115 -> 116 (comment added). Save format: NOT bumped (no new field; an old save reads the timer as 0 = free to sprint). NetProtocol: NOT bumped (no new command or wire format). Replay format: NOT bumped (replay headers carry the rules revision, so older replays are already marked as older rules). TESTS New file Tests/EditMode/MeleeEngageSprintTests.cs, 5 tests + 1 explicit balance probe. On the code as found (local file: base1): 2 FAILED as intended - no sprint (peak 4.11 m/s against a 3.92 walk, 53 ticks to reach where walking takes 49.9) and no sprint/rest pattern. After the change: 5 of 5 pass. - closes faster: peak 4.90 m/s, in reach after 44 ticks - bowman, villager, clubman on a plain move: peaks 4.08 / 3.41 / 4.08 m/s against walks of 4.08 / 3.31 / 4.08 - cap and rest, chasing a target kept 5 m ahead for 16 s: sprints of 87 and 90 ticks, rest 120 ticks (30 ticks = 1 s) - firm arrival, 2 s after arriving: 2.46 m from the target the whole time (bodies would touch at 0.88, reach 2.48), moved 0.00 m, 0 reversals, never past the target - six Clubmen on one Grunt after 6 s: 6 of 6 swinging, closest pair exactly touching (1.00), none merged Related groups (local file: related1), 181 tests: 177 passed, 4 failed: 1. FormationMarchTests.TightResultsAreByteForByteAsBefore - CAUSED BY THIS CHANGE, expected: it is a plain golden state hash of a run in which twelve Clubmen attack-move into two enemies. Re-pinned 5148417284088240230 -> 9163312606696312740 with a comment. This is the only golden I re-pinned. 2. PteranodonFlightUnchangedTests.ItsFlightOverAVersusMapIsUnchanged - NOT mine: it fails with the same two hash values on the code as found (local file: base_related). Left alone. 3. StateHashChunkTests.SavedCombatFixtureHashesRemainBitIdentical - known stale. Left alone. 4. MountedRagdollEligibilityTests.CurrentThirtySevenEntryPolicy... - known stale. Left alone. Groups run: MeleeEngageSprint, SquadSpacing, FormationMarch, FormationRank, MovementFeel, CombatBatch, CombatDepth, CombatRealism, AttackScheduleTask, TribeRiders (Lancer charge), MissionWaveAttackMove, OrcRoster, UndeadRoster, Roster, AiArmyControlFix, AiTests.IdenticalAiGamesProduceIdenticalStateHashes, StateHashChunk, Simulation, SaveGame, Replay, HeroLevel, PteranodonFlightUnchanged, MountedRagdollEligibility. Peak editor memory 5.7 GB on that run. After the re-pin (local file: final_tests): MeleeEngageSprint + FormationMarch + SquadSpacing + TribeRiders, 41 of 41 pass. BALANCE, MEASURED (explicit probe LogMeleeAgainstRangedLines: equal cost, 1200 each, a melee squad attack-moved at a standing ranged line 26 m away, no AI, 60 s, 4 seeds; the figure is the share of starting health a side has left) before x1.25 / 7 m (chosen) x1.40 / 9 m (tried) Clubman vs Axe Thrower ranged keeps 0.61 ranged keeps 0.50 ranged keeps 0.43 (ranged wins 4/4 in all) Grunt vs Bowman melee keeps 0.59 melee keeps 0.63 melee keeps 0.58 (melee wins 4/4 in all) Ghoul vs Bowman melee keeps 0.54 melee keeps 0.66 melee keeps 0.64 (melee wins 4/4 in all) Spear Warrior vs Sk. Archer ranged keeps 0.71 ranged keeps 0.64 ranged keeps 0.64 (ranged wins 4/4 in all) Reading: melee does a little better against ranged (roughly 0.05-0.12 of a squad's health), no matchup changes winner, ranged is not made pointless. Four seeds is a small sample: single runs swing by about 0.1, so treat the size of the shift as rough; the direction is consistent in all four matchups for the chosen numbers. TEST PLAYER AND CLIPS local file: CatapultTest was rebuilt (local file: build1, succeeded, no other Unity running) and now holds rules 116. Before/after frame sets of 6 Grunts against 6 Bowmen on the duel stage (300 frames each, no video encoder on this machine, so frames plus a contact sheet): local file: before and local file: before_sheet.jpg (the 10:36 test build, rules 115) local file: after and local file: after_sheet.jpg Bluntly: these clips are poor evidence. The duel stage dropped the fight beside a hut, the camera is far out, and a quarter more speed over 7 m is not something you can read off the sheets. Judge the feel in a real game. NOT VERIFIED - Nobody has looked at it in play. "Feels firm / weighty" is the owner's call; the tests only prove it stops without sliding, overlapping or jittering for a Clubman against a standing target. - Firm arrival was measured for one unit type on a standing target. Riders (up to about 9 m/s in the sprint), heroes and war beasts were not measured one by one; the squad test covers Clubmen only. - A moving target: only the cap/rest behaviour was tested, not how the stop looks when the target walks. - No AI-vs-AI games, no long balance matrix, no multiplayer session. Determinism rests on the existing hash tests. - Loading an older save into this build was not tested specifically (SaveGameTests pass; no format change). - Workers never sprint, even when fighting. That is my reading of "not workers"; say so if militia should sprint. - The full related set was not re-run after the one re-pin, only the four classes listed. - Nothing weighty was added to the hit itself (no shove, stagger or impact effect): that is the charge proposal. FILES changes.diff (every file changed, against the files as found), CHARGE_PROPOSAL.txt (part 2, design only), work (runner, capture script, copies of the original files in local file: orig), logs. No art, .fbx or art .meta touched; git status shows no settings or material noise from my runs. CatapultInstallTemp.cs left in place.